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 |
|
22.22.3+, 24.15+ or 26+ |
The schematics ( |
Run the CLI binary itself ( |
20.11+ |
The |
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
undefinedmeans 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 needsNODE_OPTIONS=--experimental-require-modulein 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
Source: NestJS: First steps - Setup
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 |
|---|---|
|
Day-to-day development, and what the rest of the documentation assumes. You are responsible for keeping it current. |
|
One-off scaffolding without a global install; always the latest published CLI. |
|
Every generated project already has it. The |
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,pnpmorbun(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/observeSDK (the official observability platform; pre-1.0) intoAppModuleandNestFactory.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-observeto 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
Source: NestJS: CLI usage - nest new
Options
| Option | Effect |
|---|---|
|
Destination directory. |
|
Print the files that would be created without writing anything. Use it to see exactly what a command would do. |
|
Do not initialize a git repository. |
|
Do not install packages. |
|
Do not generate test files. |
|
|
|
|
|
Schematics collection to use (the package name of an installed npm package that contains the schematic). |
|
TypeScript |
|
Format the generated files with Prettier. |
|
Set up |
Source: NestJS: CLI usage - nest new
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 | |
|---|---|---|
|
|
no |
Relative imports in |
must carry the |
extensionless: |
|
top-level |
|
Test runner |
Vitest ( |
Jest 30 with |
Test globals in |
|
|
|
default import: |
same generated code ( |
Extra dev dependencies |
none beyond Vitest |
|
|
|
|
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
Source: NestJS: First steps - Setup
| File | Purpose |
|---|---|
|
The entry file. Uses |
|
The root module of the application. |
|
A basic controller with a single route ( |
|
The unit test of the controller. |
|
A basic service with a single method. |
|
An end-to-end test that boots the whole |
|
CLI configuration: schematics collection, |
|
Compiler settings; the build one extends the first and restricts the build to |
|
oxlint and Prettier configuration. |
|
Unit tests match |
Source: NestJS: First steps - Setup
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"]
}
}
Source: NestJS: Migration guide
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();
Source: NestJS: First steps - Setup
(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
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();
Source: NestJS: First steps - Setup
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();
Source: NestJS: Performance (Fastify)
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 |
|
|
|
|
|
Default listen host |
Node.js default |
|
Platform escape hatch |
typed app methods such as |
|
Redirects |
|
|
Express middleware ecosystem |
works |
recipes that rely on Express may not; use the Fastify equivalents |
Source: NestJS: Performance (Fastify)
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"
}
}
Source: NestJS: CLI scripts, NestJS: SWC - Vitest
| Script | Runs | Use it for |
|---|---|---|
|
|
Compile to |
|
|
Compile once and run. No restart on changes. |
|
|
Daily development: watch, recompile, restart (see Hot reload). |
|
|
Watch mode with the Node.js inspector open on port 9229 (see Debugging). |
|
|
Production: runs the already compiled output with plain Node.js. It does not build; run |
|
|
Deploy with Mau (see |
|
oxlint, Prettier |
Static checks and formatting, also usable in CI and Git hooks. |
|
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
Source: NestJS: CLI usage - nest start
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
Source: NestJS: CLI scripts
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 |
|---|---|---|
|
|
Scaffold a new standard mode application. |
|
|
Generate or modify files from a schematic. |
|
Compile an application or workspace into an output folder. |
|
|
Compile and run an application (or the default project of a workspace). |
|
|
Import a library packaged as a Nest library, running its install schematic. |
|
|
|
Upgrade an existing project to the latest NestJS major version. |
|
Deploy to the cloud, powered by Mau. |
|
|
|
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) |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
A complete CRUD resource; see below. |
|
|
A library inside a monorepo (converts a standard project to monorepo mode). |
|
|
A new application inside a monorepo (converts a standard project to monorepo mode). |
|
|
A new application workspace; what |
|
|
Generates a CLI configuration file ( |
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 |
|---|---|
|
Report what would be created or updated, change nothing. Combine it with every example here to preview. |
|
The project the element belongs to (monorepo mode). |
|
|
|
Generate (default) or skip the |
|
Use a custom suffix for spec files. |
|
Do not import/register the generated element in its closest module. |
|
Use another schematics collection (an installed npm package). |
|
Format the generated files with Prettier. |
|
|
|
|
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.
Source: NestJS: CRUD generator
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
}
Source: NestJS: CRUD generator
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>/ |
|---|---|
|
|
|
A resolver ( |
|
Like code-first, plus a |
|
A controller (for message and event patterns) and a service; with |
|
A gateway ( |
Source: NestJS: CRUD generator
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 |
|---|---|
|
Path to the |
|
Path to the |
|
Watch mode (live reload). With |
|
|
|
Force the TypeScript compiler. |
|
Type checking when SWC is the builder. |
|
Emit |
|
Also watch non-TypeScript assets (for example |
|
Path to an Rspack configuration file. |
|
Deprecated legacy flags; use |
|
Build all projects of a monorepo. |
|
With |
|
Suppress informational compiler logs. |
|
Keep old output in |
Source: NestJS: CLI usage - nest build
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 |
|---|---|
|
Run with the Node.js inspector ( |
|
Source root, overriding |
|
Entry file to run, overriding |
|
Binary to run instead of |
|
Do not spawn child processes within a shell ( |
|
Load an environment file into |
|
Arguments for the application, read from |
Source: NestJS: CLI usage - nest start
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
Source: NestJS: CLI usage - nest info
(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.
Source: NestJS: Migration guide
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.nodeto>=20.19.0if lower. -
Moves
nest-cli.jsonoff the deprecatedwebpackoptions to--builder rspack, updating matching scripts. -
Renames the GraphQL
playgroundoption tographiqland switches subscriptions fromsubscriptions-transport-wstographql-ws; replaces the legacynatspackage with@nats-io/transport-node; moves@nestjs/configlibrary-specific settings undervalidationOptions.libraryOptionsand 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.jsonandtsconfig.build.jsonfor 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-installis 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 |
|---|---|---|
|
string or object |
|
|
boolean |
Remove the output directory (the |
|
boolean |
Type checking for SWC projects. Default |
|
boolean |
Emit |
|
array |
Non-TypeScript files to copy into the output on every compilation (see below). Not copied on incremental |
|
boolean |
Watch all non-TypeScript assets. Overrides |
|
boolean |
Enable the |
|
string |
Monorepo only: the |
|
array |
Monorepo only: library projects whose assets are copied with this application. |
|
boolean, string |
Deprecated. Use |
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 }]
}
}
Source: NestJS: CLI workspaces - Assets
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 |
no: a build with a type error succeeds |
Output |
one |
one |
a single bundled |
Extra packages |
|
|
|
Select with |
default, or |
|
|
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
Source: NestJS: SWC - Getting started
{
"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
}
}
Source: NestJS: SWC - Getting started
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
}
Source: NestJS: SWC - SWC configuration
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.
Source: NestJS: SWC - Type checking
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:
-
--watchAssetsorcompilerOptions.watchAssetsalso watches non-TypeScript files (for example.graphqlschemas). -
--preserveWatchOutputkeeps earlier output instead of clearing the screen intscwatch mode. -
Manual restart. With
"manualRestart": trueincompilerOptions, typingrsin the terminal restarts the server (the process restarts afterrsis sent to a runningnest 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
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/...
Source: NestJS: CLI usage - nest start
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 newandnest gneed 22.22.3+, 24.15+ or 26+. An older Node.js fails in the generator, not in your code. -
Missing
.jsextension (ESM).import { AppModule } from './app.module'compiles undernodenextonly with the extension in an ESM project. Do not "fix" it by switchingmoduleResolution. -
Forgetting to build before
start:prod.node dist/mainruns whatever is indist/; a stale or missing build means stale or no application.deleteOutDiremptiesdist/on every build. -
Global CLI drift. Two developers with different global
nestversions generate different code. Use thepackage.jsonscripts for build and start, andnest infoin bug reports. -
nest upgradewith 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-checkortypeCheck: trueis 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-externalsandtsconfig-paths-webpack-pluginare installed. -
Assets outside
srcor on incremental builds. Only files undersrcare copied, and not on incremental--watchcompiles unlesswatchAssetsis on. -
generateOptions.speckeys. The key must match the schematic spelling you type (serviceversuss). -
Fastify in a container. Without
'0.0.0.0'the server is unreachable from outside the container. -
Placeholder generated code. Generated guards return
trueand generated resource services return strings; replace them before exposing the endpoint. -
Port in use. A stale
start:devholds port 3000; setPORTor 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 newasks for ESM (Vitest, default) or CommonJS (Jest); both use oxlint instead of ESLint. Rspack is the monorepo default and webpack is deprecated;bunis a supported package manager;nest upgradeandnest deployare new; the decorator schematic emitsReflector.createDecorator(); theangularschematic was removed;nest buildgained--parallel,--emit-declarations,--no-type-checkand--silent, andcompilerOptionsgainedincludeLibraryAssetsandemitDeclarations. 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 newgenerates. -
Pre-1.0 packages.
@nestjs/observe(offered bynest new) and@nestjs/mau(used bynest 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.