全网最硬核NestJS实战手册(二):请求管线、数据持久化与认证授权全解析
前置阅读:[(一)从零搭建你的第一个企业级Node.js后端项目] 本文假定读者已掌握 Controller、Provider/DI、Module 三大基础概念。
一、请求管线全景图
NestJS 的请求处理是一个多阶段的管线。理解每个阶段的职责和执行顺序,是写出健壮后端代码的关键:
Request → Middleware(中间件) → Guard(守卫) → Interceptor(拦截器 - before) → Pipe(管道) → Controller(控制器方法) → Interceptor(拦截器 - after) → Exception Filter(异常过滤器) → Response下面按执行顺序逐一拆解。
二、Middleware:请求的第一道关卡
中间件是在路由处理器之前执行的函数,可访问请求对象(Request)、响应对象(Response)和next()函数。适用于请求日志、CORS 头设置、请求体解析等场景。
自定义日志中间件
import { Injectable, NestMiddleware } from '@nestjs/common'; import { Request, Response, NextFunction } from 'express'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: Request, res: Response, next: NextFunction) { console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`); next(); } }在模块中配置
中间件支持精确的路径匹配和排除规则:
export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware) .exclude( { path: 'cats', method: RequestMethod.GET }, { path: 'health', method: RequestMethod.ALL } ) .forRoutes(CatsController); } }forRoutes可以传入控制器类、路由路径字符串或通配符'*'。
三、Guard:权限的守门人
Guard 实现了CanActivate接口,用于权限校验和身份验证。它在所有中间件之后、拦截器和管道之前执行。返回true则放行,返回false则拒绝请求(返回 403 Forbidden)。
角色守卫实现
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; @Injectable() export class RolesGuard implements CanActivate { canActivate(context: ExecutionContext): boolean { const request = context.switchToHttp().getRequest(); const user = request.user; return user && user.roles && user.roles.includes('admin'); } }绑定方式
Guard 支持控制器级别、方法级别和全局级别三种绑定方式:
// 控制器级别 @Controller('cats') @UseGuards(RolesGuard) export class CatsController {} // 方法级别 @Get() @UseGuards(RolesGuard) findAll() {} // 全局级别 (main.ts) const app = await NestFactory.create(AppModule); app.useGlobalGuards(new RolesGuard());四、Interceptor:数据的拦截器
Interceptor 实现了NestInterceptor接口,基于 RxJS 的Observable,可以在方法执行前后拦截和变换数据流。典型场景包括响应格式统一包装、执行时间记录、缓存处理等。
响应统一包装
import { Injectable, NestInterceptor, ExecutionContext, CallHandler, } from '@nestjs/common'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; export interface Response<T> { data: T; success: boolean; } @Injectable() export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> { intercept( context: ExecutionContext, next: CallHandler, ): Observable<Response<T>> { return next.handle().pipe(map(data => ({ data, success: true }))); } }执行时间记录
@Injectable() export class LoggingInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const now = Date.now(); return next .handle() .pipe(tap(() => console.log(`Duration: ${Date.now() - now}ms`))); } }next.handle()返回的是 Observable,pipe()中的操作符在响应返回时执行(after 阶段)。在next.handle()之前的代码属于 before 阶段。
五、Pipe:数据的守门人
Pipe 实现了PipeTransform接口,承担两大职责:数据转换(如字符串转数字)和输入验证(校验数据格式)。管道在控制器方法调用之前执行,如果验证失败则抛出异常,阻止方法执行。
内置管道
NestJS 提供 10 个开箱即用的内置管道:
ValidationPipe、ParseIntPipe、ParseFloatPipe、ParseBoolPipe、ParseArrayPipe、ParseUUIDPipe、ParseEnumPipe、DefaultValuePipe、ParseFilePipe、ParseDatePipe
参数级绑定
@Get(':id') findOne(@Param('id', ParseIntPipe) id: number) { return this.catsService.findOne(id); }当访问/cats/abc时,ParseIntPipe会抛出 400 异常:
{ "statusCode": 400, "message": "Validation failed (numeric string is expected)", "error": "Bad Request" }自定义管道
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException, } from '@nestjs/common'; @Injectable() export class ParsePositiveIntPipe implements PipeTransform<string, number> { transform(value: string, metadata: ArgumentMetadata): number { const val = parseInt(value, 10); if (isNaN(val) || val <= 0) { throw new BadRequestException('ID must be a positive integer'); } return val; } }六、Exception Filter:异常的终结者
NestJS 内置全局异常层,自动捕获所有未处理的异常并返回统一的 JSON 响应。当业务需要自定义错误格式或处理特定异常类型时,可使用自定义异常过滤器。
自定义异常类
import { HttpException, HttpStatus } from '@nestjs/common'; export class ForbiddenException extends HttpException { constructor() { super('Forbidden', HttpStatus.FORBIDDEN); } }自定义异常过滤器
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, } from '@nestjs/common'; import { Response } from 'express'; @Catch(HttpException) export class HttpExceptionFilter implements ExceptionFilter { catch(exception: HttpException, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse<Response>(); const status = exception.getStatus(); response.status(status).json({ statusCode: status, timestamp: new Date().toISOString(), path: ctx.getRequest().url, }); } }@Catch()可以传入多个异常类型,不传参数则捕获所有异常。
七、数据持久化:TypeORM 与 Prisma
TypeORM 集成
TypeORM 是与 NestJS 配合最紧密的 ORM 框架:
npm install --save @nestjs/typeorm typeorm mysql2@Module({ imports: [ TypeOrmModule.forRoot({ type: 'mysql', host: 'localhost', port: 3306, username: 'root', password: 'password', database: 'nest_demo', entities: [Cat], synchronize: true, // 生产环境必须关闭 }), ], }) export class AppModule {}实体定义:
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; @Entity() export class Cat { @PrimaryGeneratedColumn() id: number; @Column() name: string; @Column() age: number; @Column() breed: string; }Repository 模式:
@Injectable() export class CatsService { constructor( @InjectRepository(Cat) private catsRepository: Repository<Cat>, ) {} findAll(): Promise<Cat[]> { return this.catsRepository.find(); } findOne(id: number): Promise<Cat | null> { return this.catsRepository.findOneBy({ id }); } async create(cat: CreateCatDto): Promise<Cat> { return this.catsRepository.save(cat); } async remove(id: number): Promise<void> { await this.catsRepository.delete(id); } }Prisma 集成
Prisma 是新一代 ORM,提供类型安全的数据库客户端:
npm install prisma --save-dev npm install @prisma/client npx prisma init// prisma.service.ts import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'; import { PrismaClient } from '@prisma/client'; @Injectable() export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy { async onModuleInit() { await this.$connect(); } async onModuleDestroy() { await this.$disconnect(); } }通过extends PrismaClient将 Prisma 客户端封装为 NestJS Provider,利用OnModuleInit和OnModuleDestroy生命周期钩子管理连接。
八、请求验证:DTO + class-validator
DTO 定义与验证规则
NestJS 推荐使用 DTO(Data Transfer Object)定义数据传输格式,配合class-validator实现声明式验证:
npm install class-validator class-transformerimport { IsString, IsInt, Min, Max, IsNotEmpty } from 'class-validator'; export class CreateCatDto { @IsString() @IsNotEmpty() name: string; @IsInt() @Min(0) @Max(30) age: number; @IsString() @IsNotEmpty() breed: string; }全局启用 ValidationPipe
import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes( new ValidationPipe({ whitelist: true, // 自动剥离非白名单属性 forbidNonWhitelisted: true, // 存在非白名单属性时抛出异常 transform: true, // 自动类型转换 }), ); await app.listen(3000); }三个关键配置:
whitelist: true:自动删除 DTO 中未定义的属性,防止恶意字段注入forbidNonWhitelisted: true:检测到未定义属性时直接抛出 400 错误,而非静默删除transform: true:将请求中的字符串自动转换为 DTO 声明的类型(如字符串"5"→ 数字5)
九、认证与授权:JWT + RBAC
JWT 认证
npm install @nestjs/jwt @nestjs/passport passport passport-jwtJWT 模块配置:
@Module({ imports: [ JwtModule.register({ secret: 'your-secret-key', signOptions: { expiresIn: '1h' }, }), ], providers: [AuthService], exports: [AuthService], }) export class AuthModule {}JWT 策略(Passport 集成):
import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: 'your-secret-key', }); } async validate(payload: any) { return { userId: payload.sub, username: payload.username }; } }保护路由:
@Controller('profile') export class ProfileController { @UseGuards(AuthGuard('jwt')) @Get() getProfile() { return { message: 'This is a protected route' }; } }RBAC 角色控制
结合自定义装饰器和 Guard 实现基于角色的访问控制:
// roles.decorator.ts import { SetMetadata } from '@nestjs/common'; export const Roles = (...roles: string[]) => SetMetadata('roles', roles);@Controller('admin') @UseGuards(JwtAuthGuard, RolesGuard) export class AdminController { @Roles('admin') @Get() adminOnly() { return { message: 'Admin content' }; } @Roles('user', 'admin') @Get('dashboard') dashboard() { return { message: 'Dashboard content' }; } }SetMetadata将角色信息附着在路由处理器上,RolesGuard通过Reflector读取元数据并与当前用户角色比对。
十、小结与下一步
本篇覆盖了 NestJS 请求管线中的五大组件,以及数据持久化和认证授权的完整实现:
| 组件 | 职责 | 典型场景 |
|---|---|---|
| Middleware | 请求预处理 | 日志、CORS、压缩 |
| Guard | 权限校验 | 认证、角色检查 |
| Interceptor | 数据拦截变换 | 响应包装、性能监控 |
| Pipe | 转换与验证 | 参数校验、类型转换 |
| Exception Filter | 异常处理 | 统一错误格式、异常分类 |
下一篇将进入精通阶段:GraphQL 集成、WebSocket 实时通信、微服务架构、任务调度、文件上传、测试策略、生产部署优化与最佳实践。
幸得你于纷扰时光里驻足品读,由衷致谢
Thank you for watching in your busy schedule. Thank you.