主要介绍NestJS的5个基本概念以及他们的流程
Middleware中间件Exception filter异常过滤器Pipes管道Guards守卫Interceptors拦截器
Middleware 中间件
引用官网一张图:
由此可见,中间件这个顺序是在请求路由之前的,也是最早的。
打个比方:大楼前的门卫室
典型用途:记录日志-路由请求时间,IP,地址
注意点:必须调用next() 传递给下一个处理器
执行时机:最早执行,在路由匹配之前
代码事例:
// logger.middleware.ts
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
next(); // 注意
}
}
// 注册中间件
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes('*'); // 应用到所有路由
}
}
Guards(守卫)
继续引用官网的图片
打个比方:类似保镖的职能,检查有没有权限进入
典型用途:身份认证(JWT Token)验证、IP白/黑名单、限流等功能
执行时机:在middleware之后,interceptor之前
以下是JWT验证代码事例
@Injectable()
export class JwtAuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean | Promise<boolean> {
const request = context.switchToHttp().getRequest();
const token = request.headers['authorization'];
if (!token) {
throw new UnauthorizedException('需要登录');
}
// 验证token
const user = this.verifyToken(token);
request.user = user; // 注入用户信息
return true; // true=允许访问, false=拒绝(403)
}
}
// 1.全局注入
const app = await NestFactory.create(AppModule);
app.useGlobalGuards(new RolesGuard());
// 2.导入模块
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
@Module({
providers: [
{
provide: APP_GUARD,
useClass: JwtAuthGuard ,
},
],
})
export class AppModule {}
Interceptors(拦截器) 双向执行
主要作用:在方法前后进行额外处理
打个比方: 录音笔,记录前后的所有的过程
典型用途: 统一响应格式、性能监控(记录执行时间)、日志记录
执行时机: 前置:在pipe之前,后置:在controller返回之后
@Injectable()
export class ResponseInterceptor 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(),
duration: `${Date.now() - now}ms` // 计算执行时间
}))
);
}
}
// 1.全局注入
app.useGlobalInterceptors(new ResponseInterceptor());
// 2.导入模块
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
@Module({
providers: [
{
provide: APP_INTERCEPTOR,
useClass: ResponseInterceptor,
},
],
})
export class AppModule {}
全局日志事例代码:
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
Logger,
} from "@nestjs/common";
import { Observable } from "rxjs";
import { tap, catchError } from "rxjs/operators";
import { Request, Response } from "express";
/**
* 日志拦截器
* 在请求处理前后记录日志
* 执行顺序: 请求进入 → Before → 控制器处理 → After → 响应返回
*/
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
private readonly logger = new Logger(LoggingInterceptor.name);
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest<Request>();
const response = context.switchToHttp().getResponse<Response>();
const method = request.method;
const url = request.url;
const body = request.body;
const query = request.query;
const params = request.params;
const userAgent = request.headers["user-agent"] || "unknown";
const ip = this.getClientIp(request);
// 请求进入时的日志 (Before)
const now = Date.now();
this.logger.log(
`[请求进入] ${method} ${url} - IP: ${ip} - UA: ${userAgent}`
);
// 如果有请求体,记录请求参数
if (Object.keys(body || {}).length > 0) {
this.logger.debug(`[请求参数 Body] ${JSON.stringify(body)}`);
}
if (Object.keys(query || {}).length > 0) {
this.logger.debug(`[请求参数 Query] ${JSON.stringify(query)}`);
}
if (Object.keys(params || {}).length > 0) {
this.logger.debug(`[请求参数 Params] ${JSON.stringify(params)}`);
}
return next.handle().pipe(
tap((data) => {
// 请求处理完成后的日志 (After)
const duration = Date.now() - now;
const statusCode = response.statusCode;
this.logger.log(
`[请求完成] ${method} ${url} - 状态码: ${statusCode} - 耗时: ${duration}ms`
);
// 可选: 记录响应数据 (注意: 生产环境可能不需要记录响应体)
if (process.env.NODE_ENV === "development") {
this.logger.debug(
`[响应数据] ${JSON.stringify(data).substring(0, 200)}`
);
}
}),
catchError((error) => {
// 捕获错误并记录
const duration = Date.now() - now;
const errorMessage =
error instanceof Error ? error.message : String(error);
this.logger.error(
`[请求失败] ${method} ${url} - 耗时: ${duration}ms - 错误: ${errorMessage}`
);
throw error; // 重新抛出错误,让异常过滤器处理
})
);
}
/**
* 获取客户端真实 IP
* 支持代理场景
*/
private getClientIp(request: Request): string {
const forwarded = request.headers["x-forwarded-for"];
if (forwarded) {
const ips = Array.isArray(forwarded)
? forwarded[0]
: forwarded.split(",")[0];
return ips.trim();
}
const realIp = request.headers["x-real-ip"];
if (realIp) {
return Array.isArray(realIp) ? realIp[0] : realIp;
}
return request.ip || request.socket.remoteAddress || "unknown";
}
}
// 1.全局注入
app.useGlobalInterceptors(new LoggingInterceptor());
// 2.导入模块
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
@Module({
providers: [
{
provide: APP_INTERCEPTOR,
useClass: LoggingInterceptor,
},
],
})
export class AppModule {}
与middleware的区别就是,拦截器记录的日志记录用于更详细的开发日志
Pipes 管道
主要作用: 数据转换,数据验证
执行时机: 在参数传给控制器之前执行
NestJS内置管道:
| 管道名 | 作用 |
|---|---|
ValidationPipe | 验证DTO |
ParseIntPipe | 转换为整数 |
ParseFloatPipe | 转换为浮点数 |
ParseBoolPipe | 转换为布尔值 |
ParseArrayPipe | 转换为数组 |
ParseUUIDPipe | 转换为UUID |
ParseEnumPipe | 转换为数组 |
DefaultValuePipe | 设置默认值 |
ParseFilePipe | 转换为文件 |
ParseDatePipe | 转换为日期 |
都是从@nestjs/common导出
事例:
evvent(@Query("bool", new DefaultValuePipe(true), ParseBoolPipe) bool?: boolean)
{
}
ClassValidator 类别验证器:
参考链接 : validation 验证器
npm i --save class-validator class-transformer
用法:
import { IsString, IsInt } from 'class-validator';
export class CreateCatDto {
@IsString()
name: string;
@IsInt()
age: number;
@IsString()
breed: string;
}
// 1.全局注入
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
// 2 导入模块
import { Module } from '@nestjs/common';
import { APP_PIPE } from '@nestjs/core';
@Module({
providers: [
{
provide: APP_PIPE,
useClass: ValidationPipe,
},
],
})
export class AppModule {}
Exception filters 异常过滤器
主要作用: 最后的兜底处理
打个比方:统一错误响应格式,记录错误日志
执行时机: 任何阶段抛出异常时执行
内置异常: 在 @nestjs/common李敏
| 异常类别 |
|---|
HttpException |
BadRequestException |
UnauthorizedException |
NotFoundException |
ForbiddenException |
| ...还有很多 就不一一列举了 可参考 异常记录 |
代码案例:
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Request, 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 request = ctx.getRequest<Request>();
const status = exception.getStatus();
response
.status(status)
.json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
});
}
}
// 1.全局注入
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
// 2.导入模块
import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';
@Module({
providers: [
{
provide: APP_FILTER,
useClass: HttpExceptionFilter,
},
],
})
export class AppModule {}
Custome decorator 自定义装饰器
Nest提供的参数装饰器 (Param decorators):
| 名字 | 引用 |
|---|---|
@Request(), @Req() | req |
@Response(),@Res() | res |
@Nest() | nest |
@Session() | req.session |
@Param(param?:string) | req.params/req.params[param] |
@Boby(param?:string) | req.body/req.body[param] |
@Query(param?:string) | req.query/req.query[param] |
@Header(param?:string) | req.headers/req.headers[param] |
@IP() | req.ip |
@HostParam | req.hosts |
事例代码:
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const User = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
},
);
// 使用
@Get()
async findOne(@User() user: UserEntity) {
console.log(user);
}
总结
不同点
| 机制 | 主要作用 | 执行时机 | 典型用途 |
|---|---|---|---|
| Middleware | 最早,路由前 | 最早,路由前 | 日志、cors |
| Guards | 权限、认证判断 | 控制器前 | 鉴权、橘色控制 |
| Interceptor | 扩展、包装请求响应 AOP 面向切面 | 控制器前后 | 日志、统一响应 |
| Pipe | 数据转换与校验 | 控制器参数绑定前 | DTO校验、转换 |
| Exception Filter | 统一异常处理 | 异常被抛出时 | 错误格式化 |
相同点
- 都支持全局、模块、控制器、方法级别的注册
- 全局注册方式相同
- useGlobalxxx
- providers:[]
- 都是用于增强应用到可维护性和可扩展性
正常请求流程
请求流程:
客户端
↓
Middleware (中间件) ← 最早执行,在路由之前
↓
Guards (守卫)
↓
Interceptor - Before (拦截器前置)
↓
Pipes (管道)
↓
Controller (控制器处理)
↓
Interceptor - After (拦截器后置) ← 可以转换响应数据
↓
Exception Filters (异常过滤器)
↓
客户端
好了,完结,撒花🎉🎉🎉,谢谢大家观看到这里