NestJS 主要组件详解(开发者视角)
// ---------- 模块(Module)示例 ----------
// 模块是组织代码的核心单元,用于聚合相关功能
@Module({
imports: [DatabaseModule, AuthModule], // 导入其他模块
controllers: [UserController], // 注册控制器
providers: [UserService, Logger] // 注册服务/提供者
})
export class UserModule {}
使用建议:
- 按业务领域划分模块(用户/订单/商品)
- 核心模块通过静态 register/forRoot 方法暴露配置
- 共享模块使用 exports 暴露公共组件
注意:避免循环依赖,使用 forwardRef() 处理模块间相互引用
// ---------- 控制器(Controller)示例 ----------
@Controller('users')
export class UserController {
constructor(private userService: UserService) {} // 依赖注入
@Get(':id')
@HttpCode(206) // 自定义状态码
async getUser(@Param('id') id: string) {
return this.userService.findUser(id);
}
@Post()
@Header('Cache-Control', 'none')
createUser(@Body() createUserDto: CreateUserDto) {
return this.userService.create(createUserDto);
}
}
使用建议:
- 保持控制器纤薄,仅处理 HTTP 相关逻辑
- 使用装饰器处理请求参数(@Query/@Body/@Headers)
- 复杂路由配置使用@Render/@Redirect
注意:避免在控制器中直接操作数据库,交由 Service 处理
// ---------- 服务(Service)示例 ----------
@Injectable()
export class UserService {
private cache = new Map<string, User>();
constructor(@Inject('Logger') private logger: Logger) {}
async findUser(id: string): Promise<User> {
this.logger.log(`查询用户 ${id}`);
return this.cache.get(id) || { id, name: '默认用户' };
}
}
使用建议:
- 使用 @Injectable() 声明可注入类
- 通过 constructor 注入依赖项
- 复杂业务实现领域驱动设计(DDD)
注意:服务应保持无状态,避免在实例中存储请求级数据
// ---------- 中间件(Middleware)示例 ----------
export class AuthMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const token = req.headers['authorization'];
if (!validateToken(token)) {
throw new UnauthorizedException('无效令牌');
}
next(); // 必须调用 next() 继续执行
}
}
// 模块中配置
@Module({
// ...
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(AuthMiddleware)
.exclude('public/*') // 排除特定路由
.forRoutes('*'); // 应用到所有路由
}
}
使用建议:
- 处理跨域、日志、请求预处理等通用逻辑
- 使用 exclude() 跳过不需要处理的路径
- 多个中间件使用 apply(m1, m2).forRoutes()
注意:Express 中间件需要适配器,Fastify 需要转换写法
// ---------- 管道(Pipe)示例 ----------
@Injectable()
export class ValidationPipe implements PipeTransform {
constructor(private schema: Joi.Schema) {}
transform(value: any) {
const { error } = this.schema.validate(value);
if (error) {
throw new BadRequestException('参数校验失败');
}
return value;
}
}
// 使用方式:
@Post()
createUser(@Body(new ValidationPipe(createUserSchema)) dto: CreateUserDto) {
// ...
}
使用建议:
- 优先使用内置 ValidationPipe + class-validator
- 自定义管道处理类型转换(字符串转日期/数字)
- 全局管道可统一校验规则
注意:管道抛出异常会被异常过滤器捕获
// ---------- 守卫(Guard)示例 ----------
@Injectable()
export class RolesGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const requiredRoles = ['admin'];
const user = context.switchToHttp().getRequest().user;
return requiredRoles.some(role => user.roles.includes(role));
}
}
// 控制器使用:
@UseGuards(RolesGuard)
@Get('admin')
getAdminData() { ... }
使用建议:
- 结合 JWT 策略实现 RBAC 权限控制
- 使用反射元数据存储权限信息(@SetMetadata)
- 全局守卫处理通用鉴权逻辑
注意:守卫抛出异常会直接终止请求链
// ---------- 拦截器(Interceptor)示例 ----------
@Injectable()
export class TransformInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
map(data => ({
code: 200,
data,
timestamp: new Date().toISOString()
}))
);
}
}
// 全局注册(main.ts)
app.useGlobalInterceptors(new TransformInterceptor());
使用建议:
- 统一响应格式
- 记录请求耗时(console.time/timeEnd)
- 缓存请求结果
注意:拦截器可以修改响应但无法修改请求头
实际开发注意事项
-
依赖注入顺序:
模块初始化顺序影响依赖可用性,使用 @Global() 定义全局模块 -
请求作用域:
需要请求级实例时使用 scope: Scope.REQUEST,注意性能影响 -
配置管理:
使用 @nestjs/config 加载环境变量,避免硬编码 -
异常处理优先级:
过滤器的捕获顺序:控制器方法 > 控制器类 > 全局 -
性能优化:
- 开启 FastifyAdapter 提升吞吐量
- 使用 CacheInterceptor 缓存高频请求
- 避免在循环中实例化服务
// 最佳实践示例:完整的用户模块结构
// user/
// ├── dto/
// │ ├── create-user.dto.ts
// │ └── update-user.dto.ts
// ├── entities/
// │ └── user.entity.ts
// ├── user.controller.ts
// ├── user.service.ts
// └── user.module.ts
通过合理组织代码结构,结合装饰器的声明式编程,可以显著提升 NestJS 项目的可维护性。
建议采用分层架构,区分 presentation layer(控制器)、business logic layer(服务)、data access layer(仓库模式),同时利用拦截器/过滤器等横向关注点处理机制保持代码整洁。