What’s Changed & Migration
|
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; the newest GitHub release page is v12.1.1, 2026-09-28), TypeScript 6 (6.0.x, what the Nest 12 templates install), Node.js 22 and 24 LTS, Express 5 and Fastify 5, and was checked on 2026-10-02. The code examples target the Nest 12 ESM template with Vitest and supertest, and the output blocks are what they produce. The nest upgrade --dry-run report comes from @nestjs/cli 12.0.8 run against a small NestJS 11 project.
NestJS moved quickly between the book’s era (NestJS 10, Node.js 16, TypeScript 4, published January 2025) and today. This page is the one place that collects what is different, with the version and date of each change, the old way next to the new way, the page of this section that teaches it, the official migration guide and the GitHub release notes. Read it once if you are following the book or an older tutorial, then use the tables as a lookup. Where a row says "pre-1.0", the package is still at a 0.x version and its API may change; the pages that teach it show the established alternative.
Release timeline
Dates are those of the GitHub release page, except 12.1.2, which has an npm publication but no GitHub release page yet. The migration guide column links the guide that was current for that major: the archived versions of the documentation keep their own migration-guide page.
| Version | Date | Headline | Migration guide | Release notes |
|---|---|---|---|---|
8.0.0 |
2021-07-07 |
|
||
9.0.0 |
2022-07-08 |
|
||
10.0.0 |
2023-06-15 |
The generation the book targets. |
||
11.0.0 |
2025-01-16 |
Express 5 becomes the default, Node.js 16 and 18 are no longer supported ( |
||
11.1.0 |
2025-04-23 |
RabbitMQ topic exchanges and wildcards, microservice |
||
11.2.0 |
2026-08-14 |
The HTTP |
||
12.0.0 |
2026-08-27 |
ESM packages, Standard Schema, rebuilt CLI ( |
||
12.1.0 |
2026-09-23 |
Built-in cookies, CSRF protection and security headers, Fastify upload interceptors backed by |
||
12.1.1 |
2026-09-28 |
Logger fixes (JSON output, level changes, |
||
12.1.2 |
2026-09-30 (npm) |
Latest packages on npm at the time of writing; no GitHub release page. |
The 11.x line is still maintained (v11.2.6 was published on 2026-09-23), so an application that cannot move to Node.js 20.19 or to ESM packages yet is not stranded.
Node.js and TypeScript requirements
The Node.js floor depends on what you are doing. Running a Nest 12 application and using the CLI’s generators are different questions, because @nestjs/schematics inherits a higher floor from the Angular devkit it builds on.
| What you are doing | Requirement | Notes |
|---|---|---|
Running a Nest 12 application |
Node.js 20.19+, or 22.12+ on the 22.x line |
|
|
Node.js 22.22.3+, 24.15+ or 26+ |
The 23.x and 25.x lines and earlier 22.x and 24.x releases are excluded. |
Jest loading the ESM-only v12 packages |
Node.js 24.9+ |
Older versions fail with |
CommonJS Nest 12 on AWS Lambda |
|
Lambda disables |
Nest 11 (for comparison) |
Node.js 20+ ( |
Node.js 16 and 18 stopped being supported in 11.0.0 (2025-01-16). Nest 10’s CLI accepted Node.js 16.14+. |
TypeScript |
6.0.x |
|
Express and Fastify |
Express 5.2, Fastify 5.12 |
Express 5 has been the default since 11.0.0; see the routing section below. |
The book’s Dockerfiles (node:16-alpine, node:18) and CI workflows (node-version: 14, actions/*@v3) are therefore all past their end of life; the deployment and CI/CD pages show current ones, and getting started and the CLI covers the project layout of a new project.
What changed, by area
Each table lists the version and date in which the change shipped, what older material (the book, tutorials written for Nest 8 to 10) does, what to do now, and where this section teaches it. "Guide" is the migration guide for that major and "notes" is the GitHub release. Dates for packages other than @nestjs/core are the npm publication dates of the first release that carries the change.
Versions, runtime and tooling
| Version | Date | Old way | New way | Covered on | Guide | Notes |
|---|---|---|---|---|---|---|
12.0.0 |
2026-08-27 |
CommonJS packages; |
All core packages ship as ESM. CommonJS applications keep working through |
|||
12.0.0 |
2026-08-27 |
|
|
|||
12.0.0 |
2026-08-27 |
webpack for monorepos and for |
Rspack is the default bundler for monorepos. |
|||
12.0.0 |
2026-08-27 |
npm, yarn and pnpm; the |
|
|||
12.0.0 |
2026-08-27 |
Upgrading by hand with |
|
Getting started and the CLI, Deployment; see |
||
9.0.0 |
2022-07-08 |
Hand-written configuration for dynamic modules. |
|
|||
10.2.0 |
2023-08-21 |
|
|
|||
11.0.0 |
2025-01-16 |
Expected, non-HTTP exceptions are logged as errors by the built-in filters; dates in query strings are parsed by hand. |
Exceptions that extend |
Exception filters, Pipes and validation, ModuleRef and lazy loading |
||
12.0.0 (docs) |
2026-08-27 |
The Devtools package is called |
The package is |
Express 5 routing and HTTP
| Version | Date | Old way | New way | Covered on | Guide | Notes |
|---|---|---|---|---|---|---|
11.0.0 |
2025-01-16 |
|
Express 5 and |
Controllers and routing, Middleware, Fastify and HTTP platforms |
||
12.0.0 |
2026-08-27 |
Routes are matched in declaration order, so |
Two opt-in |
|||
11.2.0 |
2026-08-14 |
Only |
|
|||
11.2.0 |
2026-08-14 |
An |
The SSE handler can receive an |
|||
12.0.0 |
2026-08-27 |
The Express adapter closes the server and may cut in-flight requests; adapter error mapping differs per platform. |
The Express adapter drains in-flight requests on shutdown; error mapping was reworked across core, Express and Fastify. |
|||
12.1.0 |
2026-09-23 |
|
Built-in, adapter-agnostic cookies: the |
|||
12.1.0 |
2026-09-23 |
|
|
Security headers, CORS and CSRF, Rate limiting and encryption |
||
12.1.0 |
2026-09-23 |
Multer interceptors on Express only; Fastify applications bring their own multipart handling. |
Fastify upload interceptors backed by |
|||
12.1.0 (notes only) |
2026-09-23 |
|
The release notes list "array of global prefixes" (PR 17713), but the published 12.1.2 packages still type the prefix as a string. See The global prefix: string, with an array of exclusions. |
Caching, HTTP client and packages that moved
| Version | Date | Old way | New way | Covered on | Guide | Notes |
|---|---|---|---|---|---|---|
10.0.0, 11.0.0 |
2023-06-15, 2025-01-16 |
|
Import them from |
|||
8.0.0, 9.0.0 |
2021-07-07, 2022-07-08 |
|
Deprecated in |
|||
12.0.0 |
2026-08-27 |
|
|
Events, scheduling and queues, Resilience, idempotency and locks |
Validation, serialization and configuration
| Version | Date | Old way | New way | Covered on | Guide | Notes |
|---|---|---|---|---|---|---|
12.0.0 |
2026-08-27 |
|
|
|||
12.0.0 |
2026-08-27 |
|
|
|||
12.0.0 |
2026-08-27 |
|
|
|||
12.0.0 |
2026-08-27 |
Swagger DTOs described only with decorators or the CLI plugin. |
The same Standard Schema schemas feed |
|||
12.0.0 |
2026-08-27 |
Errors identified by their message string; custom |
|
Authentication, authorization and security
| Version | Date | Old way | New way | Covered on | Guide | Notes |
|---|---|---|---|---|---|---|
2026 (pre-1.0) |
Aug to Oct 2026 |
A hand-written JWT guard with bcrypt and |
The official docs teach |
|||
12.1.0 |
2026-09-23 |
|
An authenticated mode ( |
GraphQL
| Version | Date | Old way | New way | Covered on | Guide | Notes |
|---|---|---|---|---|---|---|
10.0.0 to 12.0.0 |
2023 to 2026 |
|
|
GraphQL code-first and schema-first, Subscriptions, security and plugins |
||
12.0.0 |
2026-08-27 |
|
GraphiQL is the default IDE ( |
GraphQL code-first and schema-first, Subscriptions, security and plugins |
||
(graphql-subscriptions 3.0.0) |
2024-11-11 |
|
|
|||
12.0.0 |
2026-08-27 |
Federation described only conceptually. |
|
Microservices, queues and reliability
| Version | Date | Old way | New way | Covered on | Guide | Notes |
|---|---|---|---|---|---|---|
11.0.0, 11.1.0 |
2025-01-16, 2025-04-23 |
Fire-and-forget clients; transport options fixed at module registration. |
Clients and servers expose a |
|||
12.0.0 |
2026-08-27 |
|
NATS v3: |
|||
12.0.0 |
2026-08-27 |
Kafka handlers matched by exact string; gRPC errors surface as |
|
Microservice transports, Microservices architecture and patterns |
||
12.0.0 |
2026-08-27 |
WebSocket gateways are singletons; |
Gateways support request-scoped providers (the socket is injectable through |
|||
2026 (pre-1.0), 12.1.0 |
Aug to Oct 2026 |
A hand-rolled outbox table, idempotency keys, Redlock-style locks and sagas; |
First-party |
Outbox and durable workflows, Resilience, idempotency and locks, CQRS |
||
2026 (pre-1.0) |
Aug to Oct 2026 |
Webhooks hand-written as controllers; i18n, mail and file storage with unrelated third-party modules. |
|
Webhooks, i18n, mail and file storage, Prisma, Drizzle, MikroORM and Sequelize |
Health checks, logging and observability
| Version | Date | Old way | New way | Covered on | Guide | Notes |
|---|---|---|---|---|---|---|
12.0.0 |
2026-08-27 |
Custom indicators extend |
The legacy API (deprecated in |
|||
11.0.0, 12.0.0 |
2025-01-16, 2026-08-27 |
|
|
|||
12.0.0 |
2026-08-27 |
A generic APM agent attached to the HTTP server; OpenTelemetry wired by hand. |
|
Lifecycle, dependency injection and testing
| Version | Date | Old way | New way | Covered on | Guide | Notes |
|---|---|---|---|---|---|---|
11.0.0 |
2025-01-16 |
Termination hooks ( |
Termination hooks run in reverse order; middleware of global modules runs first; module resolution uses object-reference keys, which speeds up bootstrap. |
Lifecycle events and shutdown, Providers and dependency injection |
||
12.0.0 |
2026-08-27 |
Lifecycle hooks of related providers run in an order that depends on registration. |
Hooks are invoked by component hierarchy level; review any ordering assumption between related providers and modules. |
|||
12.0.0, 12.1.0 |
2026-08-27, 2026-09-23 |
A subclass without its own constructor inherits the parent’s |
The guide says the markers are no longer inherited; the 12.1.0 notes list a fix that inherits them again when the constructor is not redeclared. Declare the constructor and repeat |
|||
12.0.0 |
2026-08-27 |
Jest only; |
Vitest is the default for ESM projects (with SWC through |
Deployment and documentation
| Version | Date | Old way | New way | Covered on | Guide | Notes |
|---|---|---|---|---|---|---|
12.0.0 |
2026-08-27 |
Dockerfiles that skip the build step or install production dependencies before building; Google Container Registry; a |
Multi-stage builds that build first, then copy |
|||
(docs restructure) |
2026-09 |
The Techniques chapter, |
Application, Data, HTTP, Security, Observability and Reliability groups with new URLs; see The September 2026 documentation restructure. |
Before-and-after examples
The following examples target @nestjs/core 12.1.2, Express 5.2.1, TypeScript 6.0.3, Zod 4.6.5 and Vitest 4.1.11, with requests sent through supertest.
Standard Schema in the route decorators
@Body({ schema }) and @Param('id', { schema }) only attach the schema; StandardSchemaValidationPipe validates it. Zod is used here, but any Standard Schema library works.
import { Body, Controller, Get, Param, Post } from '@nestjs/common';
import { z } from 'zod';
// Any Standard Schema library works (Zod 4, Valibot, ArkType); Zod is used here.
export const createBookSchema = z.object({
title: z.string().min(1),
isbn: z.string().regex(/^\d{13}$/),
});
export type CreateBook = z.infer<typeof createBookSchema>;
@Controller('books')
export class BooksController {
private readonly books: Array<CreateBook & { id: number }> = [];
@Post()
// The decorator only attaches the schema; StandardSchemaValidationPipe validates it.
create(@Body({ schema: createBookSchema }) body: CreateBook) {
const book = { id: this.books.length + 1, ...body };
this.books.push(book);
return book;
}
@Get(':id')
findOne(@Param('id', { schema: z.coerce.number().int().positive() }) id: number) {
return { id, typeofId: typeof id };
}
}
Register the pipe once, for example in main.ts (app.useGlobalPipes(new StandardSchemaValidationPipe())). A valid body and an invalid one produced:
{"id":1,"title":"Dune","isbn":"9780441013593"}
{"message":["title: Too small: expected string to have >=1 characters","isbn: Invalid string: must match pattern /^\\d{13}$/"],"error":"Bad Request","statusCode":400}
GET /books/42 returned {"id":42,"typeofId":"number"} (the schema coerces the string) and GET /books/-1 returned 400. Source: Migration guide, "Route decorator schemas".
Machine-readable error codes
import { BadRequestException, Controller, Post } from '@nestjs/common';
@Controller('accounts')
export class AccountsController {
@Post('password')
changePassword() {
// errorCode is serialized into the response body next to the message.
throw new BadRequestException('Password is too weak', {
errorCode: 'WEAK_PASSWORD',
});
}
}
{"message":"Password is too weak","error":"Bad Request","statusCode":400,"errorCode":"WEAK_PASSWORD"}
Route conflicts
The controller below declares the parametric route first, as in the documentation’s example:
import { Controller, Get, Param } from '@nestjs/common';
@Controller('members')
export class MembersController {
// Declared first: on Express it is registered first and shadows 'me'.
@Get(':id')
byId(@Param('id') id: string) {
return { handler: 'byId', id };
}
@Get('me')
me() {
return { handler: 'me' };
}
}
With the defaults, GET /members/me returned {"handler":"byId","id":"me"}. With the two opt-in options:
const app = await NestFactory.create(MembersModule, {
routeConflictPolicy: { duplicate: 'error', shadow: 'warn' },
routeResolutionStrategy: 'specificity',
});
routeResolutionStrategy: 'specificity' alone made GET /members/me return {"handler":"me"}. routeConflictPolicy: { shadow: 'warn' } logged the following warning at bootstrap, and { shadow: 'error' } aborted the bootstrap with the same text under a "Conflicting HTTP routes detected" heading:
Route GET /members/me (MembersController#me) is shadowed by GET /members/:id (MembersController#byId). The first-registered route will match all matching requests on order-sensitive adapters.
Combining shadow: 'error' with routeResolutionStrategy: 'specificity' started without error, because the specificity ordering removes the shadowing. Source: Controllers, "Route conflicts and resolution order".
Express 5 wildcards, optional parts and the query parser
import { Controller, Get, MiddlewareConsumer, Module, NestModule, Param, Query } from '@nestjs/common';
import type { Request, Response, NextFunction } from 'express';
@Controller()
export class FilesController {
// Express 4: 'files/*'. Express 5 needs a named wildcard; the value is an array of segments.
@Get('files/*path')
file(@Param('path') path: string[]) {
return { path };
}
// '{*path}' also matches the bare 'docs' (the wildcard is optional).
@Get('docs{/*path}')
docs(@Param('path') path?: string[]) {
return { path: path ?? null };
}
// Express 5 uses the 'simple' query parser: 'a[b]=1' stays a flat key, it is no longer nested.
@Get('search')
search(@Query() query: Record<string, unknown>) {
return query;
}
// Express 4: 'report/:id?'. Express 5 has no '?'; optional parts use braces.
@Get('report/:id{.:ext}')
report(@Param('id') id: string, @Param('ext') ext?: string) {
return { id, ext: ext ?? null };
}
}
export function tag(req: Request, res: Response, next: NextFunction) {
res.setHeader('x-seen', 'yes');
next();
}
@Module({ controllers: [FilesController] })
export class FilesModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
// Express 4 / Nest 10: forRoutes('*'). Nest 11+: a named wildcard.
consumer.apply(tag).forRoutes('{*splat}');
}
}
export function restoreExtendedQueryParser(app: import('@nestjs/platform-express').NestExpressApplication) {
// Express 5 defaults to the 'simple' parser; opt back in to nested objects.
app.set('query parser', 'extended');
}
GET /files/a/b/c.txt {"path":["a","b","c.txt"]}
GET /docs {"path":null}
GET /docs/x/y {"path":["x","y"]}
GET /report/7 {"id":"7","ext":null}
GET /report/7.pdf {"id":"7","ext":"pdf"}
GET /search?filter[genre]=sf&tag=a&tag=b {"filter[genre]":"sf","tag":["a","b"]}
GET /search?filter[genre]=sf (after app.set('query parser', 'extended')) {"filter":{"genre":"sf"}}
The old spellings still run: a controller with @Get('old/') and a module that calls forRoutes('') start, answer 200 and apply the middleware, but Nest logs a warning (Unsupported route path: "/old/*" … Attempting to auto-convert to "/old/{*path}"…). Treat that warning as a to-do item: the converter is a compatibility layer, not a supported spelling. Source: Controllers, "Route wildcards" and Express: Migrating to Express 5.
Structured logging params
const logger = new ConsoleLogger('Users', { json: true });
logger.log('User created', { userId: 1, plan: 'free' });
text: [Nest] ... LOG [Users] User created { userId: 1, plan: 'free' }
json: {"level":"log","pid":...,"message":"User created","context":"Users","params":{"userId":1,"plan":"free"}}
json + flattenParams: {"level":"log","pid":...,"message":"User created","context":"Users","userId":1,"plan":"free"}
structuredParams false: [Nest] ... LOG [Users] User created
[Nest] ... LOG [Users] Object(2) { userId: 1, plan: 'free' }
With structuredParams: false the object is a separate record, as in Nest 11. Source: Logger, "Structured logging params".
Terminus health indicators without HealthCheckError
@Injectable()
export class DogHealthIndicator {
constructor(@Inject(HealthIndicatorService) private readonly indicators: HealthIndicatorService) {}
isHealthy(key: string, badBoys: number) {
const indicator = this.indicators.check(key);
// v12: return down()/up(); throwing HealthCheckError no longer reports "down".
if (badBoys > 0) return indicator.down({ badBoys });
return indicator.up();
}
}
{"status":"ok","info":{"dogs":{"status":"up"}},"error":{},"details":{"dogs":{"status":"up"}}}
{"status":"error","info":{},"error":{"dogs":{"badBoys":2,"status":"down"}},"details":{"dogs":{"badBoys":2,"status":"down"}}}
Cache TTL in milliseconds
import { CACHE_MANAGER, CacheModule } from '@nestjs/cache-manager';
// cache-manager v6 + Keyv: the ttl is in MILLISECONDS (the book-era API used seconds).
await this.cache.set(key, value, 50);
// CacheModule.register({ ttl: 1000 }) is one second.
The value was readable right after set() and gone after 120 ms, so the third argument is milliseconds. Source: Caching, "Time-to-live (TTL)".
The global prefix: string, with an array of exclusions
import type { INestApplication } from '@nestjs/common';
export function configurePrefix(app: INestApplication) {
// exclude has always been an array; the prefix itself is a string in 12.1.2.
app.setGlobalPrefix('api', { exclude: ['health', 'status'] });
}
export function arrayPrefixTypeCheck(app: INestApplication) {
// The v12.1.0 notes list "array of global prefixes"; the published 12.1.2 typings
// still take a string, so this line is a compile-time error.
// @ts-expect-error prefix must be a string in the published packages
app.setGlobalPrefix(['api', 'v1']);
}
The first function behaved as documented (/health unprefixed, /api/members/5 served, /members/5 404). The @ts-expect-error line proves the 12.1.2 signature is setGlobalPrefix(prefix: string, options?: GlobalPrefixOptions): if the array overload had shipped, the directive would itself be reported as unused. Forcing the array through a cast did not fail at bootstrap, but /api/members/5, /v1/members/5 and /api,v1/members/5 all returned 404. The v12.1.0 release notes (PR 17713) and the published 12.1.2 packages therefore disagree; use a single string prefix and exclude (which was always an array), and re-check the typings after upgrading. Source: Global prefix.
@Optional() and inheritance
The migration guide says Nest 12 reads optional markers with Reflect.getOwnMetadata, so a subclass without its own constructor keeps the parent’s parameter types but loses their optional status, and Nest throws UnknownDependenciesException. The 12.1.0 release notes list "inherit optional ctor params when the constructor is not redeclared" (PR 17838). Both were tested with a parent that has an optional parameter, a subclass that does not redeclare the constructor and a subclass that does:
@Injectable()
export class BaseClient {
constructor(@Optional() @Inject('HTTP_OPTIONS') readonly options?: HttpOptions) {}
}
// Does not redeclare the constructor: what happens to the parent's @Optional()?
@Injectable()
export class ImplicitChild extends BaseClient {}
// Recommended: redeclare the constructor and repeat the marker.
@Injectable()
export class ExplicitChild extends BaseClient {
constructor(@Optional() @Inject('HTTP_OPTIONS') options?: HttpOptions) {
super(options);
}
}
HTTP_OPTIONS provider missing, then present)BaseClient, dep missing: resolved options=undefined
ExplicitChild, dep missing: resolved options=undefined
ImplicitChild, dep missing: resolved options=undefined
BaseClient, dep present: resolved options={"timeout":5}
ExplicitChild, dep present: resolved options={"timeout":5}
ImplicitChild, dep present: resolved options={"timeout":5}
A typed parameter without @Inject() (constructor(@Optional() readonly mailer?: Mailer)), compiled with SWC so that decorator metadata is emitted as in a real project, gives the same result: the implicit subclass resolves with mailer undefined. With 12.1.2, then, the behaviour matches the 12.1.0 fix, not the migration guide’s text. The 12.0.x behaviour is the one the guide describes. The portable rule is the one the guide gives: when a subclass takes optional dependencies, declare the constructor and repeat @Optional(). That is correct on every v12 release and does not depend on the fix. Source: Migration guide, "@Optional() is no longer inherited" and release v12.1.0.
nest upgrade
nest upgrade (alias nest update) ships with the CLI, so upgrade the CLI first, and do it globally if you use a global binary, because the command only bumps the project’s local @nestjs/cli dependency:
npm i -g @nestjs/cli@latest @nestjs/schematics@latest
npm i -D @nestjs/cli@latest @nestjs/schematics@latest # if the CLI is a local dev dependency
nest upgrade --dry-run # print the report, change nothing
nest upgrade # apply the changes and run the install
It refuses to run on Node.js releases without require(esm) (older than 20.19, or 22.x older than 22.12). It moves every recognised @nestjs/* package to its v12-compatible major (and reports unrecognised ones), bumps TypeScript to v6, raises engines.node when it declares a lower floor, migrates the webpack options of nest-cli.json and package.json scripts to Rspack, renames the GraphQL playground option to graphiql and swaps the subscription packages, replaces nats with @nats-io/transport-node, moves @nestjs/config library settings under libraryOptions and bumps Joi to v18, bumps Jest to v30 where present, can install and wire @nestjs/observe, checks the tsconfig files for options TypeScript 6 or the ESM packages no longer support, and prints notes about behaviour it cannot migrate (lifecycle hook ordering, pipe signatures, structured logging params). It deliberately does not move your project to ESM, Vitest or oxlint.
| Option | Description |
|---|---|
|
Report the changes that would be made without writing to the filesystem. |
|
Skip the package installation. |
|
Set up |
|
Use an npm dist-tag (for example |
|
The schematics collection to use. |
A dry run against a small NestJS 11 project (CommonJS, nest-cli.json with webpack: true, a GraphQL module with playground: true, TypeScript 5.7 and Jest 29) printed the following report (abridged) and ended with "Dry run enabled. No files written to disk.":
nest upgrade --dry-run --no-observe (abridged)Changes applied:
✔ Updated NestJS packages: @nestjs/common@^12.0.0, @nestjs/core@^12.0.0, ... @nestjs/graphql@^14.0.0, @nestjs/apollo@^14.0.0
✔ Updated "typescript" from "^5.7.3" to "^6.0.0" (required by @nestjs/cli and @nestjs/schematics v12)
✔ Updated "jest" from "^29.7.0" to "^30.0.0"
✔ nest-cli.json: compilerOptions.builder set to {"type":"rspack","options":{"configPath":"webpack.config.js"}}
✔ package.json: updated the "build" script to use Rspack ("nest build --builder rspack")
✔ /src/app.module.ts: renamed "playground: true" to "graphiql: true"
✔ Scheduled "npm install"
Notes:
ℹ @nestjs/config now validates with Standard Schema ...
Please review:
⚠ TypeScript 6 deprecates several legacy "tsconfig.json" options ...
⚠ Jest: the NestJS 12 packages are ESM-only and Jest can only require() ESM modules on Node.js v24.9+ ...
Action required:
➜ Port the webpack configuration file(s) "webpack.config.js" to Rspack ...
➜ tsconfig.json uses "module": "commonjs" with a legacy module resolution, which cannot resolve the ESM-only NestJS 12 packages. ...
UPDATE package.json (629 bytes)
UPDATE nest-cli.json (262 bytes)
UPDATE src/app.module.ts (348 bytes)
The report separates what the command did, what to review and what you must do by hand (here the tsconfig.json module and moduleResolution settings and the webpack configuration file). Its note about @nestjs/config still prints the legacy /techniques/configuration URL, which the documentation redirects to /application/configuration. Running nest upgrade --dry-run in CI on a branch is a cheap way to see what a framework upgrade will touch; the CI/CD page shows where it fits in a pipeline. Source: CLI usages, "nest upgrade" and Migration guide, "Upgrading packages".
Where the book is outdated
The reference book (see the section bibliography) targets NestJS 10, Node.js 16 and TypeScript 4, and was published in January 2025, a few days before NestJS 11. Use it for the concepts and this section for the current API; the tables above give every difference. In short:
-
Runtime and tooling: its Node.js, Docker and CI versions are past their end of life, and it predates
nest upgrade, ESM packages, Vitest, oxlint, Rspack, bun and Mau. -
Express 5 (11.0.0): its
forRoutes('*'), unnamed wildcards and optional?parameters need the named forms, and nested query strings are no longer parsed by default. -
Packages that moved: caching comes from
@nestjs/cache-manager(cache-manager 6, Keyv, TTL in milliseconds), HTTP calls from@nestjs/axiosor@nestjs/http-client, queues from@nestjs/bullmq, health checks from theHealthIndicatorServiceAPI, GraphQL from@nestjs/apollowith adriver, GraphiQL andgraphql-ws. -
Validation, serialization and configuration: Standard Schema is a second option beside class-validator;
@nestjs/configis a first-class dependency, not an afterthought, and credentials come from it, never from source code orsynchronize: truein production. -
Security: Helmet, CSRF and cookies have built-in equivalents since 12.1, rate limiting is
@nestjs/throttler6, and encryption uses an authenticated mode. -
Reliability: the book’s hand-rolled outbox, idempotency, locks and sagas have first-party, pre-1.0 counterparts, and
@nestjs/cqrssagas are in-process, not distributed. -
Code errors: the book contains code that does not run (decorators that do not exist, removed TypeORM APIs, errors returned as HTTP 200, a response interceptor registered twice, GraphQL types without
@ObjectType, onenew PubSub()per resolver, invalid CLI commands). The pages of this section show the correct form where they touch those topics instead of repeating them.
What changed recently
-
NestJS 11.2.0 (2026-08-14): the HTTP
QUERYmethod and the SSE signal, plus fixes for lazy-loaded modules and Fastify middleware on excluded routes. -
NestJS 12.0.0 (2026-08-27): ESM packages, Standard Schema, the rebuilt CLI, native observability, route conflict diagnostics,
errorCode, structured logging, and the breaking changes listed in the migration guide (Node.js floor, lifecycle hook order, NATS v3,graphql-wsonly, GraphiQL,@nestjs/configvalidation, pipe signatures, removed Terminus legacy API, deprecated webpack workflows, removedangularschematic). -
NestJS 12.1.0 (2026-09-23): built-in cookies, CSRF protection and security headers, Fastify upload interceptors, the
ParseDatePipedefault fix, middleware fixes, the optional constructor parameter fix and the array-prefix entry that the published packages do not contain. -
NestJS 12.1.1 (2026-09-28) and 12.1.2 (2026-09-30): logger fixes for JSON output, level changes,
fataland buffered logs, reconnect fixes for the Redis, MQTT, TCP, NATS, Kafka and RabbitMQ clients,ParseArrayPipeitem checking and circular dependency detection across more than two providers. -
Documentation (September 2026): the restructure described below, a new Observability group, a new Reliability group, and the migration guide rewritten for 11 to 12.
-
Ecosystem: TypeORM is on 1.x (1.1.1), Prisma’s
latesttag points at the 8.0.0 release candidate (8.0.0-rc.19), so the pages of this section pinprisma@7(checknpm view prisma dist-tagsbefore choosing a version),@nestjs/graphql14 resolves GraphQL 16, and TypeScript 7.0 is on npm while Nest 12 templates stay on TypeScript 6.
The September 2026 documentation restructure
In September 2026 the official documentation split its Techniques chapter into three groups, Application, Data and HTTP, moved the security topics into a Security group, and added Observability and Reliability groups; the left-hand navigation of docs.nestjs.com now has Introduction, Overview, Fundamentals, Application, Data, Security, HTTP, Observability, Reliability, GraphQL, WebSockets, Microservices, Deployment, CLI, OpenAPI, Recipes, FAQ and Devtools groups. Old links keep working through redirects, but most tutorials and the book still use the old addresses. Every link in this section uses the new addresses, which were checked against the sidebar of the documentation source repository on 2026-10-02 (the site is a single-page application that answers any path with HTTP 200, so the check cannot rely on the status code).
The legacy addresses below are listed as plain text on purpose: they are not links to follow, only addresses to recognise.
| Legacy address (plain text) | New address | Covered on |
|---|---|---|
/techniques/caching |
||
/techniques/compression |
||
/techniques/configuration |
||
/techniques/cookies |
||
/techniques/database and /techniques/sql |
/data/overview (sections land on /data/typeorm and /data/sequelize) |
|
/techniques/events |
||
/techniques/file-upload and /techniques/streaming-files |
||
/techniques/http-module |
||
/techniques/logger |
||
/techniques/mongodb |
||
/techniques/mvc |
||
/techniques/performance |
||
/techniques/queues |
||
/techniques/serialization |
||
/techniques/server-sent-events |
||
/techniques/session |
||
/techniques/task-scheduling |
||
/techniques/validation |
||
/techniques/versioning |
||
/techniques/authentication |
||
/techniques/security |
||
/techniques/hot-reload |
||
/recipes/prisma |
||
/recipes/mikroorm |
||
/recipes/swagger |
||
/fundamentals/dependency-injection |
||
/fundamentals/async-components |
||
/fundamentals/unit-testing and /fundamentals/e2e-testing |
||
/components |
||
/application-context |
Two practical consequences follow. Anchors survive the move for most pages (the redirect keeps the fragment, and the split Database page maps each old section to the page it now lives on), so an old deep link normally still lands in the right place. And the versioned archives (/v8 to /v11 on docs.nestjs.com) keep the old structure and their own migration guides, which is where to look when you maintain an older application.
Checklist for moving a book-era project to NestJS 12
-
Upgrade Node.js to a supported release (20.19+ or 22.12+ to run, 22.22.3+ or 24.15+ to use the generators) and the CLI, then run
nest upgrade --dry-runand read the report. -
Run
nest upgrade, fix thetsconfig.jsonand Rspack items it lists, and run the test suite on Node.js 24.9+ if you stay on Jest. -
Replace unnamed wildcards and
?parameters, check any code that relied on nested query strings, and considerrouteConflictPolicyin a test environment. -
Move cache, HTTP client, queue and health-check code to the packages and APIs in the tables above.
-
Give every subclass with optional dependencies an explicit constructor with
@Optional(). -
Review lifecycle hook ordering, GraphQL subscriptions clients, NATS imports and custom pipe signatures, then adopt Standard Schema, the built-in security features and the new first-party packages on your own schedule.
References
-
NestJS documentation: Migration guide, archived v11 documentation (10 to 11)
-
NestJS documentation: Migration guide, archived v10 documentation
-
NestJS documentation: Migration guide, archived v9 documentation
-
NestJS documentation: Migration guide, archived v8 documentation
-
Packt: Scalable Application Development with NestJS (publisher page)