NestJS 是一个高度结构化的 Node.js 服务端框架,它的核心设计理念来自 Angular,强调模块化、依赖注入和面向切面编程(AOP)。理解这套体系并不需要死记硬背概念,关键是弄清楚:每个请求从进入系统到返回响应的完整生命周期中,这些组件分别在哪个阶段做什么事。
下面我们从最基础的“模块”开始,依次拆解各个角色的职责和协作方式。
1. 模块:组织代码的基本单元
每个 NestJS 应用至少有一个根模块(AppModule),然后功能被拆分到一个个特性模块中。模块负责将控制器、服务等组件打包成一个范围明确的功能块,并通过 providers 和 controllers 数组声明该模块包含哪些可注入的对象。
@Module({
imports: [DatabaseModule, CacheModule], // 引入其他模块
controllers: [UserController], // 注册控制器
providers: [UserService], // 注册服务
exports: [UserService], // 导出给其他模块使用
})
export class UserModule {}
模块的实用原则:
- 按业务域拆分:
UserModule、OrderModule、AuthModule,而不是按技术层拆分。 - 跨模块共享的服务必须显式
exports,否则注入会报错。 - 全局模块可以用
@Global()装饰,但应谨慎使用,避免依赖关系模糊。
2. 控制器:请求的入口
控制器负责接收 HTTP 请求,并返回响应。它是路由的载体,决定了哪个 URL 由哪个方法处理。控制器的职责应该很“薄”:只做参数提取、调用服务、构建响应对象,不应该包含业务逻辑。
@Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}
@Get(':id')
async findOne(@Param('id', ParseIntPipe) id: number) {
return this.userService.findById(id);
}
@Post()
async create(@Body() createUserDto: CreateUserDto) {
return this.userService.create(createUserDto);
}
}
关键点:
- 使用
@Get、@Post、@Put、@Delete等装饰器映射 HTTP 动词和路径。 - 通过管道(如
ParseIntPipe)进行参数转换和校验,控制器本身不应做校验。 - 返回值会被 NestJS 自动序列化为 JSON,并设置合适的 Content-Type。
3. 服务:业务逻辑的载体
服务是真正干活的地方。它被注解为 @Injectable(),通过依赖注入被控制器或其他服务使用。服务包含了核心业务逻辑、数据库操作、外部 API 调用等。
@Injectable()
export class UserService {
constructor(
@InjectRepository(User)
private readonly userRepository: Repository<User>,
) {}
async findById(id: number): Promise<User> {
const user = await this.userRepository.findOneBy({ id });
if (!user) {
throw new NotFoundException(`用户 ${id} 不存在`);
}
return user;
}
async create(dto: CreateUserDto): Promise<User> {
const user = this.userRepository.create(dto);
return this.userRepository.save(user);
}
}
服务的设计要点:
- 一个服务只负责一个明确的领域,避免“万能 Service”。
- 业务逻辑集中在服务中,控制器只做委托调用。
- 服务之间可以互相注入,但要避免循环依赖(可用
forwardRef解决,但更推荐重构代码结构)。
4. 守卫:请求的“门卫”
守卫在请求到达控制器之前执行,用于判断该请求是否有权继续。最常见的场景是身份认证和权限校验。守卫返回 true 时请求继续,返回 false 或抛异常时请求被拦截。
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const token = request.headers.authorization;
if (!token) {
throw new UnauthorizedException('未提供认证令牌');
}
try {
request.user = verifyToken(token);
return true;
} catch {
throw new UnauthorizedException('令牌无效或已过期');
}
}
}
使用方式:
@Controller('admin')
@UseGuards(AuthGuard) // 整个控制器受保护
export class AdminController { ... }
@Get('profile')
@UseGuards(AuthGuard, RoleGuard) // 单一路由受保护,可以组合多个守卫
getProfile(@Req() req) {
return req.user;
}
守卫的执行时机: 在所有中间件之后,但在任何管道和拦截器之前。
5. 拦截器:请求与响应的“包装器”
拦截器可以在方法执行前后介入,处理以下通用需求:
- 在方法执行前绑定额外逻辑(如观测计时)
- 在方法执行后转换响应结构(如统一包装成
{ code, data, message }格式) - 在异常时处理错误响应
- 完全覆盖方法行为(如实现缓存)
@Injectable()
export class TransformInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const now = Date.now();
return next.handle().pipe(
map(data => ({
code: 200,
data,
message: 'success',
timestamp: new Date().toISOString(),
})),
tap(() => console.log(`请求耗时: ${Date.now() - now}ms`)),
);
}
}
全局应用:
app.useGlobalInterceptors(new TransformInterceptor());
或者在模块级别:
@Module({
providers: [{
provide: APP_INTERCEPTOR,
useClass: TransformInterceptor,
}],
})
export class AppModule {}
拦截器 vs 守卫 vs 中间件: 中间件在请求进入框架之前(路由解析之前)执行,适合处理如日志、CORS 等底层任务;守卫做权限判断;拦截器做方法前后的增强和转换。三层各有分工。
6. 请求生命周期的完整协作流程
以一个需要认证的 GET /users/:id 请求为例,完整流程如下:
- 中间件 先执行(如果有全局或模块中间件),处理请求日志、CORS 头等。
- 路由匹配,找到对应的控制器方法。
- 守卫 执行认证检查,失败则直接返回 401。
- 拦截器的
intercept方法(前处理)执行,可以记录开始时间。 - 管道 对
:id进行类型转换和验证(如ParseIntPipe)。 - 控制器 方法执行,调用
userService.findById(id)。 - 服务 执行数据库查询,返回用户对象或抛出异常。
- 如果无异常,拦截器的
pipe操作(后处理)转换响应体为统一格式。 - 框架将最终响应序列化为 JSON 发送给客户端。
在这个流程中,每个组件各司其职,开发者可以清晰地决定“验证”放在哪一层,“日志”放在哪一层,“变换响应”放在哪一层,从而让代码职责单一、可测试、可维护。
7. 体系设计的工程落地建议
- 控制器要薄,服务要厚:业务复杂度上升时,重构服务比重构控制器安全得多。
- 不要滥用拦截器:虽然能统一格式很方便,但把过多业务逻辑塞进拦截器会让调试变难。拦截器更适合切面关注点(日志、事务、缓存)。
- 守卫 + 自定义装饰器 = 优雅的权限模型:比如用
@Roles('admin')配合RolesGuard,元数据(Reflector)驱动权限控制,比在每个方法里写判断清晰得多。 - 模块拆分适度:对于小型项目,模块过多反而增加目录跳转成本;对于中型以上项目,按领域拆模块能有效界定边界,避免相互侵入。
- 利用 Nest CLI 生成骨架:
nest g module user、nest g controller user、nest g service user可以快速搭建一致的项目结构。
NestJS 的这套体系,本质上是对“分层架构”和“AOP”思想的具体实现。它不要求你刚入门就完全掌握所有概念,但随着业务规模的增长,这种有规矩、可插拔的结构会成为保持代码整洁的强有力保障。