NestJS v12 正式发布:ESM 一等公民、Zod 原生验证、CLI 全面重写,这次改动真的很大
本文基于 NestJS v12 官方 Release Notes 翻译并整理,加入了个人解读和迁移建议。如果你正在考虑升级,这篇文章应该能帮你快速判断值和该注意什么。
一句话总结
NestJS v12 的核心主题是现代化:核心库全面 ESM 化、Standard Schema 验证一等公民支持、原生可观测性、CLI 从零重写。这不是一次"加点小功能"的更新,而是 NestJS 在工具链和生态定位上的一次大跃进。
先上运行环境硬指标:Node.js v20.19+ 或 v22.12+,21.x 系列正式出局。
新特性解读
ESM:终于可以选了,而且不强制
核心库现在以 ESM 格式发布,nest new 会让你选择 ESM 还是 CommonJS。但最关键的一点是------不强制迁移。已有的 CommonJS 项目借助 Node.js 的互操作能力可以继续跑。
这对我们意味着什么? 新项目可以直接上 ESM,不用再搞各种 workaround;老项目不用急,按自己的节奏来。说实话,这个策略很务实,没搞一刀切,降低了升级的心理负担。
Standard Schema 一等公民:告别 class-validator 的时代来了
这可能是 v12 最让人兴奋的特性。路由装饰器(@Body()、@Param()、@Query())现在直接接受一个 schema 参数,支持任何符合 Standard Schema 规范的库------Zod、Valibot、ArkType,随你挑。
typescript
import { Body, Controller, Post } from '@nestjs/common';
import { z } from 'zod';
const CreateUserSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
age: z.number().int().positive(),
});
@Controller('users')
export class UsersController {
@Post()
create(@Body({ schema: CreateUserSchema }) dto: z.infer<typeof CreateUserSchema>) {
return dto;
}
}
不需要 class-validator,不需要 class-transformer,不需要 ValidationPipe 配合 DTO class。一个 Zod schema 搞定验证和类型推断。
要启用这个功能,需要注册 StandardSchemaValidationPipe;响应序列化则用 StandardSchemaSerializerInterceptor。老的 class-based 方式完全保留,两套可以共存。
这对我们意味着什么? 如果你的团队已经在用 Zod(现在前端用 Zod 的比例越来越高),前后端终于可以共用一套 schema 了,不用在 class DTO 和 Zod schema 之间反复翻译。对于新项目,这直接减少了一层抽象。
原生可观测性:@nestjs/observe 来了
NestJS 终于有了官方的可观测性方案。@nestjs/observe 通过 instrumentation 选项接入请求生命周期,自动追踪 HTTP、微服务、消息队列和后台任务,不用手动创建 span。
typescript
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
instrumentation: true,
});
await app.listen(3000);
}
bootstrap();
这对我们意味着什么? 之前搞 OpenTelemetry 接入要么自己封装 middleware,要么用社区的 @autom instrumentation 之类的包,总归有点别扭。现在官方下场了,至少 MVP 阶段的可观测性可以开箱即用。
配置模块:Joi 不再是唯一选择
@nestjs/config 的验证层从 Joi 专属迁移到了 Standard Schema。现在你可以用 Zod 来验证配置文件了。如果继续用 Joi,需要升级到 v18+,并把特定配置项嵌套到库选项下。
对于已经大量使用 Joi 的项目,迁移成本不高但需要注意细节。新项目嘛,直接用 Zod 就完事了。
路由冲突检测
新增 routeConflictPolicy 应用级选项,帮你诊断路由遮蔽问题。这是个 opt-in 功能,不影响现有行为。
这对我们意味着什么? 大项目里路由越来越多,偶尔会碰到"明明注册了但就是命中不了"的诡异问题。这个功能相当于给路由注册加了一双眼睛,建议新项目和排查问题时都打开。
异常错误码
异常现在可以携带 errorCode:
typescript
throw new BadRequestException({
errorCode: 'USER_ALREADY_EXISTS',
message: '该用户名已被注册',
});
这对我们意味着什么? 以前客户端要根据错误做分支处理,只能解析 message 文本,既脆弱又受国际化影响。现在有了稳定的 errorCode,前端/客户端的错误处理可以写得更干净。这个改动不大但很实用。
日志增强
ConsoleLogger 现在会把连续的普通对象归入同一条日志,JSON 模式下自动嵌套为结构化参数。小改进,但对日志采集场景很友好。
CLI 大换血
CLI 被从零重写了------ESM 模块、Vitest 测试、端到端测试、类型化上下文。不只是内部重构,用户体验也有明显变化:
新命令:
nest update:一键自动迁移,处理依赖版本、配置调整、库替换等机械性操作。加--dry-run可以预览而不实际修改nest deploy:对接 Mau 平台的部署命令
工具链大洗牌:
- Rspack 替代 Webpack,成为 monorepo 默认打包器
- oxlint 替代 ESLint,新项目默认使用
- Vitest 替代 Jest,新建 ESM 项目默认测试运行器(底层用 OXC 编译器处理 TypeScript 装饰器元数据)
- Bun 正式加入支持的包管理器列表
其他变化:
- 装饰器生成器改用
Reflector.createDecorator() - 新增构建选项:
--parallel(并行构建)、--declarations(输出声明文件)、--silent(静默模式)
说实话,从 Webpack → Rspack、ESLint → oxlint、Jest → Vitest 这套工具链迁移,能感受到 NestJS 团队在往"更快、更现代"的方向全速推进。对老项目来说,这些工具切换不是强制的,但新项目会直接享受红利。
破坏性变更:升级前必看
生命周期钩子执行顺序变了
onModuleInit、onModuleDestroy 等钩子现在基于组件层级执行,不再是之前的扁平顺序。如果你的模块之间有依赖特定初始化顺序的逻辑(比如数据库连接 → 缓存预热 → 消息队列注册),升级后务必仔细检查。
NATS 传输层换包了
NATS 集成切换到新的包,序列化方式改为 JSON 字符串。如果你在用 NATS,需要更新载荷读取方法。
GraphQL 两项变更
- WebSocket 订阅从
subscriptions-transport-ws切换到graphql-ws,客户端必须同步更新 - 默认 GraphQL IDE 从 Apollo Sandbox 切换到 GraphiQL
Pipe 签名泛型化
ArgumentMetadata 变成了泛型类型:
typescript
// v11
transform(value: any, metadata: ArgumentMetadata) { ... }
// v12
transform(value: any, metadata: ArgumentMetadata<T>) { ... }
如果你写了自定义 Pipe,需要检查 transform 方法签名。
配置验证全面转向 Standard Schema
Joi 专属逻辑被移除,改走 Standard Schema。用 Joi 的需要升级版本并调整配置结构。
废弃与移除
- Webpack 工作流:已废弃,推荐迁移到 Rspack
- Angular 脚手架(Angular schematic):完全移除,不再随 CLI 分发
迁移三步走
第一步:更新全局 CLI
bash
npm i -g @nestjs/cli@latest
第二步:运行自动迁移
bash
nest update
这个命令会处理大部分机械性变更(依赖版本、配置格式、库替换等)。建议先 nest update --dry-run 预览一下再实际执行。
第三步:手动检查清单
自动化工具不覆盖的部分需要你手动过一遍:
- 自定义 bootstrap 代码:模块格式变了,手动写的引导逻辑可能需要调整
- 自定义打包器配置:如果有自定义 Webpack 配置,考虑迁移到 Rspack 或确认兼容
- 生命周期钩子依赖:审查模块间初始化/销毁的先后关系
- NATS 序列化代码:如使用 NATS,适配新的 JSON 字符串格式
- GraphQL WebSocket 客户端 :如使用订阅,确保客户端切到
graphql-ws协议 - 自定义 Pipe 签名 :检查
ArgumentMetadata泛型化是否需要改动
nest update 不会帮你做的事: CJS → ESM 迁移、Jest → Vitest 切换、ESLint → oxlint 替换。这三项需要你根据团队情况自行决策。
其他值得关注的改进
- 自定义验证错误输出形状------终于可以控制验证错误的格式了
- gRPC 状态码映射通过新异常过滤器修正
- Kafka 消息模式支持正则表达式
- WebSocket 网关支持请求作用域提供者
- disconnect 处理器可接收断开原因
- 微服务新增 pre-execution 生命周期钩子
- Express 适配器支持优雅排空(graceful draining)
- HTTP 错误映射在核心适配器层面全面重构
要不要现在升级?
新项目: 直接上 v12,没有历史包袱,ESM + Zod + Vitest 的全套现代工具链开箱即用。
存量项目: 建议先 nest update --dry-run 看看影响面。如果你没用 NATS、没用 GraphQL 订阅、没写自定义 Pipe,那大部分变更自动迁移就能搞定,升级风险不大。如果涉及这几项,建议留够半天到一天的时间来手动检查和测试。
不着急升级的场景: 如果你的项目深度依赖 Webpack 自定义配置、Joi 验证和 Jest 测试,这些都不会在 v12 里坏掉(只是标记废弃),可以按自己的节奏安排迁移计划。
参考链接
- NestJS v12 GitHub Release
- NestJS 官方文档
- NestJS v12 Roadmap - InfoQ
- NestJS v12 is Now Available - Trilon
觉得有帮助的话,点个赞收藏一下?有问题欢迎评论区讨论。