本文为从 NestJS 10 版本迁移到 11 版本提供了全面的指导。如果你想了解 v11 新增的功能,请参阅这篇文章。虽然此次升级包含了一些小的非兼容变更,但大多数用户不会受到影响。你可以在这里查看完整的变更列表。
虽然你可以手动升级依赖包,但我们推荐使用 npm-check-updates (ncu) 工具来简化升级流程。
经过多年的开发,Express v5 于 2024 年正式发布,并在 2025 年成为稳定版本。从 NestJS 11 开始,Express v5 已成为框架默认集成的版本。对于大多数用户来说,这次升级是无感的,但需要注意 Express v5 引入了一些非兼容变更。详细迁移指导请参考 Express v5 迁移指南。
Express v5 最显著的更新之一是路径路由匹配算法的调整。以下是路径字符串与请求匹配方式的主要变更:
* 现在必须具备名称,行为与参数一致:请使用 /*splat 或 /{{ '{' }}*splat},而不是 /*。其中 splat 只是通配符参数的名称,没有特殊含义。你可以自定义名称,例如 *wildcard?,请改用大括号:/:file{{ '{' }}.:ext}(()[]?+!),如需使用请用 \ 转义。:"this"。因此,之前在 Express v4 可用的路由写法,在 Express v5 可能无法正常工作。例如:
@Get('users/*')
findAll() {
// 在 NestJS 11 中,这将被自动转换为有效的 Express v5 路由。
// 虽然可能仍然可用,但在 Express v5 中已不推荐继续使用该通配符写法。
return '这条路由在 Express v5 中是无效的'
}要修复此问题,可以将路由更新为具名通配符:
@Get('users/*splat')
findAll() {
return '这条路由在 Express v5 中是可用的'
}*splat 是一个具名通配符,匹配除根路径外的所有路径。如果你需要同时匹配根路径(/users),可以使用 /users/{{ '{' }}*splat},即用大括号包裹通配符(可选分组)。再次提醒,splat 只是参数名,没有特殊含义,你可以自定义为任意名称,例如 *wildcard。
同样地,如果你有中间件需要应用于所有路由,也需要将路径更新为具名通配符:
// 在 NestJS 11 中,这将被自动转换为有效的 Express v5 路由。
// 虽然可能仍然可用,但在 Express v5 中已不推荐继续使用该通配符写法。
forRoutes('*') // <-- 这在 Express v5 中将无法正常工作正确做法是将路径更新为具名通配符:
forRoutes('{*splat}') // <-- 这在 Express v5 中可用请注意,{{ '{' }}*splat} 是一个具名通配符,能够匹配包括根路径在内的所有路径。外层大括号表示该路径为可选。
在 Express v5 中,查询参数默认不再使用 qs 库进行解析,而是采用了 simple 解析器。该解析器不支持嵌套对象或数组。
因此,像下面这样的查询字符串:
?filter[where][name]=John&filter[where][age]=30
?item[]=1&item[]=2将不再按预期方式解析。若需恢复之前的行为,可以通过设置 query parser 选项为 extended(Express v4 的默认值),让 Express 使用扩展解析器:
import { NestFactory } from '@nestjs/core'
import { NestExpressApplication } from '@nestjs/platform-express'
import { AppModule } from './app.module'
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule) // <-- 请确保使用 <NestExpressApplication>
app.set('query parser', 'extended') // <-- 添加此行
await app.listen(3000)
}@nestjs/platform-fastify v11 现已正式支持 Fastify v5。对于大多数用户来说,此更新过程应当十分顺利;但需要注意,Fastify v5 引入了一些不兼容变更,不过这些变更对大多数 NestJS 用户影响不大。更多详细信息,请参阅 Fastify v5 迁移指南。
Fastify v5
的路径匹配方式没有变化(中间件除外,详见下文),因此你可以像以前一样继续使用通配符语法。行为保持一致,使用通配符(如
*)定义的路由依然可以正常工作。
默认情况下,仅允许 CORS 安全列表方法。如果你需要启用额外的方法(如 PUT、PATCH 或 DELETE),必须在 methods 选项中显式声明。
const methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'] // 或使用逗号分隔的字符串 'GET,POST,PUT,PATH,DELETE'
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter(),
{
cors: { methods },
}
)
// 或者,你也可以使用 `enableCors` 方法
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter()
)
app.enableCors({ methods })NestJS 11 现已在 @nestjs/platform-fastify 中使用最新版的 path-to-regexp 包来匹配中间件路径。因此,原先用于匹配所有路径的 (.*) 语法已不再支持,需改用命名通配符。
例如,如果你有一个应用于所有路由的中间件:
// 在 NestJS 11 中,即使你没有更新,这段代码也会被自动转换为有效路由。
.forRoutes('(.*)')你需要将其更新为使用命名通配符:
.forRoutes('*splat')其中 splat 只是通配符参数的任意名称,你可以根据需要自定义。
从 NestJS 11 开始,模块解析算法得到了优化,大多数应用的性能和内存占用都将提升。此更改通常无需手动干预,但在某些边缘场景下,行为可能与旧版本不同。
在 NestJS v10 及更早版本中,动态模块会根据其动态元数据生成唯一的不透明 key,并用该 key 在模块注册表中标识模块。例如,如果你在多个模块中都引入了 TypeOrmModule.forFeature([User]),NestJS 会对这些模块去重,并将其视为注册表中的同一个模块节点。这个过程称为节点去重。
在 NestJS 11 发布后,我们不再为动态模块生成可预测的哈希值,而是通过对象引用来判断模块是否等价。要在多个模块间共享同一个动态模块,只需将其赋值给一个变量,并在需要的地方导入即可。这种新方式更灵活,也能更高效地处理动态模块。
这种新算法可能会影响你在集成测试中大量使用动态模块的场景。因为没有了上述手动去重,你的测试模块(TestingModule)中可能会出现同一个依赖的多个实例。这样在模拟(stub)方法时,需要定位到正确的实例。你可以选择:
module.select(ParentModule).get(Target) 查找正确的实例module.get(Target, { each: true }) 模拟所有实例Test.createTestingModule({}, { moduleIdGeneratorAlgorithm: 'deep-hash' })NestJS 11 对 Reflector 类进行了多项改进,增强了其功能和元数据值的类型推断。这些更新让开发者在处理元数据时更加直观和健壮。
value 类型为对象时,getAllAndMerge 现在会返回对象,而不是只包含一个元素的数组。这样在处理基于对象的元数据时更加一致。getAllAndOverride 的返回类型已从 T 更新为 T | undefined,更准确地反映了可能找不到元数据的情况,便于正确处理 undefined。ReflectableDecorator 的类型参数现已在所有方法中得到正确推断。这些增强提升了开发体验,为 NestJS 11 中的元数据处理带来了更好的类型安全性和易用性。
终止类生命周期钩子(如 OnModuleDestroy、BeforeApplicationShutdown 和 OnApplicationShutdown)现在会按照其初始化钩子的相反顺序执行。
假设有如下场景:
// 其中 A、B、C 为模块(Module),"->" 表示模块依赖关系。
A -> B -> C在这种情况下,OnModuleInit 钩子的执行顺序如下:
C -> B -> A而 OnModuleDestroy 钩子的执行顺序则与之相反:
A -> B -> C全局模块(Global Module)会被视为依赖所有其他模块。这意味着全局模块会最先初始化,并在最后销毁。
在 NestJS v11 中,中间件注册顺序的行为已更新。此前,中间件的注册顺序由模块依赖图的拓扑排序决定,即距离根模块的远近决定了中间件的注册顺序,无论中间件是在全局模块还是普通模块中注册。全局模块在这方面与普通模块无异,这导致了与其他框架特性相比行为不一致。
从 v11 开始,在全局模块中注册的中间件总是最先执行,无论其在模块依赖图中的位置如何。此更改确保了全局中间件始终在任何导入模块的中间件之前运行,从而保持了顺序的一致性和可预测性。
CacheModule(缓存模块,来自 @nestjs/cache-manager 包)现已升级,支持最新版的 cache-manager(Node.js 缓存库)。本次升级带来了一些重大变更,包括迁移至 Keyv,Keyv 通过存储适配器为多种后端存储提供统一的键值存储接口。
新旧版本的主要区别在于外部存储的配置方式。旧版本中,如果你需要注册 Redis 存储,通常会这样配置:
// 旧版本 - 已不再支持
CacheModule.registerAsync({
useFactory: async () => {
const store = await redisStore({
socket: {
host: 'localhost',
port: 6379,
},
})
return {
store,
}
},
}),在新版本中,你应当使用 Keyv 适配器来配置存储:
// 新版本 - 支持
CacheModule.registerAsync({
useFactory: async () => {
return {
stores: [
new KeyvRedis('redis://localhost:6379'),
],
}
},
}),其中,KeyvRedis 需从 @keyv/redis 包中引入。更多内容请参阅缓存(Caching)文档。
本次升级后,Keyv 库处理的缓存数据结构变为包含 value 和 expires 字段的对象,例如:{{ '{' }}"value": "yourData", "expires": 1678901234567{{ '}' }}。通过 Keyv API 访问数据时会自动获取 value 字段,但如果你直接操作缓存数据(如绕过 cache-manager API),或需要兼容旧版 @nestjs/cache-manager 写入的数据,请注意此变更。
如果你正在使用 @nestjs/config 包中的 ConfigModule,请注意 @nestjs/config@4.0.0 引入了若干重大变更。最重要的是,ConfigService#get 读取配置变量的顺序已更新。新顺序如下:
process.env(Node.js 环境变量对象)此前,已验证的环境变量和 process.env 会被优先读取,导致它们无法被内部配置覆盖。现在,内部配置将始终优先生效。
此外,ignoreEnvVars 配置项(此前用于禁用对 process.env 的验证)已被弃用。请改用 validatePredefined 选项(设置为 false 可禁用对预定义环境变量的验证)。预定义环境变量指在导入模块前已设置的 process.env 变量。例如,使用 PORT=3000 node main.js 启动应用时,PORT 变量即为预定义环境变量。而通过 ConfigModule 从 .env 文件加载的变量不属于预定义环境变量。
新增的 skipProcessEnv 选项允许你完全阻止 ConfigService#get 方法访问 process.env,当你希望限制服务直接读取环境变量时,这一选项非常有用。
如果你正在使用 TerminusModule 并且实现了自定义健康指示器(custom health indicator),那么在 11 版本中引入了一个新的 API。新的 HealthIndicatorService 旨在提升自定义健康指示器的可读性和可测试性。
在 11 版本之前,健康指示器的实现方式如下:
@Injectable()
export class DogHealthIndicator extends HealthIndicator {
constructor(private readonly httpService: HttpService) {
super()
}
async isHealthy(key: string) {
try {
const badboys = await this.getBadboys()
const isHealthy = badboys.length === 0
const result = this.getStatus(key, isHealthy, {
badboys: badboys.length,
})
if (!isHealthy) {
throw new HealthCheckError('Dog check failed', result)
}
return result
} catch (error) {
const result = this.getStatus(key, isHealthy)
throw new HealthCheckError('Dog check failed', result)
}
}
private getBadboys() {
return firstValueFrom(
this.httpService.get<Dog[]>('https://example.com/dog').pipe(
map((response) => response.data),
map((dogs) => dogs.filter((dog) => dog.state === DogState.BAD_BOY))
)
)
}
}从 11 版本开始,推荐使用新的 HealthIndicatorService API,这将简化健康指示器的实现流程。下面是相同健康指示器的新版实现方式:
@Injectable()
export class DogHealthIndicator {
constructor(
private readonly httpService: HttpService,
// 注入由 TerminusModule 提供的 HealthIndicatorService(健康指示器服务)
private readonly healthIndicatorService: HealthIndicatorService
) {}
async isHealthy(key: string) {
// 为指定 key 启动健康检查
const indicator = this.healthIndicatorService.check(key)
try {
const badboys = await this.getBadboys()
const isHealthy = badboys.length === 0
if (!isHealthy) {
// 标记健康指示器为 "down",并在响应中添加额外信息
return indicator.down({ badboys: badboys.length })
}
// 标记健康指示器为 "up"
return indicator.up()
} catch (error) {
return indicator.down('无法获取狗狗信息')
}
}
private getBadboys() {
// ...
}
}主要变更点:
HealthIndicatorService 取代了旧的 HealthIndicator 和 HealthCheckError 类,提供了更简洁的健康检查 API。check 方法便于跟踪健康状态(up 或 down),同时支持在健康检查响应中添加额外的元数据。请注意,HealthIndicator 和 HealthCheckError
类已被标记为弃用,并计划在下一个主要版本中移除。
从 NestJS 11 开始,Node.js v16 已不再受支持,因为该版本已于 2023 年 9 月 11 日正式结束生命周期(EOL)。同样,Node.js v18 的安全支持计划于 2025 年 4 月 30 日结束,因此我们也提前停止了对该版本的支持。
NestJS 11 现在要求 Node.js v20 或更高版本。
为了获得最佳体验,我们强烈建议你使用最新的 Node.js LTS 版本。
如果你错过了相关公告,我们已于 2024 年推出了官方部署平台 Mau。 Mau 是一个全托管平台,可简化 NestJS 应用的部署流程。借助 Mau,你可以通过一条命令将应用部署到云端(AWS;Amazon Web Services),管理环境变量,并实时监控应用性能。
Mau 让基础设施的配置和维护变得像点击几下按钮一样简单。Mau 的设计理念是简单直观,让你专注于构建应用,而无需担心底层基础设施。平台底层采用 AWS 为你提供强大且可靠的支撑,同时屏蔽了 AWS 的复杂性。我们为你处理所有繁琐的运维工作,让你专注于开发和业务增长。
$ npm install -g @nestjs/mau
$ mau deploy你可以在此章节了解更多关于 Mau 的信息。