定位
NestJS 把 Angular 那套搬到后端:模块、控制器、服务、依赖注入、装饰器。
它解决的是「大型项目怎么组织」,不是「怎么跑得快」。 团队超过 5 个人、项目要活几年,这套约定能省下大量「这段逻辑该放哪」的讨论。
安装
npm i -g @nestjs/cli
nest new my-app
cd my-app
npm run start:dev
最小示例
1. 模块
// users/users.module.ts
import { Module } from '@nestjs/common'
import { UsersController } from './users.controller'
import { UsersService } from './users.service'
@Module({
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService], // 给别的模块用
})
export class UsersModule {}
2. 控制器:只做协议转换
// users/users.controller.ts
import { Controller, Get, Post, Param, Body, ParseIntPipe } from '@nestjs/common'
import { UsersService } from './users.service'
import { CreateUserDto } from './dto/create-user.dto'
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get()
findAll() {
return this.usersService.findAll()
}
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.usersService.findOne(id)
}
@Post()
create(@Body() dto: CreateUserDto) { // 自动校验(配合 ValidationPipe)
return this.usersService.create(dto)
}
}
3. 服务:业务逻辑
// users/users.service.ts
import { Injectable, NotFoundException } from '@nestjs/common'
@Injectable()
export class UsersService {
private users = []
findAll() { return this.users }
findOne(id: number) {
const u = this.users.find(x => x.id === id)
if (!u) throw new NotFoundException(`user ${id} not found`)
return u
}
create(dto) { /* ... */ }
}
4. DTO 与自动校验
// users/dto/create-user.dto.ts
import { IsString, IsEmail, MinLength } from 'class-validator'
export class CreateUserDto {
@IsString() @MinLength(2)
name: string
@IsEmail()
email: string
}
在 main.ts 开启全局管道:
app.useGlobalPipes(new ValidationPipe({ whitelist: true }))
请求不合法会自动返回 400 和详细错误,不用写一行校验代码。
核心概念
1. 依赖注入
Nest 有完整的 DI 容器。@Injectable() 标记的类可以被注入,@Module 的 providers 声明作用域。
constructor(
private readonly usersService: UsersService,
private readonly config: ConfigService,
) {}
测试时可以替换:
const module = await Test.createTestingModule({
controllers: [UsersController],
providers: [{ provide: UsersService, useValue: mockService }],
}).compile()
2. 模块作用域
exports 声明哪些 provider 对外可见。不 export 的,别的模块拿不到——这是 Nest 强制边界的方式。
3. 守卫 / 拦截器 / 管道 / 过滤器
| 概念 | 作用 | 类比 |
|---|---|---|
| Guard | 鉴权,决定请求能否进入 | 门卫 |
| Interceptor | 请求前后加工(日志、缓存、超时) | 中间件 |
| Pipe | 参数转换与校验 | 校验器 |
| Filter | 异常处理 | 错误中间件 |
常见坑
① 一个接口要写三个文件
controller + service + module。小项目会觉得繁琐——这正是 Nest 不适合小项目的原因。
② 循环依赖
两个模块互相 import 会报错。用 forwardRef() 解决,但更该做的是重新划分模块边界。
③ 默认用 Express,可以换 Fastify
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter(),
)
换 Fastify 能提升吞吐,但部分 Express 中间件不能用。
④ 装饰器需要 TS 配置
tsconfig.json 里 experimentalDecorators 和 emitDecoratorMetadata 必须开,改错了 DI 会静默失效。
什么时候选它
- 团队 ≥ 5 人,需要统一架构
- 项目要长期维护(3 年以上)
- 需要清晰的模块边界和可测试性
- 团队有 Angular 经验(心智模型一致)
不选它的场景:小服务、脚本、API 网关、单人项目——这时候约定带来的收益抵不过学习成本。
一个判断标准
问自己:「这个项目会有别人接手吗?」
- 会 → NestJS 的结构让接手成本低很多
- 不会 → 用 Fastify 更轻快
另一个标准:「这个项目会活过两年吗?」 短期项目用 Nest 是给自己找麻烦。