NestJS 12 Preview — Ditching class-validator for native Zod support

Maru

@maru

NestJS 12 프리뷰 — class-validator 버리고 Zod 바로 쓴다

NestJS 12 Preview — Ditching class-validator for native Zod support

NestJS 12 Preview is a major update that addresses the legacy dependencies and complex build configurations that have long hampered backend development. By providing core support for Standard Schema 1.0, it allows you to validate request data directly with Zod or Valibot without needing class-validator, and the entire package is shifting to native ESM. In addition, the adoption of high-performance Rust-based toolchains like Rspack and Vitest significantly boosts developer productivity. From a backend developer's perspective, let's break down what problems this major update solves and how to prepare for a smooth migration.

Introducing Standard Schema 1.0: Saying goodbye to class-validator

One of the most significant changes in NestJS 12 is the native integration of the Standard Schema 1.0 specification into the framework core. Now, you can pass schemas like Zod or Valibot directly as arguments into the @Body(), @Query(), and @Param() decorators in route handlers.

Previously, validating payloads in NestJS required dependencies on class-validator and class-transformer. Since this approach used decorators to infer types dynamically, it required enabling the tsconfig.json option in emitDecoratorMetadata. However, this option not only forced expensive operations during TypeScript compilation but also created the biggest hurdle for adopting modern build tools, as it was incompatible with high-speed Rust-based bundlers like Rspack, Vite, and esbuild.

To solve this, NestJS 12 introduces the built-in StandardSchemaValidationPipe. Instead of relying on runtime metadata reflection, it works by directly executing the standard validation method of the schema object injected into the decorator. This enables you to build safe, fast validation pipelines without cumbersome compiler settings or runtime reflection overhead.

Comparing the traditional class-validator approach with the NestJS 12 Zod-based validation reveals a clear difference.

typescript
// [기존 방식] class-validator 사용 (emitDecoratorMetadata 옵션 필수)
import { IsString, IsEmail } from 'class-validator';

export class CreateUserDto {
  @IsString()
  name!: string;

  @IsEmail()
  email!: string;
}

@Post()
create(@Body() createUserDto: CreateUserDto) {}

typescript
// [NestJS 12 방식] Standard Schema 사용 (Zod 스키마 직접 주입)
import { z } from 'zod';

export const CreateUserSchema = z.object({
  name: z.string(),
  email: z.string().email(),
});

@Post()
create(@Body({ schema: CreateUserSchema }) body: z.infer<typeof CreateUserSchema>) {}

By simply passing the schema directly to the controller decorators, StandardSchemaValidationPipe handles payload validation internally, and StandardSchemaSerializerInterceptor handles response serialization when needed. Because you can use standard schema specifications without complex external integration libraries, both developer experience and build speed are significantly improved.

Transition to Native ESM and Synchronous require(esm) Compatibility

All NestJS 12 core packages are moving to native ESM. In the backend ecosystem, configuring module imports between CommonJS and ESM has long been a notorious time-sink for developers. This major update actively utilizes the synchronous require(esm) feature of modern Node.js environments to break down this barrier.

Synchronous require(esm) is a core feature officially supported in Node.js v20.19.0 or higher and v22.12.0 or higher. This allows you to safely import NestJS 12 core packages—built with native ESM—into existing CommonJS-based legacy applications without having to rewrite your entire project configuration immediately.

Looking at community benchmarks, the difference in cold start times or idle memory usage (RSS) between NestJS 11-based CJS environments and NestJS 12 native ESM environments is negligible. Ultimately, the true value of this ESM transition lies not in immediate dramatic performance gains, but in the improved developer experience of being able to integrate modern, ESM-only libraries into your project immediately without tricky configurations or wrappers.

High-Speed Rust Toolchain: Rspack, Vitest, and oxlint

NestJS 12 adopts a suite of modern, Rust-based development tools to drastically shorten the developer feedback loop. This new build and testing system is the default for new native ESM project templates generated via the CLI. To ensure ecosystem stability for existing CommonJS projects, CJS-based templates retain the current Webpack, Jest, and ESLint configurations.

The most noticeable change is the replacement of Webpack, the long-standing default bundler, with Rspack, a high-speed Rust-based bundler. Build performance is several times faster, drastically improving local development server startup and production build times. Additionally, the code analysis tool has shifted from the heavy ESLint to oxlint, which is written in Rust and boasts overwhelming parsing speeds, minimizing wait times for code analysis on save.

There are also major changes in the testing environment. Vitest replaces Jest, which was often heavy and difficult to configure in ESM environments, as the new default test runner. Vitest specifically utilizes the ultra-fast parsing engine OXC to handle NestJS's complex decorators efficiently. Real-world migration cases show that total test execution times, previously 42 seconds, have dropped to just 11 seconds, massively reducing waiting times for developers during local work and CI/CD pipelines.

What to prepare now for a safe migration

To perform a stable NestJS 12 migration, you should align your runtime environment and TypeScript settings in advance.

First, you must update the Node.js version on which your server runs. Even though the core packages are moving to native ESM, maintaining compatibility with existing CommonJS projects requires an environment with Node.js v20.19.0 or v22.12.0 or higher, which officially support the synchronous require(esm) feature.

Next, update your TypeScript compiler settings. In your tsconfig.json file, update the module and moduleResolution options to NodeNext to clearly define ESM module resolution rules.

json
{
  "compilerOptions": {
    "module": "NodeNext",
    "moduleResolution": "NodeNext"
  }
}

NodeNext rules require that all import statements importing local files via relative paths must include the .js extension. Even if your source files are written with .ts extensions, the paths are resolved based on the resulting JavaScript files after build, so it is highly recommended to audit all import statements and add the necessary extensions in advance.

Conclusion: Should you migrate now?

NestJS 12 is a significant update that clears out technical debt that has held the framework back while leading the modernization of its ecosystem. If you are starting a new project or already using Zod or ESM packages extensively, it is well worth the transition, as you can immediately benefit from the new Rust-based toolchain and the convenience of native ESM.

However, there is no need to rush if you are already operating an existing service. According to actual benchmarks, the dramatic runtime performance gains or memory savings from switching to ESM are minimal. Therefore, rather than forcing an upgrade for performance reasons, a wiser migration strategy is to gradually convert class-validator code to standard schemas and verify the ESM support of the libraries you currently use.


Reference Links

No comments yet.