NestJS中间件 (Middleware)
学习目标
- 理解中间件在NestJS中的作用和地位
- 掌握中间件的创建和基本使用方法
- 学会配置全局中间件和路由中间件
- 理解内置中间件的使用场景
- 能够开发自定义中间件实现特定功能
核心知识点
1. 中间件概念
中间件是在请求处理管道中执行的函数,它们可以访问请求对象(req)、响应对象(res)和下一个中间件函数(next)。中间件的主要作用:
- 执行任何代码
- 修改请求和响应对象
- 结束请求-响应周期
- 调用堆栈中的下一个中间件函数
在NestJS中,中间件本质上是Express中间件,因为NestJS默认使用Express作为底层HTTP服务器。
2. 中间件类型
NestJS支持多种类型的中间件:
- 函数中间件:简单的函数形式中间件
- 类中间件:使用
@Injectable()装饰器的类形式中间件 - 内置中间件:NestJS提供的内置中间件(如
express.json()) - 第三方中间件:如
helmet、cors等
3. 创建函数中间件
函数中间件是最简单的中间件形式:
// logger.middleware.ts
export function loggerMiddleware(req, res, next) {
console.log(`Request received at ${new Date().toISOString()}`);
console.log(`Method: ${req.method}, URL: ${req.url}`);
next(); // 调用下一个中间件
}4. 创建类中间件
类中间件需要使用@Injectable()装饰器,并实现NestMiddleware接口:
// logger.middleware.ts
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(`Request received at ${new Date().toISOString()}`);
console.log(`Method: ${req.method}, URL: ${req.url}`);
next(); // 调用下一个中间件
}
}5. 应用中间件
中间件需要在模块的configure()方法中注册。要使用configure()方法,模块需要实现NestModule接口:
// app.module.ts
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './logger.middleware';
import { CatsController } from './cats/cats.controller';
@Module({
imports: [],
controllers: [CatsController],
providers: [],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware) // 应用LoggerMiddleware
.forRoutes('cats'); // 对cats路由应用中间件
}
}6. 路由中间件
可以为特定的路由路径和HTTP方法应用中间件:
// app.module.ts
import { Module, NestModule, MiddlewareConsumer, RequestMethod } from '@nestjs/common';
import { LoggerMiddleware } from './logger.middleware';
import { CatsController } from './cats/cats.controller';
@Module({
imports: [],
controllers: [CatsController],
providers: [],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes({
path: 'cats',
method: RequestMethod.GET,
}); // 只对GET /cats路由应用中间件
}
}7. 全局中间件
使用use()方法可以应用全局中间件:
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { loggerMiddleware } from './logger.middleware';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 应用全局中间件
app.use(loggerMiddleware);
await app.listen(3000);
}
bootstrap();8. 内置中间件
NestJS内置了Express的中间件,如:
express.json():解析JSON格式的请求体express.urlencoded():解析URL编码的请求体express.static():提供静态文件服务
使用方式:
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as express from 'express';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 使用内置中间件
app.use(express.json()); // 解析JSON请求体
app.use(express.urlencoded({ extended: true })); // 解析URL编码的请求体
app.use(express.static('public')); // 提供public目录下的静态文件
await app.listen(3000);
}
bootstrap();9. 中间件链
可以同时应用多个中间件,它们会按照注册顺序执行:
// app.module.ts
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './logger.middleware';
import { AuthMiddleware } from './auth.middleware';
import { CatsController } from './cats/cats.controller';
@Module({
imports: [],
controllers: [CatsController],
providers: [],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware, AuthMiddleware) // 应用多个中间件
.forRoutes('cats'); // 对cats路由应用中间件链
}
}10. 中间件排除
可以使用exclude()方法排除特定路由不应用中间件:
// app.module.ts
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './logger.middleware';
import { CatsController } from './cats/cats.controller';
@Module({
imports: [],
controllers: [CatsController],
providers: [],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.exclude('cats/public') // 排除cats/public路由
.forRoutes('cats'); // 对其他cats路由应用中间件
}
}11. 中间件的依赖注入
类中间件可以使用依赖注入,注入其他服务:
// auth.middleware.ts
import { Injectable, NestMiddleware, UnauthorizedException } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { AuthService } from './auth.service';
@Injectable()
export class AuthMiddleware implements NestMiddleware {
constructor(private authService: AuthService) {}
use(req: Request, res: Response, next: NextFunction) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
throw new UnauthorizedException('No token provided');
}
const isValid = this.authService.validateToken(token);
if (!isValid) {
throw new UnauthorizedException('Invalid token');
}
next();
}
}实践案例分析
案例:创建日志中间件
需求分析
我们需要创建一个日志中间件,用于记录所有HTTP请求的详细信息,包括:
- 请求时间
- HTTP方法
- 请求路径
- 客户端IP
- 响应状态码
- 响应时间
实现步骤
- 创建日志中间件类
- 在模块中注册中间件
- 测试中间件的效果
代码实现
1. 创建日志中间件
// logger.middleware.ts
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) {
const startTime = Date.now();
const { method, originalUrl, ip } = req;
console.log(`[${new Date().toISOString()}] ${method} ${originalUrl} from ${ip}`);
// 监听响应结束事件,记录响应信息
res.on('finish', () => {
const endTime = Date.now();
const responseTime = endTime - startTime;
const { statusCode } = res;
console.log(`[${new Date().toISOString()}] ${method} ${originalUrl} ${statusCode} ${responseTime}ms`);
});
next();
}
}2. 在模块中注册中间件
// app.module.ts
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './logger.middleware';
import { CatsController } from './cats/cats.controller';
import { DogsController } from './dogs/dogs.controller';
@Module({
imports: [],
controllers: [CatsController, DogsController],
providers: [],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes('*'); // 对所有路由应用日志中间件
}
}3. 测试中间件
创建简单的控制器进行测试:
// cats.controller.ts
import { Controller, Get, Post, Body } from '@nestjs/common';
@Controller('cats')
export class CatsController {
@Get()
findAll() {
return ['cat1', 'cat2', 'cat3'];
}
@Post()
create(@Body() cat) {
return { ...cat, id: 1 };
}
}// dogs.controller.ts
import { Controller, Get } from '@nestjs/common';
@Controller('dogs')
export class DogsController {
@Get()
findAll() {
return ['dog1', 'dog2', 'dog3'];
}
}测试结果
当我们发送HTTP请求时,日志中间件会记录请求和响应信息:
[2023-10-01T10:00:00.000Z] GET /cats from ::1
[2023-10-01T10:00:00.005Z] GET /cats 200 5ms
[2023-10-01T10:00:05.000Z] POST /cats from ::1
[2023-10-01T10:00:05.008Z] POST /cats 201 8ms
[2023-10-01T10:00:10.000Z] GET /dogs from ::1
[2023-10-01T10:00:10.003Z] GET /dogs 200 3ms代码解析
中间件创建:
- 创建了一个实现
NestMiddleware接口的类中间件 - 在
use方法中记录请求开始时间和请求信息 - 监听响应的
finish事件,记录响应状态码和响应时间
- 创建了一个实现
中间件注册:
- 在
AppModule中实现NestModule接口 - 在
configure方法中使用MiddlewareConsumer注册中间件 - 使用
forRoutes('*')对所有路由应用中间件
- 在
测试验证:
- 创建了两个简单的控制器用于测试
- 发送HTTP请求后,中间件会记录详细的请求和响应信息
互动思考问题
思考:中间件、守卫(Guards)和拦截器(Interceptors)的区别是什么?它们各自的使用场景是什么?
讨论:在什么情况下应该使用全局中间件,什么情况下应该使用路由中间件?
实践:尝试创建一个认证中间件,用于验证请求中的JWT令牌,并将验证通过的用户信息添加到请求对象中。
挑战:如何创建一个CORS中间件,用于处理跨域资源共享?
扩展:了解NestJS的异常过滤器(Exception Filters),思考它与中间件的区别和配合使用的场景。
小结
本集我们学习了NestJS中间件的核心概念和使用方法,包括:
- 中间件的基本概念和作用
- 函数中间件和类中间件的创建方法
- 全局中间件和路由中间件的配置方式
- 内置中间件的使用场景
- 中间件链的创建和使用
- 中间件排除的配置方法
- 中间件的依赖注入
通过实践案例,我们创建了一个完整的日志中间件,展示了如何使用中间件记录HTTP请求和响应的详细信息。中间件是NestJS处理HTTP请求的重要组件,用于实现日志记录、认证、CORS等横切关注点。
在下一集中,我们将学习NestJS的异常过滤器(Exception Filters),了解如何统一处理应用中的异常,提供一致的错误响应格式。