Getting Started & CLI

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against NestJS 12.1 (npm @nestjs/core 12.1.2, published 2026-09-30), @nestjs/cli 12.0.8, @nestjs/schematics 12.0.6, Node.js 20.19+ or 22.12+ to run an application (a newer Node.js to use the generators), TypeScript 6 (6.0.x, what the Nest 12 templates install), Express 5.2 and Fastify 5.12, all as of 2026-10-02. It takes you from an empty directory to a running, watched, debuggable application, and documents every command of the CLI together with the nest-cli.json options and builders behind it. The commands and snippets apply to @nestjs/cli 12.0.8 on Node.js 24.16 (ESM and CommonJS projects); the app grows into the Bookshelf over the following pages. For what NestJS is and how it is layered, see Introduction & Architecture.

For installing Node.js and choosing a release line, see Installing Node.js and Release Lines & LTS in the Node.js section.

Requirements

There are two different Node.js floors in Nest 12, and mixing them up is the most common first-day surprise:

What you do Minimum Node.js Why

Run a Nest 12 application

20.19+, or 22.12+ on the 22.x line

The v12 packages are ESM-only. A CommonJS application loads them through require(esm), which is unflagged from 20.19 and 22.12. The 21.x line never received it.

nest new, nest generate, nest upgrade

22.22.3+, 24.15+ or 26+

The schematics (@nestjs/schematics) inherit the floor of the Angular devkit they are built on. The 23.x and 25.x lines and earlier 22.x/24.x releases are excluded.

Run the CLI binary itself (nest build, nest start, nest --help)

20.11+

The @nestjs/cli package declares this engine range.

In practice: install the latest active LTS release of Node.js and both floors are met. Pick the bare minimum only for a specific reason, and remember that Node.js 20.19 can run your application but cannot scaffold or generate code. Two more requirements are worth knowing:

  • ICU. The CLI needs a Node.js binary built with internationalization support (ICU), such as the official binaries from nodejs.org. Check yours with the command below; it prints a version, and undefined means the binary has no ICU.

  • AWS Lambda. Lambda disables require(esm) on its Node.js 20, 22 and 24 runtimes. A CommonJS Nest 12 function needs NODE_OPTIONS=--experimental-require-module in its environment (append it to existing flags).

node --version
node -p process.versions.icu

NestJS is written in TypeScript and also supports plain JavaScript (JavaScript needs Babel because Nest relies on recent language features). TypeScript 6 (~6.0.2) is a dependency of the v12 CLI and is pinned by the generated project. TypeScript 7, the native compiler, is the latest tag on npm at the time of writing but is not what nest new installs, so do not upgrade it by hand and expect the CLI to follow.

Installing the CLI

The Nest CLI is the nest binary from the @nestjs/cli package. It scaffolds projects, runs and builds them, and generates code. Install it globally:

npm i -g @nestjs/cli
nest --version
nest --help
12.0.8
Usage: nest <command> [options]

Options:
  -v, --version                                   Output the current version.
  -h, --help                                      Output usage information.

Commands:
  new|n [options] [name]                          Generate Nest application.
  build [options] [apps...]                       Build Nest application.
  start [options] [app]                           Run Nest application.
  info|i                                          Display Nest project details.
  add [options] <library>                         Adds support for an external library to your project.
  deploy                                          Deploy your application to the cloud (powered by Mau).
  upgrade|update [options]                        Upgrade your project to the latest NestJS major version.
  generate|g [options] <schematic> [name] [path]  Generate a Nest element.
  ...

A global install means every project on the machine runs the same CLI version, and nothing in a project’s package.json pins it. There are three ways to deal with that:

Approach When to use it

npm i -g @nestjs/cli

Day-to-day development, and what the rest of the documentation assumes. You are responsible for keeping it current.

npx @nestjs/cli@latest new bookshelf

One-off scaffolding without a global install; always the latest published CLI.

npm i -D @nestjs/cli (local dev dependency)

Every generated project already has it. The package.json scripts (npm run build, npm run start:dev) use this local copy, so every team member and the CI runner build with the same version.

The split matters: nest new and nest generate are not part of the build pipeline and have no package script, so they use the global (or npx) binary, while nest build and nest start should be run through the package.json scripts so the project controls their version. Command syntax is uniform: nest <commandOrAlias> <requiredArg> [optionalArg] [options]. Commands and many options have aliases, and omitted required arguments are prompted for:

nest new bookshelf --dry-run     # long form
nest n bookshelf -d              # identical, with aliases
nest generate --help             # help for one command

Creating a Project with nest new

nest new bookshelf
cd bookshelf
npm run start:dev

The command prompts for what you did not pass as an option: the project name, the package manager, the module system and whether to set up NestJS Observe. It then creates the directory, writes the configuration files and a src/ and test/ skeleton, installs the dependencies and initializes a git repository.

The prompts

  • Package manager — npm, yarn, pnpm or bun (bun is supported since v12). The chosen manager must already be installed globally; Nest uses it for the install step and records nothing else about it.

  • Module system — ESM (the default; Vitest for testing) or CommonJS (Jest for testing). Both use oxlint for linting. The generated code differs in a few visible ways, summarised below.

  • NestJS Observe — whether to wire the @nestjs/observe SDK (the official observability platform; pre-1.0) into AppModule and NestFactory.create(). In an interactive terminal the prompt defaults to yes; without a terminal (CI) it is skipped and Observe is not added. Use --observe / --no-observe to decide without being asked.

In a non-interactive environment such as CI, pass the name and --package-manager; the module system then defaults to ESM, and Observe is only set up if you pass --observe:

nest new bookshelf --package-manager npm --skip-git --no-observe

Options

Option Effect

--directory [directory]

Destination directory.

-d, --dry-run

Print the files that would be created without writing anything. Use it to see exactly what a command would do.

-g, --skip-git

Do not initialize a git repository.

-s, --skip-install

Do not install packages.

-t, --skip-tests

Do not generate test files.

-p, --package-manager [name]

npm, yarn, pnpm or bun.

-l, --language [language]

TS (default) or JS; case-insensitive, typescript and javascript are also accepted.

-c, --collection [name]

Schematics collection to use (the package name of an installed npm package that contains the schematic).

--strict

TypeScript strict mode. Enabled by default; there is no flag to turn it off.

--format

Format the generated files with Prettier.

--observe / --no-observe

Set up @nestjs/observe, or skip it, without a prompt.

Strict mode

New projects are generated with TypeScript’s strict flag on, and with strictPropertyInitialization switched off. That second exception exists because decorated class properties (DTOs, entities, injected fields) are assigned by a framework, not by a constructor; with the flag on, every name: string; would need a ! or an initializer. To opt out of strict mode, set "strict": false in the generated tsconfig.json — the --strict option cannot do it. Keep it on: it catches the undefined and null mistakes that otherwise surface at runtime in a request handler. See TypeScript for NestJS for the ! convention on DTO properties.

ESM versus CommonJS

Both variants run the same Nest code; they differ in module format and tooling. The facts below come from projects generated with CLI 12.0.8 (the CommonJS variant through the application schematic with --type=cjs, because nest new offers it only as a prompt).

ESM (default) CommonJS

package.json

"type": "module"

no "type" field

Relative imports in src/

must carry the .js extension: import { AppModule } from './app.module.js'

extensionless: import { AppModule } from './app.module'

main.ts bootstrap

top-level await bootstrap();

void bootstrap();

Test runner

Vitest (vitest.config.ts, vitest.config.e2e.ts, vite-tsconfig-paths)

Jest 30 with ts-jest (jest.config.ts, test/jest-e2e.json)

Test globals in tsconfig.json

"types": ["vitest/globals", "node"]

"types": ["node", "jest"]

supertest import in e2e tests

default import: import request from 'supertest'

same generated code (supertest/types is imported without the .js extension)

Extra dev dependencies

none beyond Vitest

ts-jest, ts-loader, ts-node, tsconfig-paths, @types/jest

test script

vitest run

node --experimental-vm-modules ./node_modules/jest/bin/jest.js

Both variants compile with "module": "nodenext" and "moduleResolution": "nodenext", which is what makes the .js extensions mandatory in the ESM variant. Two practical consequences: existing CommonJS projects can upgrade to v12 and stay CommonJS (nest upgrade deliberately leaves the module format alone), and Jest can load the ESM-only v12 packages only on Node.js 24.9 or newer. The .js extension rule, emitDecoratorMetadata and the other tsconfig.json settings are explained in TypeScript for NestJS and, for the compiler options themselves, in TSConfig & Compiler Options.

The Generated Files

An ESM project created with nest new bookshelf --package-manager npm --skip-git contains (before installation adds node_modules):

bookshelf/
  .oxlintrc.json
  .prettierrc
  README.md
  nest-cli.json
  package.json
  tsconfig.json
  tsconfig.build.json
  vitest.config.ts
  vitest.config.e2e.ts
  src/
    app.controller.ts
    app.controller.spec.ts
    app.module.ts
    app.service.ts
    main.ts
  test/
    app.e2e-spec.ts
File Purpose

src/main.ts

The entry file. Uses NestFactory to create the application and start the HTTP listener.

src/app.module.ts

The root module of the application.

src/app.controller.ts

A basic controller with a single route (GET /).

src/app.controller.spec.ts

The unit test of the controller.

src/app.service.ts

A basic service with a single method.

test/app.e2e-spec.ts

An end-to-end test that boots the whole AppModule and calls it with supertest.

nest-cli.json

CLI configuration: schematics collection, sourceRoot, compilerOptions, generateOptions (see below).

tsconfig.json, tsconfig.build.json

Compiler settings; the build one extends the first and restricts the build to src/.

.oxlintrc.json, .prettierrc

oxlint and Prettier configuration.

vitest.config.ts, vitest.config.e2e.ts (ESM)

Unit tests match **/*.spec.ts, end-to-end tests **/*.e2e-spec.ts.

The controller, service and module are the three building blocks the next pages explain; the structure encourages one directory per feature module. The generated tsconfig.json is the one to know:

{
  "compilerOptions": {
    "module": "nodenext",
    "moduleResolution": "nodenext",
    "resolvePackageJsonExports": true,
    "esModuleInterop": true,
    "isolatedModules": true,
    "declaration": true,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "target": "ES2023",
    "sourceMap": true,
    "outDir": "./dist",
    "rootDir": ".",
    "incremental": true,
    "skipLibCheck": true,
    "strict": true,
    "strictPropertyInitialization": false,
    "types": ["vitest/globals", "node"]
  }
}

experimentalDecorators and emitDecoratorMetadata are what make constructor injection work: TypeScript emits the constructor parameter types and Nest reads them as injection tokens (explained in Decorators & Metadata). sourceMap is what makes IDE debugging map dist/ back to your .ts files (see Debugging). tsconfig.build.json sets "rootDir": "./src" and excludes test, dist and spec files, which is why the compiled entry point is dist/main.js and not dist/src/main.js. The project is also formatted and linted out of the box (npm run lint runs oxlint --type-aware src/ test/, npm run format runs Prettier). Source: NestJS: First steps - Linting and formatting

main.ts and NestFactory.create

The file src/main.ts bootstraps the application:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module.js';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(process.env.PORT ?? 3000);
}
await bootstrap();

(The CommonJS variant imports './app.module' and ends with void bootstrap();.) Starting it with npm run start and calling it:

curl http://localhost:3000/
Hello World!

NestFactory (from @nestjs/core) is the static entry point of the framework. Its create() method takes the root module (and optional options or an adapter) and returns an application object implementing INestApplication. Everything else — global pipes and filters, shutdown hooks, app.listen(), app.close() — is a method of that object and is covered in the pages that follow. The listen() port comes from the PORT environment variable, falling back to 3000 (do not hardcode it; containers and platforms inject it).

Where bootstrap sits in the lifecycle

flowchart LR A["node dist/main.js"] --> B["NestFactory.create(AppModule)
scan modules, build the DI graph,
instantiate providers"] B --> C["app.listen(port)
init: onModuleInit,
onApplicationBootstrap"] C --> D["HTTP server accepts requests
middleware, guards, pipes,
handler"] D --> E["app.close()
onModuleDestroy,
onApplicationShutdown"]

create() resolves the module graph and instantiates providers; onModuleInit() and onApplicationBootstrap() hooks run when the application is initialized, which happens when you call app.init() or app.listen() (or, in tests, createNestApplication().init()), and the HTTP port is bound only afterwards. The per-request path (middleware, guards, interceptors, pipes, filters) begins after listen(); see Request Lifecycle, and Lifecycle Events & Shutdown for the hooks. Source: NestJS: Lifecycle events

Failing at startup: abortOnError

If anything throws while the application is being created (for example a provider factory that cannot reach its database), Nest logs the error and the process exits with code 1. To receive the error instead — to close resources, report it, or set your own exit code — disable abortOnError:

import { NestFactory } from '@nestjs/core';
import { FailingModule } from './failing.module.js';

async function bootstrap() {
  try {
    const app = await NestFactory.create(FailingModule, {
      abortOnError: false,
    });
    await app.listen(process.env.PORT ?? 3000);
  } catch (error) {
    console.error('Bootstrap failed, cleaning up before exit', error);
    process.exitCode = 1;
  }
}
await bootstrap();

An end-to-end check of this behaviour (await expect(NestFactory.create(FailingModule, { abortOnError: false })) .rejects.toThrow(…​)) passes; without the option the same call terminates the Node.js process, which is also why a test that needs to assert on the failure must set it.

Express and Fastify

Nest is platform-agnostic. Two HTTP platforms are supported out of the box: @nestjs/platform-express (Express 5, the default and already installed) and @nestjs/platform-fastify. Each has its own application interface: NestExpressApplication and NestFastifyApplication. Pass the type to create() only when you need the underlying platform API; otherwise the plain INestApplication is enough.

import { NestFactory } from '@nestjs/core';
import type { NestExpressApplication } from '@nestjs/platform-express';
import { AppModule } from './app.module.js';

async function bootstrap() {
  const app = await NestFactory.create<NestExpressApplication>(AppModule);
  app.set('trust proxy', 'loopback'); // Express-only API, available through the typed app
  await app.listen(process.env.PORT ?? 3000);
}
await bootstrap();

To use Fastify, install the adapter (npm i @nestjs/platform-fastify) and pass a FastifyAdapter:

import { NestFactory } from '@nestjs/core';
import {
  FastifyAdapter,
  NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { AppModule } from './app.module.js';

async function bootstrap() {
  const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter(),
  );
  // Fastify listens on localhost only unless a host is given.
  await app.listen(process.env.PORT ?? 3000, '0.0.0.0');
}
await bootstrap();

Both entry files compile and start with PORT=3555 node dist/getting-started/main-express.js / main-fastify.js, and curl localhost:3555 returns Hello Bookshelf! from each. The differences you meet first:

Express (default) Fastify

Package

@nestjs/platform-express (installed by nest new)

@nestjs/platform-fastify (install it)

create() generic

NestExpressApplication

NestFastifyApplication, with new FastifyAdapter() as second argument

Default listen host

Node.js default

localhost only; pass '0.0.0.0' to accept outside connections (containers)

Platform escape hatch

typed app methods such as app.set(…​)

app.getHttpAdapter().getInstance() (the Fastify instance), app.inject() in tests

Redirects

res.redirect(url)

res.status(302).redirect(url)

Express middleware ecosystem

works

recipes that rely on Express may not; use the Fastify equivalents

The complete Fastify story (plugins, hooks, options, benchmarks and what breaks when you switch) is in Fastify & HTTP Platforms.

Running the Application: the start Scripts

A Nest application is a standard TypeScript application that must be compiled before Node.js runs it. The CLI is a thin, replaceable layer: nest build wraps the compiler (tsc or swc for standard projects, Rspack for monorepos) and adds only path mapping and CLI plugins, and nest start ensures the project is built and then launches node portably. The scripts nest new writes into package.json are:

{
  "scripts": {
    "build": "nest build",
    "deploy": "nest deploy",
    "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
    "start": "nest start",
    "start:dev": "nest start --watch",
    "start:debug": "nest start --debug --watch",
    "start:prod": "node dist/main",
    "lint": "oxlint --type-aware src/ test/",
    "test": "vitest run",
    "test:watch": "vitest",
    "test:cov": "vitest run --coverage",
    "test:debug": "vitest --inspect-brk --no-file-parallelism",
    "test:e2e": "vitest run --config ./vitest.config.e2e.ts"
  }
}
Script Runs Use it for

npm run build

nest build

Compile to dist/. First deletes dist/ because the generated nest-cli.json sets deleteOutDir.

npm run start

nest start

Compile once and run. No restart on changes.

npm run start:dev

nest start --watch

Daily development: watch, recompile, restart (see Hot reload).

npm run start:debug

nest start --debug --watch

Watch mode with the Node.js inspector open on port 9229 (see Debugging).

npm run start:prod

node dist/main

Production: runs the already compiled output with plain Node.js. It does not build; run npm run build first.

npm run deploy

nest deploy

Deploy with Mau (see nest deploy).

npm run lint, npm run format

oxlint, Prettier

Static checks and formatting, also usable in CI and Git hooks.

npm run test, test:e2e, …​

Vitest (ESM) or Jest (CJS)

Unit and end-to-end tests; the testing page covers them.

Extra options are passed to the CLI after --. For example npm run start — -b swc starts with the SWC builder without touching nest-cli.json. Because nest start forwards process-level flags, you can also load environment files and pass application arguments:

echo "PORT=3111" > .env.dev
nest start --env-file .env.dev -- mode=demo

With a main.ts that logs process.env.PORT and process.argv.slice(2), this prints PORT=3111 ARGV=["mode=demo"] and serves on port 3111. --env-file is relative to the current directory, may be repeated, and is for development: in production set real environment variables or use the configuration module (see Configuration). A typical production build-and-run sequence is:

npm ci
npm run build
npm run start:prod

That sequence is the minimum; containerizing it properly (multi-stage builds, pruning dev dependencies, a non-root user) is covered in Deployment.

The CLI Commands

Run nest <command> --help for the options of any command. The commands, with their aliases from the installed --help output:

Command Alias Description

new

n

Scaffold a new standard mode application.

generate

g

Generate or modify files from a schematic.

build

Compile an application or workspace into an output folder.

start

Compile and run an application (or the default project of a workspace).

add

Import a library packaged as a Nest library, running its install schematic.

upgrade

update

Upgrade an existing project to the latest NestJS major version.

deploy

Deploy to the cloud, powered by Mau.

info

i

Display installed Nest packages and system information.

nest generate

nest generate <schematic> <name> [options] (alias nest g) generates, and sometimes modifies, files from a schematic of the @nestjs/schematics collection. These are all of them, with the aliases and outputs of schematics 12.0.6 for nest g <alias> <name> (names are singular and lowercase; the class and file names derive from them):

Schematic Alias What it creates (for the name authors unless stated)

module

mo

authors/authors.module.ts; imports it into the closest module.

controller

co

authors/authors.controller.ts and spec; adds it to the closest module’s controllers.

service

s

authors/authors.service.ts and spec; adds it to the closest module’s providers.

provider

pr

clock/clock.ts and spec (no suffix: it is a plain provider class); adds it to providers.

class

cl

money/money.ts and spec.

interface

itf

shape/shape.interface.ts; no spec.

guard

gu

auth/auth.guard.ts and spec. Not registered anywhere: you apply it.

interceptor

itc

timing/timing.interceptor.ts and spec. Not registered.

pipe

pi

parse/parse.pipe.ts and spec. Not registered.

filter

f

http/http.filter.ts and spec. Not registered.

middleware

mi

logger/logger.middleware.ts and spec. Not registered (configure it in a module).

decorator

d

roles/roles.decorator.ts; no spec. As of v12 it uses Reflector.createDecorator().

gateway

ga

chat/chat.gateway.ts and spec (WebSocket gateway); adds it to providers.

resolver

r

reviews/reviews.resolver.ts and spec (GraphQL resolver); adds it to providers.

resource

res

A complete CRUD resource; see below.

library

lib

A library inside a monorepo (converts a standard project to monorepo mode).

sub-app

app

A new application inside a monorepo (converts a standard project to monorepo mode).

application

application

A new application workspace; what nest new runs.

configuration

config

Generates a CLI configuration file (nest-cli.json); fails with a merge conflict if one exists.

Two details on the table. The official docs list the monorepo application schematic as app; in the installed CLI the schematic is named sub-app with the alias app, and both spellings work. And the "Not registered" rows are not an omission: guards, interceptors, pipes and filters are applied with decorators or useGlobal* calls, and middleware in configure(), so the generator cannot know where you want them. The decorator schematic produces:

import { Reflector } from '@nestjs/core';

export const Roles = Reflector.createDecorator<string[]>();

A generated guard is a minimal implementation of CanActivate that always allows the request:

import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    return true;
  }
}

Source: NestJS: Guards

Never ship it as is: the template is permissive on purpose, so write the real check before applying it (see Guards).

Generate options

Option Effect

-d, --dry-run

Report what would be created or updated, change nothing. Combine it with every example here to preview.

-p, --project [project]

The project the element belongs to (monorepo mode).

--flat / --no-flat

--flat writes the files directly in the target folder; the default is a folder named after the element.

--spec / --no-spec

Generate (default) or skip the .spec.ts file.

--spec-file-suffix [suffix]

Use a custom suffix for spec files.

--skip-import

Do not import/register the generated element in its closest module.

-c, --collection [name]

Use another schematics collection (an installed npm package).

--format

Format the generated files with Prettier.

--type <type>

resource only: rest, graphql-code-first, graphql-schema-first, microservice or ws.

--crud [value]

resource only: whether to generate the CRUD entry points (true or false).

A path in the name becomes directories, and the generated element is imported into the closest module found walking up from that directory:

nest g mo authors --dry-run
nest g mo a/b/c --flat --dry-run
nest g co authors --skip-import --dry-run
CREATE src/authors/authors.module.ts
UPDATE src/app.module.ts
CREATE src/a/b/c.module.ts
UPDATE src/app.module.ts
CREATE src/authors/authors.controller.spec.ts
CREATE src/authors/authors.controller.ts

nest g resource: the CRUD generator

Adding a feature normally means a module, a controller, a service, an entity and DTOs, plus tests. nest g resource (alias res; TypeScript only) generates all of it and wires it into AppModule:

nest g resource books --type rest --crud
CREATE src/books/books.controller.spec.ts
CREATE src/books/books.controller.ts
CREATE src/books/books.module.ts
CREATE src/books/books.service.spec.ts
CREATE src/books/books.service.ts
CREATE src/books/dto/create-book.dto.ts
CREATE src/books/dto/update-book.dto.ts
CREATE src/books/entities/book.entity.ts
UPDATE package.json
UPDATE src/app.module.ts
- Installing packages (npm)...
Packages installed successfully.

Run without --type and --crud, the command prompts for the transport layer and whether to generate entry points. It adds @nestjs/mapped-types to package.json (the UpdateBookDto uses PartialType) and installs it. The generated controller and service are:

@Controller('books')
export class BooksController {
  constructor(private readonly booksService: BooksService) {}

  @Post()
  create(@Body() createBookDto: CreateBookDto) {
    return this.booksService.create(createBookDto);
  }

  @Get()
  findAll() {
    return this.booksService.findAll();
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.booksService.findOne(+id);
  }

  @Patch(':id')
  update(@Param('id') id: string, @Body() updateBookDto: UpdateBookDto) {
    return this.booksService.update(+id, updateBookDto);
  }

  @Delete(':id')
  remove(@Param('id') id: string) {
    return this.booksService.remove(+id);
  }
}
@Injectable()
export class BooksService {
  create(createBookDto: CreateBookDto) {
    return 'This action adds a new book';
  }

  findAll() {
    return `This action returns all books`;
  }

  findOne(id: number) {
    return `This action returns a #${id} book`;
  }
  // update() and remove() follow the same pattern
}

CreateBookDto and Book are empty classes and the service returns placeholder strings: the generator gives you the shape, not the behaviour. Add DTO fields and validation (Pipes & Validation) and replace the service bodies with a real repository (Data Overview & TypeORM). Note also how the controller coerces the :id path parameter with a unary plus: it arrives as a string, and a non-numeric value becomes NaN, which is why production code uses ParseIntPipe.

The --type option changes what is generated (--dry-run lists the names):

--type Files created in src/<name>/

rest

<name>.controller.ts, <name>.service.ts, <name>.module.ts, dto/create-.dto.ts, dto/update-.dto.ts, entities/*.entity.ts and specs.

graphql-code-first

A resolver (<name>.resolver.ts) instead of a controller, dto/create-.input.ts and dto/update-.input.ts input types, an entity and specs.

graphql-schema-first

Like code-first, plus a <name>.graphql schema file.

microservice

A controller (for message and event patterns) and a service; with --crud false only the controller, module and service (and specs) are created.

ws

A gateway (<name>.gateway.ts) instead of a controller, plus service, module, DTOs and entity.

nest build

nest build [name…​] [options] compiles the project (or the named monorepo projects) into the output folder. Beyond compiling, it maps tsconfig path aliases and runs the enabled CLI plugins that annotate DTOs with OpenAPI or GraphQL decorators. The options:

Option Effect

-p, --path [path]

Path to the tsconfig file.

-c, --config [path]

Path to the nest-cli.json file.

-w, --watch

Watch mode (live reload). With tsc and manualRestart, type rs to restart.

-b, --builder [name]

tsc, swc or rspack.

--tsc

Force the TypeScript compiler.

--type-check / --no-type-check

Type checking when SWC is the builder.

--emit-declarations

Emit .d.ts files with the SWC builder.

--watchAssets

Also watch non-TypeScript assets (for example .graphql files).

--rspackPath [path]

Path to an Rspack configuration file.

--webpack, --webpackPath [path]

Deprecated legacy flags; use --builder rspack.

--all

Build all projects of a monorepo.

--parallel [concurrency]

With --all, build in parallel; a number caps concurrency, omitted means unlimited.

--silent

Suppress informational compiler logs.

--preserveWatchOutput

Keep old output in tsc watch mode instead of clearing the screen.

nest start

nest start [name] [options] builds if needed and runs the application. It accepts the build options above (--path, --config, --watch, --builder, --tsc, --type-check, --emit-declarations, --watchAssets, --rspackPath, --silent, --preserveWatchOutput, and the deprecated webpack flags) plus the run options:

Option Effect

-d, --debug [hostport]

Run with the Node.js inspector (--inspect).

--sourceRoot [sourceRoot]

Source root, overriding nest-cli.json.

--entryFile [entryFile]

Entry file to run, overriding nest-cli.json (default main).

-e, --exec [binary]

Binary to run instead of node.

--no-shell

Do not spawn child processes within a shell (child_process.spawn()).

--env-file [path]

Load an environment file into process.env; repeatable.

-- [key=value]

Arguments for the application, read from process.argv.

nest add

nest add <library> [options] imports a library that has been packaged as a Nest library and runs its install schematic. Options: -d/--dry-run, -s/--skip-install and -p/--project [project]. For a package that is not a Nest library, use npm install and register its module yourself. Source: NestJS: CLI usage - nest add

nest info

nest info prints system information and the installed Nest packages. Paste it into every bug report:

nest info
[System Information]
OS Version     : macOS 27.0.0
NodeJS Version : v24.16.0
NPM Version    : 11.13.0

[Nest CLI]
Nest CLI Version : 12.0.8

[Nest Platform Information]
platform-express version : 12.1.2
schematics version       : 12.0.6
testing version          : 12.1.2
common version           : 12.1.2
core version             : 12.1.2
cli version              : 12.0.8
mau version              : 0.2.8

(The command prints a banner first and, outside a project directory, the line "cannot read your project package.json file".) Mismatched @nestjs/* versions are a classic cause of odd runtime errors, and this output shows them at a glance.

nest upgrade

nest upgrade (alias update), added in v12, upgrades an existing v11 project. Update the global CLI first, because the command ships with it and only the local @nestjs/cli dependency is bumped by the upgrade:

npm i -g @nestjs/cli@latest
nest upgrade --dry-run --no-observe

Run in a minimal v11 project (CommonJS tsconfig.json, TypeScript 5), the dry run reported:

Changes applied:
  Updated NestJS packages: @nestjs/common@^12.0.0, @nestjs/core@^12.0.0, @nestjs/platform-express@^12.0.0, @nestjs/cli@^12.0.0, @nestjs/schematics@^12.0.0
  Updated "typescript" from "^5.7.3" to "^6.0.0" (required by @nestjs/cli and @nestjs/schematics v12)
  Scheduled "npm install"

Please review:
  TypeScript 6 deprecates several legacy "tsconfig.json" options ...

Action required:
  tsconfig.json uses "module": "commonjs" with a legacy module resolution, which cannot resolve
  the ESM-only NestJS 12 packages. Update "compilerOptions" as follows ...

Dry run enabled. No files written to disk.

With --dry-run nothing was written (package.json was unchanged). What the command does, in full:

  • Refuses to run on Node.js lacking require(esm) (older than 20.19, or 22.x older than 22.12).

  • Moves every recognized @nestjs/* package to its v12-compatible major (GraphQL, Apollo and Mercurius packages go to v14) and lists the unrecognized ones for you to review.

  • Bumps TypeScript to v6 and raises engines.node to >=20.19.0 if lower.

  • Moves nest-cli.json off the deprecated webpack options to --builder rspack, updating matching scripts.

  • Renames the GraphQL playground option to graphiql and switches subscriptions from subscriptions-transport-ws to graphql-ws; replaces the legacy nats package with @nats-io/transport-node; moves @nestjs/config library-specific settings under validationOptions.libraryOptions and bumps Joi to v18.

  • Bumps Jest to v30 where present (Jest loads the ESM-only v12 packages only on Node.js 24.9+).

  • Optionally installs and wires up @nestjs/observe (prompt defaults to no).

  • Checks tsconfig.json and tsconfig.build.json for settings TypeScript 6 or the ESM packages no longer support.

  • Scans sources and prints notes about behaviour changes it cannot migrate (lifecycle hook ordering, refined pipe signatures, structured logging params), then installs dependencies unless --skip-install is given and prints a report.

It deliberately does not migrate your project to ESM, Vitest or oxlint. Options: -d/--dry-run, -s/--skip-install, --observe/--no-observe, -t/--tag [tag] (an npm dist-tag such as next instead of the default ranges) and -c/--collection. Always run it on a clean git working tree, review the diff, and run your tests. The full migration checklist is on What’s Changed & Migration. Source: NestJS: CLI usage - nest upgrade

nest deploy

nest deploy [mau-options] is a thin wrapper around the Mau CLI (the official platform for deploying Nest applications on AWS) and forwards every argument to mau deploy:

nest deploy

If @nestjs/mau is not installed the command offers to add it as a dev dependency; in a non-interactive environment it fails instead of prompting, so run npm install --save-dev @nestjs/mau first (new projects already include it). nest deploy needs a Mau account; nest deploy --help shows it accepts no options of its own. Treat it as one deployment option among many: containers, serverless and Kubernetes are covered in Deployment.

nest-cli.json

Nest keeps the project metadata the CLI needs in nest-cli.json. A new project’s file is:

{
  "$schema": "https://json.schemastore.org/nest-cli",
  "collection": "@nestjs/schematics",
  "sourceRoot": "src",
  "compilerOptions": {
    "deleteOutDir": true
  }
}

The top-level properties are collection (the schematics collection; leave it), sourceRoot (the root of the source code; of the default project in monorepo mode), compilerOptions, generateOptions, and, in monorepo mode only, monorepo, root and projects (see Project Structure & Workspaces). Keep the $schema line: editors then validate and autocomplete the file.

compilerOptions

These apply to every compilation, whether from nest build or nest start, and whichever builder is used:

Property Type Description

builder

string or object

tsc, swc or rspack. An object { "type": "…​", "options": { …​ } } customizes the builder.

deleteOutDir

boolean

Remove the output directory (the outDir of tsconfig.json, ./dist by default) before every compilation.

typeCheck

boolean

Type checking for SWC projects. Default false.

emitDeclarations

boolean

Emit .d.ts files with the SWC builder. Default false.

assets

array

Non-TypeScript files to copy into the output on every compilation (see below). Not copied on incremental --watch compiles.

watchAssets

boolean

Watch all non-TypeScript assets. Overrides watchAssets inside assets entries.

manualRestart

boolean

Enable the rs shortcut to restart the server by hand. Default false.

tsConfigPath

string

Monorepo only: the tsconfig used when no project is named.

includeLibraryAssets

array

Monorepo only: library projects whose assets are copied with this application.

webpack, webpackConfigPath

boolean, string

Deprecated. Use builder (and builder.options.configPath).

Assets must live under src to be copied. To ship GraphQL schema files (or templates, images, and so on) with the build:

{
  "$schema": "https://json.schemastore.org/nest-cli",
  "collection": "@nestjs/schematics",
  "sourceRoot": "src",
  "compilerOptions": {
    "deleteOutDir": true,
    "builder": "swc",
    "typeCheck": true,
    "assets": [{ "include": "**/*.graphql", "watchAssets": true }]
  }
}

With this file, nest build prints the type-checker and SWC lines and leaves both main.js and schema.graphql in dist/. An assets entry is either a glob string or an object with include, exclude, outDir (relative to the project root; defaults to the compiler output directory) and watchAssets. In monorepo mode a project’s compilerOptions are not merged with the top-level ones, so a project-level assets array replaces the global one.

generateOptions

generateOptions sets defaults for nest generate: spec (a boolean, or an object keyed by schematic) and flat. A command-line flag always overrides it.

{
  "collection": "@nestjs/schematics",
  "sourceRoot": "src",
  "generateOptions": {
    "spec": { "service": false, "s": false },
    "flat": true
  }
}

Note the two keys for the service. In the installed CLI (12.0.8) the object key must match the schematic name as you type it: with only "service": false, nest g service tags skipped the spec but nest g s tags still created it, and { "s": false } alone behaves the other way round. List both spellings (or always use the long name). "spec": false disables spec files for everything.

Builders: tsc, SWC and Rspack

tsc swc rspack

Default for

standard projects

opt-in

monorepo projects (v12)

Type checking

yes, part of the compile

no, unless --type-check or typeCheck: true

no: a build with a type error succeeds

Output

one .js per source file

one .js per source file (same module format as tsc)

a single bundled main.js

Extra packages

typescript (installed)

@swc/cli, @swc/core

@rspack/core, webpack-node-externals, tsconfig-paths-webpack-plugin

Select with

default, or --tsc

-b swc / "builder": "swc"

-b rspack / "builder": "rspack"

SWC

SWC is a Rust-based compiler and the quickest way to speed up the development loop. Install the packages and select the builder on the command line or in nest-cli.json:

npm i --save-dev @swc/cli @swc/core
nest start -b swc
nest start -b swc --watch
{
  "compilerOptions": {
    "builder": "swc"
  }
}

To customize it, pass an object. Here the SWC settings are read from a custom .swcrc path:

{
  "compilerOptions": {
    "builder": {
      "type": "swc",
      "options": { "swcrcPath": "infrastructure/.swcrc" }
    },
    "typeCheck": true
  }
}

The builder is preconfigured for Nest. To change SWC options, put a .swcrc in the project root; for example this file (with legacyDecorator and decoratorMetadata, which Jest-with-SWC setups also need):

{
  "$schema": "https://swc.rs/schema.json",
  "sourceMaps": true,
  "jsc": {
    "parser": { "syntax": "typescript", "decorators": true, "dynamicImport": true },
    "transform": { "legacyDecorator": true, "decoratorMetadata": true },
    "baseUrl": "./"
  },
  "minify": false
}

Both configurations build and run. The SWC builder emits ES modules when package.json has "type": "module" (the default for new projects) and CommonJS otherwise; a module setting in .swcrc overrides this.

SWC and type checking

SWC only strips types; it never reports a type error. With a deliberate type error in the source (a number constant assigned the string 'forty-two') the three variants behaved differently:

nest build                       # tsc
nest build -b swc                # SWC without checking
nest build -b swc --type-check   # SWC plus tsc in noEmit mode
src/bad.ts:1:14 - error TS2322: Type 'string' is not assignable to type 'number'.
Found 1 error(s).

SWC  Running...
Successfully compiled: 17 files with swc (36.11ms)

1 export const answer: number = 'forty-two';
   ~~~~~~
Error  Found 1 type error(s) during compilation.

Plain SWC built the broken file without complaint. With --type-check (or "typeCheck": true) the CLI runs tsc in noEmit mode alongside SWC and fails the build on errors. In watch mode the check runs asynchronously, so the application restarts quickly and reports type problems next to it; in CI always enable it, or run tsc --noEmit as a separate step. The --type-check flag also executes CLI plugins and writes the serialized metadata file the application loads at runtime.

Known SWC limits, from the official page: it handles circular imports poorly, so wrap entity property types (profile: Relation<Profile> with TypeORM’s Relation, or a WrapperType<T> alias of your own, also used with forwardRef injections); and in a monorepo CLI plugins need the PluginMetadataGenerator. The docs claim SWC is about 10x (overview page) or 20x (SWC page) faster than tsc; the two pages disagree and the factor depends on the project, so measure your own. On a fresh 17-file project, SWC compiled the files in 36 ms. Source: NestJS: SWC - Common pitfalls

Rspack

Rspack bundles the application into a single file and is the default builder for monorepo projects in v12 (it replaces webpack, which is deprecated together with --webpack, --webpackPath and the webpack and webpackConfigPath options; nest upgrade migrates them). It requires extra packages that the CLI checks for. Selecting it without them fails with:

The "webpack-node-externals" package is required when using the rspack compiler but could not be found. Please install it:

  npm install --save-dev @rspack/core webpack-node-externals tsconfig-paths-webpack-plugin

After installing them, "builder": "rspack" (or nest build -b rspack) produces dist/main.js and dist/main.js.map only, and node dist/main.js serves Hello World!. Use --rspackPath [path] for a custom Rspack configuration. Standard projects default to tsc, monorepos to Rspack, which the docs say can significantly reduce build times on larger codebases and emits one file with all components. Rspack did not report a type error that tsc reports (the same broken file built successfully), so run tsc --noEmit in CI when you use it. Details of monorepo builds are in Project Structure & Workspaces. Source: NestJS: CLI workspaces - Specified compiler, NestJS: CLI usage - nest build

Hot Reload and Watch Mode

nest start --watch (npm run start:dev) recompiles on change and restarts the process. It is live reload, not hot-module replacement: application state is lost on every change. With the SWC builder, editing app.service.ts while nest start --watch is running produces Successfully compiled src/app.service.ts with swc, a type-checker pass, a second Starting Nest application…​ and a changed curl response a few seconds later. SWC recompiles only the changed file, which is why it feels faster than tsc on a large project.

npm run start:dev
# or, faster compile with type checking in the background:
nest start --watch -b swc --type-check

Related switches:

  • --watchAssets or compilerOptions.watchAssets also watches non-TypeScript files (for example .graphql schemas).

  • --preserveWatchOutput keeps earlier output instead of clearing the screen in tsc watch mode.

  • Manual restart. With "manualRestart": true in compilerOptions, typing rs in the terminal restarts the server (the process restarts after rs is sent to a running nest start --watch).

  • HMR. The official Hot reload recipe uses webpack’s Hot-Module Replacement for CommonJS projects. Webpack is deprecated in v12 and the recipe’s own warning points new projects to --builder rspack; for daily work the SWC watch loop above is the supported fast path. Source: NestJS: Hot reload

The development loop

flowchart LR A["Edit a .ts file"] --> B["nest start --watch
tsc or swc recompiles"] B --> C{"Type errors?"} C -- "yes" --> D["Compiler output
shows the errors"] D --> A C -- "no" --> E["Process restarts
NestFactory.create runs again"] E --> F["curl, Swagger UI or a test
exercises the endpoint"] F --> G["npm run test / test:e2e"] G --> H["Commit, npm run build,
npm run start:prod or deploy"] F --> A

Debugging in an IDE

nest start --debug starts the compiled application with the Node.js inspector (--inspect); start:debug adds --watch. The optional value is a host and port:

nest start --debug
nest start --debug 0.0.0.0:9230
Debugger listening on ws://127.0.0.1:9229/...
Debugger listening on ws://0.0.0.0:9230/...

The default is port 9229 on the loopback address. Do not bind the inspector to 0.0.0.0 on a shared network: the protocol offers remote code execution to anyone who can reach it (the second example is for a container where the port is published only to your machine). Any client that speaks the inspector protocol can attach. In VS Code, run npm run start:debug in a terminal and attach with a .vscode/launch.json like this:

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "attach",
      "name": "Attach to Nest",
      "port": 9229,
      "restart": true
    }
  ]
}

restart: true re-attaches after each watch-mode restart. Breakpoints in .ts files work because the generated tsconfig.json sets sourceMap: true, so the debugger maps dist/*.js back to the sources (the SWC builder also emits source maps with the preconfigured settings, and the .swcrc shown above sets sourceMaps). The attach configuration itself is an IDE feature; the CLI’s part is opening the inspector on the expected port. The inspector also works with Chrome DevTools via the chrome://inspect page and with other IDEs. For tests, the ESM template’s test:debug script (vitest --inspect-brk --no-file-parallelism) pauses before the first line so you can attach and step through a test; the CommonJS one runs Jest with --inspect-brk and --runInBand. Source: Node.js: Debugging - Getting started

Beyond a step debugger, the Nest Devtools integration visualises the module graph; it is the subject of Debugging & Devtools.

Pitfalls

  • Wrong Node.js for the job. Running works on 20.19+/22.12+, but nest new and nest g need 22.22.3+, 24.15+ or 26+. An older Node.js fails in the generator, not in your code.

  • Missing .js extension (ESM). import { AppModule } from './app.module' compiles under nodenext only with the extension in an ESM project. Do not "fix" it by switching moduleResolution.

  • Forgetting to build before start:prod. node dist/main runs whatever is in dist/; a stale or missing build means stale or no application. deleteOutDir empties dist/ on every build.

  • Global CLI drift. Two developers with different global nest versions generate different code. Use the package.json scripts for build and start, and nest info in bug reports.

  • nest upgrade with an old global CLI. The command ships with the CLI; upgrade the global binary first.

  • SWC without type checking. Builds pass with type errors unless --type-check or typeCheck: true is set.

  • SWC and circular imports. Use Relation<> or a wrapper type for entity relations.

  • Rspack without its packages. The build fails until @rspack/core, webpack-node-externals and tsconfig-paths-webpack-plugin are installed.

  • Assets outside src or on incremental builds. Only files under src are copied, and not on incremental --watch compiles unless watchAssets is on.

  • generateOptions.spec keys. The key must match the schematic spelling you type (service versus s).

  • Fastify in a container. Without '0.0.0.0' the server is unreachable from outside the container.

  • Placeholder generated code. Generated guards return true and generated resource services return strings; replace them before exposing the endpoint.

  • Port in use. A stale start:dev holds port 3000; set PORT or stop the old process.

  • Reading outdated tutorials. See the next section.

What Changed Recently, and Where the Book Is Outdated

  • v12 (2026-08-27). nest new asks for ESM (Vitest, default) or CommonJS (Jest); both use oxlint instead of ESLint. Rspack is the monorepo default and webpack is deprecated; bun is a supported package manager; nest upgrade and nest deploy are new; the decorator schematic emits Reflector.createDecorator(); the angular schematic was removed; nest build gained --parallel, --emit-declarations, --no-type-check and --silent, and compilerOptions gained includeLibraryAssets and emitDeclarations. Core packages are ESM-only.

  • Docs (Sep 2026). The documentation was restructured; this section only links the new URLs (/cli/, /recipes/, /http/*, …​).

  • TypeScript. The v12 templates pin TypeScript 6; TypeScript 7 is not what nest new generates.

  • Pre-1.0 packages. @nestjs/observe (offered by nest new) and @nestjs/mau (used by nest deploy) are pre-1.0; expect their APIs and the prompts that reference them to move.

The book Scalable Application Development with NestJS (published January 2025) predates all of this: it cannot show the ESM/Vitest default, oxlint, Rspack, nest upgrade or nest deploy, and it contains commands that are not valid, such as nest new app hr (nest new takes one name; generate an extra monorepo app with nest g app <name>) and nest start:dev order (a script name is not a CLI subcommand; use nest start order --watch, or npm run start:dev). Use the commands on this page instead; the bibliography is on the section index.