跳转到内容

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 起步

Section titled “案例 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));

逐部分解释

  1. @InjectableUsersService 登记为可注入工人;@Controller('users') 挂路由前缀
  2. constructor 声明依赖 → Nest 启动时自动塞进单例;@Get() 映射 GET /users
  3. list() 直接 return 数据,框架序列化 JSON——不用 res.json()
  4. @Module 把 controller + provider 打包;NestFactory.create 建依赖图后听 3000

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

Section titled “案例 2:DI 怎么从元数据自动注入”
@Injectable()
class Logger { log(msg: string) { console.log(msg); } }
@Injectable()
class UsersService {
constructor(private logger: Logger) {} // ← TS 写入 design:paramtypes
}

TS 开 emitDecoratorMetadata 后,Nest 启动时:① 读 design:paramtypes[Logger];② resolve(Logger) 拿单例;③ new UsersService(logger)。全程不用手写 wire。

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

逐部分解释:请求按 guard → pipe → handler 走。① @UseGuards 先验 token 头;② ParseIntPipe:id 转成 number;③ handler 只收已校验参数。比 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)——但这通常说明拆分粒度不对,应该重构而不是绕。

适用

  • 企业级后端(金融、电商、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 思想搬到 Node.js,TypeScript-first
  • 2018-2020 v5-7:GraphQL / Microservice / WebSocket 与 Fastify adapter 稳定
  • 2022-2024 v9-10:Standalone application、Node 16+ 起步
  • 2025 v11:原生 ESM、Node 20+,开始迁 stage 3 decorator

8 年长到约 5M weekly downloads,成 Node 后端企业级范本。

  1. decorator + Reflect.metadata 是根:TS emit metadata 才让 DI 自动化成立
  2. module 是依赖图打包单元:imports/providers/controllers/exports 决定可见性
  3. adapter 解耦 HTTP 层——上层可切 Express / Fastify / Kafka / gRPC
  4. 结构有成本:小项目仪式感是负担;收益在多 controller、多 transport 时显现
  • express —— 默认 HTTP adapter,被包了一层 module + DI
  • fastify —— 高性能可选 adapter
  • koa —— 同代极简 async 中间件路线
  • hono —— 边缘 runtime 赛道,NestJS 不覆盖
  • spring-boot —— Java 思想原型,annotation/IoC/AOP 对应
  • fastapi —— Python type hint + Depends 简化版
  • aspnetcore —— .NET 同位框架,DI/middleware/filter 同构
  • affine —— AFFiNE — 文档和白板共用同一棵 block 树的开源知识库
  • 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
  • gin —— Gin — Go 写 web API 的事实标准框架
  • hot-chocolate —— Hot Chocolate — .NET 里 code-first 写 GraphQL 服务器
  • immich —— Immich — 把家庭照片从别人的云里救回自己机器
  • 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 个独立组件再拼起来