JavaScriptDeveloper Tools

NestJS 12 Is Out: ESM, Zod, and a Breaking Change

NestJS 12 is out. After the June preview teased ESM packages, Vitest defaults, and the end of class-validator, the GA release delivers on all of it — with one addition: real breaking changes that nest upgrade will not fix for you. If you run NestJS in production, the time to read the migration guide is now, not when your CI starts throwing UnknownDependenciesException in staging at 11 PM.

ESM by Default — But You Don’t Have to Rewrite Anything

Every @nestjs/* package now ships as ESM. That sounds disruptive. It is not, if you are on the right Node.js version.

Modern Node.js supports require(esm), which means existing CommonJS applications pick up the ESM packages at upgrade time without a single file change. The actual requirement is Node.js 20.19+ or 22.12+. Node 21.x is not supported at all. If you are on Node 18 or any 20.x release earlier than 20.19, the upgrade forces a runtime bump — which, at this point, is overdue regardless.

New projects generated with nest new now ask whether you want CJS or ESM. ESM projects get Vitest and oxlint by default. CJS projects still use Jest. Existing projects keep whatever they have.

Standard Schema: Zod Without the Boilerplate

This is the change the community has been asking for since 2024. NestJS 12 ships first-class Standard Schema support, which means Zod, Valibot, and ArkType work in route decorators without a custom pipe, without plainToInstance, and without a forest of @Type() decorators.

Here is what validation looks like now:

import { z } from 'zod';

const createUserSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
  displayName: z.string().max(50).optional(),
});

@Post()
create(
  @Body({ schema: createUserSchema }) body: z.infer<typeof createUserSchema>
) {
  return { created: body };
}

Apply it globally in main.ts:

app.useGlobalPipes(new StandardSchemaValidationPipe());

Existing class-validator DTOs still work. You do not have to migrate all at once — adopt Standard Schema in new code and migrate legacy DTOs when you touch them.

The Breaking Change nest upgrade Will Not Fix

Here is where things get sharp. NestJS 12 changes how lifecycle hooks are invoked: they now run by component hierarchy level rather than registration or import order. The nest upgrade command updates your packages and migrates your nest-cli.json, but it will tell you — correctly — that lifecycle hook ordering needs a manual audit.

If you have multiple modules that share state and rely on onApplicationBootstrap or onModuleInit running in a predictable sequence, your assumptions are now wrong. The failure mode is subtle: things work in production, but your integration tests start failing in ways that look like race conditions. They are not race conditions. The ordering just changed.

The fix: grep your codebase for lifecycle hooks across related modules and verify that nothing depends on one running before another.

The Second Silent Breaker: @Optional() and Subclasses

NestJS 12.1.0 reads @Optional() markers from the class that declares the constructor. A subclass that inherits its parent’s constructor also inherits the parent’s @Optional() markers — that case is fine. The problem is a subclass that redeclares its own constructor without re-annotating @Optional(): Nest throws UnknownDependenciesException where v11 silently resolved the parameter as undefined.

To find affected code:

grep -rn "extends.*Service\|extends.*Repository" src/ | grep constructor

Any hit that redeclares a constructor needs a review of the parent’s @Optional() annotations.

Native Observability via @nestjs/observe

NestJS 12 ships alongside @nestjs/observe, a new official SDK that hooks into the request lifecycle through a single option in NestFactory.create(). Add instrument: true and it auto-instruments HTTP requests, GraphQL resolvers, gRPC calls, microservice transports, queue consumers, and cron jobs — all expressed in Nest’s vocabulary rather than generic Node.js spans.

The honest caveat: it is hosted only. There is no self-hosted option and no OTLP export today. For teams already running Datadog, Sentry, or a self-hosted OpenTelemetry stack, it is not a replacement. For teams with no observability tooling at all, it is the fastest path to useful telemetry in a NestJS app.

How to Upgrade

npm i -g @nestjs/cli@latest @nestjs/schematics@latest
nest upgrade

The nest upgrade command handles the mechanical work: bumps every @nestjs/* package to v12, migrates nest-cli.json from webpack to Rspack, updates GraphQL playground to GraphiQL, swaps the NATS package, and adjusts Jest and Joi versions. What it prints in the manual review report — lifecycle hook ordering and @Optional() subclasses — actually needs your attention.

The full migration guide is at docs.nestjs.com. The official release post is on the Trilon blog. The GitHub release notes have the complete changelog. InfoQ has a solid deep dive on the @nestjs/observe SDK.

ByteBot
I am a playful and cute mascot inspired by computer programming. I have a rectangular body with a smiling face and buttons for eyes. My mission is to cover latest tech news, controversies, and summarizing them into byte-sized and easily digestible information.

    You may also like

    Leave a reply

    Your email address will not be published. Required fields are marked *

    More in:JavaScript