跳转到内容

NestJS — 把 Angular 思想搬到 Node.js 后端的企业级框架

是什么

NestJS 是一个让你用装饰器和模块组织 Node.js 后端代码的框架。日常类比:像建一座工厂——@Module 是车间,@Injectable 是在册工人,@Controller 是对外窗口,DI 容器是调度系统,开班前自动把工人分配到每个窗口。

你写:

@Injectable()
class UsersService { findAll() { return [{ id: 1 }]; } }
@Controller('users')
class UsersController {
constructor(private users: UsersService) {} // ← 自动注入
@Get() list() { return this.users.findAll(); }
}
@Module({ controllers: [UsersController], providers: [UsersService] })
class AppModule {}

NestJS 启动时读所有装饰器、构建依赖图、把单例工人塞进每个 controller 的 constructor,再把路由挂到 HTTP 层。HTTP 层不是它自己做的——底下是 Express(默认)或 Fastify(可切),NestJS 只解决”代码怎么组织”。

为什么重要

不理解 NestJS 这套,下面这些事都没法解释:

  • 为什么 Node.js 也能写出像 Spring Boot 一样的企业级代码(5M weekly downloads,Adidas / Roche / Decathlon 在用)
  • 为什么 Angular 团队转后端零摩擦——@NgModule@Module 几乎是同一份设计
  • 为什么”换一行代码”就能从 Express 切到 Fastify 或加上 gRPC / Kafka 传输层
  • 为什么”小项目用 NestJS 就是过度工程”是真的——结构化收益要 100+ controller 才显现

核心要点

NestJS 的运转可以拆成 三块

  1. 装饰器 + Reflect.metadata@Injectable / @Module / @Controller 不做任何运行时动作,只是把”我是 service”、“我需要这些依赖”写进 metadata。类比:给每个零件贴标签,标签上写身份和搭档。

  2. module 是依赖图的打包单元imports / providers / controllers / exports 四元组决定可见性——只有显式 export 的 provider 才能被别的 module 注入。类比:车间之间不能随便借工人,必须挂在”对外业务”清单上。

  3. DI 容器在启动时把图建出来NestFactory.create 递归扫描 module 树,按拓扑序实例化 provider(依赖谁谁先 new),最后把路由挂到 HttpAdapter。类比:调度系统先排好”哪个工人先到岗”,再开门接客。

三块加起来构成 NestJS 的核心抽象——经验从 Angular 迁移,运行时跑在 Node.js

实践案例

案例 1:最小 controller + service 起步

import { Module, Controller, Get, Injectable } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
@Injectable()
class UsersService { findAll() { return [{ id: 1, name: 'Jason' }]; } }
@Controller('users')
class UsersController {
constructor(private users: UsersService) {}
@Get() list() { return this.users.findAll(); }
}
@Module({ controllers: [UsersController], providers: [UsersService] })
class AppModule {}
NestFactory.create(AppModule).then(app => app.listen(3000));

注意 controller 里直接 return 业务数据,框架自动序列化 JSON——不用 res.json()

案例 2:DI 怎么从元数据自动注入

@Injectable()
class Logger { log(msg: string) { console.log(msg); } }
@Injectable()
class UsersService {
constructor(private logger: Logger) {} // ← TS 编译写入 design:paramtypes
}

TS 开启 emitDecoratorMetadata 后,编译器把 Logger 类型写进 metadata。NestJS 启动时:

  1. Reflect.getMetadata('design:paramtypes', UsersService) 拿到 [Logger]
  2. 递归 resolve(Logger) 拿单例
  3. new UsersService(loggerInstance) 完成注入

整个过程没让你写一行手动 wire 代码。这是 NestJS 一切的根。

案例 3:guard + interceptor + pipe 搭管线

@Injectable()
class JwtAuthGuard implements CanActivate {
canActivate(ctx: ExecutionContext): boolean {
const req = ctx.switchToHttp().getRequest();
return !!verifyToken(req.headers.authorization);
}
}
@Controller('users')
@UseGuards(JwtAuthGuard)
class UsersController {
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) { return { id }; }
}

请求进来按 middleware → guard → interceptor → pipe → handler → interceptor → filter 走管线。每一阶段都有专门的接口(CanActivate / NestInterceptor / PipeTransform / ExceptionFilter),各司其职——比 Express 的”线性中间件链”类型化更强。

踩过的坑

  1. decorator 在 TS 5+ 是 baggage:v10 仍依赖老 experimentalDecorators + emitDecoratorMetadata,和 TS 5 stage 3 标准不兼容。升级 TS 时按官方推荐关掉 experimental,整个项目就崩——v11 才开始迁移。

  2. REQUEST scope 是性能毒药:某 provider 标 Scope.REQUEST 后,所有依赖它的 provider 都变 request-scoped,每个 HTTP 请求要实例化整棵子树。新人为”日志带 trace id”加 REQUEST 容易让 TPS 砍半。

  3. Express → Fastify adapter 切换不无缝:官方话术”换一行”,真实迁移要 1-2 周——cookie-parser 不兼容、multipart 上传 API 不同、req.app / res.locals 这种逃逸到底层的代码会崩。

  4. forwardRef 滥用是 code smell:循环依赖时 NestJS 抛 “can’t resolve dependencies”,给的逃生通道是 forwardRef(() => OtherService)——但这通常说明拆分粒度不对,应该重构而不是绕。

适用 vs 不适用场景

适用

  • 企业级后端(金融、电商、SaaS):100+ controller、团队 5+ 人、需要”代码风格统一”
  • 从 Java/Spring 迁移团队:思想 1:1 对应,迁移成本最低
  • 复杂业务 + 多 transport(HTTP + WebSocket + Kafka + gRPC 混合):一个框架统一
  • GraphQL + REST 混合后端:@nestjs/graphql 集成度高于其他框架

不适用

  • 小项目 / MVP / 单文件 API:用 Express / Hono,module 仪式感是负担
  • 极致性能场景(高频交易、超高 QPS):用裸 Fastify,DI 开销不必要
  • 边缘部署(CF Worker / Bun edge):NestJS 强依赖 Node 启动模型,扫描 + 实例化整棵树和短生命周期 stateless 模型不适配
  • 团队人均经验少:NestJS 的强结构对新人是双刃剑——能写好的是工程师,写烂了的是垃圾

历史小故事(可跳过)

  • 2017-05 v1:Kamil Mysliwiec 发布,Angular 2 思想搬到 Node.js,TypeScript-first
  • 2018-2020 v5-7:稳定 GraphQL/Microservice/WebSocket 三传输层;Fastify adapter stable
  • 2022-2024 v9-10:Standalone application、custom decorator 简化、Node 16+ 起步
  • 2025 v11:原生 ESM,Node 20+,开始迁移到 stage 3 decorator

8 年从 0 长到 5M weekly downloads,成为 Node 后端的”企业级范本”。

学到什么

  1. decorator + Reflect.metadata 是 NestJS 一切的根:TS 编译 emit metadata 才让 DI 自动化成立
  2. module 是依赖图打包单元:四元组(imports/providers/controllers/exports)决定可见性
  3. adapter 模式解耦 HTTP 层——同一套上层代码可切 Express/Fastify/Kafka/gRPC

延伸阅读

  • 官方文档:docs.nestjs.com(中文社区翻译质量也高)
  • 视频:Marius Espejo — NestJS Crash Course(一小时跑通 controller / module / DI)
  • 源码精读:packages/core/scanner.ts + instance-loader.ts 看 DI 实例化的拓扑序算法
  • 对比阅读:fastapi 的 Depends 比 NestJS DI 简化一半,看抽象数量怎么影响学习曲线

关联

  • express —— NestJS 的默认 HTTP adapter,被 NestJS 包了一层 module + DI
  • fastify —— NestJS 的高性能可选 adapter,schema 校验思路和 NestJS class-validator 互补
  • koa —— async/await 中间件思想原型,和 NestJS 同代但走极简路线
  • hono —— 边缘 runtime first 的对手,NestJS 不覆盖的赛道
  • spring-boot —— Java 世界的 NestJS”思想原型”,annotation/IoC/AOP 一一对应
  • fastapi —— Python 同思路(type hint + Depends + Pydantic),DI 简化版
  • aspnetcore —— .NET 世界的同位框架,DI/middleware/filter 三件套思路相同

反向链接

  • affine —— AFFiNE — 文档和白板共用同一棵 block 树的开源知识库
  • aspnetcore —— ASP.NET Core — 微软跨平台 web 框架
  • axum —— axum — 用 Rust 类型系统当『路由参数表』的 Web 框架
  • bullmq —— BullMQ — Node.js 上的 Redis 任务队列
  • commander —— commander.js — Node.js CLI 解析的声明式标准
  • echo —— Echo — 极简高性能 Go 框架,5 行起服务
  • elysia —— Elysia — 长在 Bun 上的极致类型安全 Web 框架
  • express —— Express — Node.js 最经典的 Web 框架
  • fastapi —— FastAPI — 用 Python 类型注解写 API
  • fastify —— Fastify — 让 schema 替你写校验和序列化的 Node.js 框架
  • gin —— Gin — Go 写 web API 的事实标准框架
  • hono —— Hono — 多运行时 Web 框架
  • hot-chocolate —— Hot Chocolate — .NET 里 code-first 写 GraphQL 服务器
  • immich —— Immich — 把家庭照片从别人的云里救回自己机器
  • koa —— Koa — async/await + ctx 对象 + 洋葱模型 的极简 Node.js web 框架
  • litestar —— Litestar — 类型驱动的 ASGI 框架(原 Starlite)
  • micronaut —— Micronaut — 编译期搞定 DI 的 JVM 云原生框架
  • socket-io —— Socket.IO — 让浏览器和 Node.js 像打电话一样互相喊事件
  • spring-boot —— Spring Boot — 用 Auto-configuration 把 Java 后端从 XML 地狱里救出来的事实标准框架
  • symfony —— Symfony — 把 PHP 框架拆成 30 个独立组件再拼起来