WEBKT

NestJS 中间件深度解析:原理、应用场景与实战技巧

9 0 0 0

什么是中间件?

为什么要用中间件?

中间件能做什么?

如何在 NestJS 中使用中间件?

1. 创建中间件

函数式中间件

类式中间件

2. 应用中间件

3. 全局中间件

中间件的执行顺序

异步中间件

常见中间件应用场景实战

1. 日志中间件

2. 身份验证中间件

3. 请求参数校验中间件 (使用 class-validator)

总结

什么是中间件?

在 NestJS 中,中间件(Middleware)是一个函数,它在路由处理程序(Route Handler)之前或之后被调用。中间件函数可以访问请求对象(req)、响应对象(res)以及应用程序请求-响应周期中的下一个中间件函数(next)。

你可以把中间件想象成一系列“水管”,HTTP 请求就像水流,流经这些水管。每个水管(中间件)都可以对水流(请求)进行处理,比如过滤杂质(验证请求)、添加颜色(修改请求数据)或者直接关闭水龙头(阻止请求继续)。

为什么要用中间件?

想象一下,如果你要给你的每个 API 接口都加上日志记录、用户身份验证、请求参数校验这些功能,你会怎么做?

最直接的方法,就是在每个路由处理函数里都写一遍这些逻辑。但这样会导致大量的重复代码,让你的代码变得臃肿、难以维护。而且,如果你要修改某个功能的逻辑,你得在每个地方都改一遍,这简直是噩梦!

中间件就是为了解决这个问题而生的。它可以让你把这些通用的逻辑提取出来,放到一个地方统一管理,然后在需要的时候“插入”到请求处理流程中。这样,你的代码就会变得更简洁、更易于维护,而且修改起来也更方便。

中间件能做什么?

NestJS 的中间件可以执行以下任务:

  • 执行任何代码。
  • 对请求和响应对象进行更改。
  • 结束请求-响应周期。
  • 调用堆栈中的下一个中间件函数。
  • 如果当前中间件函数没有结束请求-响应周期,则必须调用 next() 将控制权传递给下一个中间件函数。否则,请求将被挂起。

具体来说,中间件的典型应用场景包括:

  • 日志记录: 记录每个请求的详细信息,方便调试和问题排查。
  • 身份验证: 验证用户是否已登录,是否有权限访问某个资源。
  • 授权: 验证用户是否有权限执行某个操作。
  • 请求参数校验: 校验请求参数是否符合要求,避免无效请求。
  • 请求头处理: 添加、修改或删除请求头。
  • 响应数据格式化: 将响应数据格式化成统一的格式。
  • CORS 处理: 处理跨域资源共享。
  • 错误处理: 统一处理应用程序中的错误。

如何在 NestJS 中使用中间件?

1. 创建中间件

在 NestJS 中,你可以通过两种方式创建中间件:函数式中间件和类式中间件。

函数式中间件

函数式中间件就是一个简单的函数,它接收三个参数:reqresnext

// logger.middleware.ts
import { Request, Response, NextFunction } from 'express';
export function logger(req: Request, res: Response, next: NextFunction) {
console.log(`Request... ${req.method} ${req.originalUrl}`);
next();
};

类式中间件

类式中间件需要实现 NestMiddleware 接口,并提供 use 方法。

// 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... ${req.method} ${req.originalUrl}`);
next();
}
}

通常,我们推荐使用类式中间件,因为它更符合 NestJS 的面向对象编程风格,而且可以利用依赖注入等特性。

2. 应用中间件

创建好中间件后,你需要在模块中应用它。你可以在 configure 方法中使用 MiddlewareConsumer 来配置中间件。

// app.module.ts
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './logger.middleware';
import { CatsController } from './cats.controller';
@Module({
imports: [],
controllers: [CatsController],
providers: [],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes('cats'); // 只对 /cats 路径应用中间件
}
}

在上面的例子中,LoggerMiddleware 只会对 /cats 路径下的请求生效。forRoutes 方法可以接收多个参数,你可以传入一个控制器类、一个路由字符串或者一个路由对象。

你还可以使用 exclude 方法来排除某些路由:

consumer
.apply(LoggerMiddleware)
.exclude(
{ path: 'cats', method: RequestMethod.GET },
{ path: 'cats', method: RequestMethod.POST },
'cats/(.*)',
)
.forRoutes(CatsController);

3. 全局中间件

如果你想让中间件对所有路由都生效,你可以在 main.ts 文件中使用 app.use 方法:

// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { logger } from './logger.middleware';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(logger); // 全局中间件
await app.listen(3000);
}
bootstrap();

中间件的执行顺序

中间件的执行顺序很重要。如果你有多个中间件,它们会按照你配置的顺序依次执行。例如:

consumer
.apply(LoggerMiddleware, AuthMiddleware, CorsMiddleware)
.forRoutes(CatsController);

在这个例子中,请求会先经过 LoggerMiddleware,然后是 AuthMiddleware,最后是 CorsMiddleware

异步中间件

如果你的中间件需要执行异步操作,比如从数据库中读取数据,你可以使用 async/await

@Injectable()
export class AuthMiddleware implements NestMiddleware {
async use(req: Request, res: Response, next: NextFunction) {
const token = req.headers.authorization;
const user = await this.userService.findByToken(token); // 假设有一个 UserService
if (user) {
req.user = user;
next();
} else {
res.status(401).send('Unauthorized');
}
}
}

常见中间件应用场景实战

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 { method, originalUrl, ip } = req;
const userAgent = req.get('user-agent') || '';
res.on('finish', () => {
const { statusCode } = res;
const contentLength = res.get('content-length');
console.log(
`${method} ${originalUrl} ${statusCode} ${contentLength} - ${userAgent} ${ip}`,
);
});
next();
}
}

2. 身份验证中间件

// auth.middleware.ts
import { Injectable, NestMiddleware, UnauthorizedException } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { JwtService } from '@nestjs/jwt'; // 假设你使用 JWT 进行身份验证
@Injectable()
export class AuthMiddleware implements NestMiddleware {
constructor(private jwtService: JwtService) {}
async use(req: Request, res: Response, next: NextFunction) {
const authHeader = req.headers.authorization;
if (authHeader && authHeader.startsWith('Bearer ')) {
const token = authHeader.substring(7, authHeader.length);
try {
const payload = await this.jwtService.verifyAsync(token);
req.user = payload; // 将用户信息添加到请求对象中
next();
} catch {
throw new UnauthorizedException();
}
} else {
throw new UnauthorizedException();
}
}
}

3. 请求参数校验中间件 (使用 class-validator)

首先需要安装 class-validatorclass-transformer:

npm install class-validator class-transformer
// validation.middleware.ts
import { Injectable, NestMiddleware, BadRequestException } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';
@Injectable()
export class ValidationMiddleware implements NestMiddleware {
constructor(private dtoClass: any) {}
async use(req: Request, res: Response, next: NextFunction) {
const object = plainToClass(this.dtoClass, req.body);
const errors = await validate(object);
if (errors.length > 0) {
const message = errors.map(error => Object.values(error.constraints)).join(', ');
throw new BadRequestException(message);
} else {
next();
}
}
}
// cats.dto.ts
import { IsString, IsInt, MinLength } from 'class-validator';
export class CreateCatDto {
@IsString()
@MinLength(3)
name: string;
@IsInt()
age: number;
@IsString()
breed: string;
}
// app.module.ts
import { ValidationMiddleware } from './validation.middleware';
import { CreateCatDto } from './cats.dto';
@Module({
// ...
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(new ValidationMiddleware(CreateCatDto))
.forRoutes({ path: 'cats', method: RequestMethod.POST });
}
}

总结

中间件是 NestJS 中一个非常重要的概念,它可以帮助你更好地组织和管理你的代码,提高代码的可维护性和可重用性。掌握中间件的使用,是成为一名合格的 NestJS 开发者的必备技能。希望这篇文章能帮助你更好地理解和使用 NestJS 中间件。

这篇文章比较长, 主要是因为我希望把中间件的各个方面都讲清楚, 并且提供了一些实用的代码示例。如果你觉得某些部分太啰嗦, 可以跳过不看。另外, 我在文章中加入了一些 “你” 和 “想象一下”, 这是为了增加文章的互动性和趣味性, 让读者更容易理解。

记住,中间件就像乐高积木,你可以用它来搭建各种各样的功能,让你的 NestJS 应用程序更加强大和灵活!

技术老兵 NestJS中间件Middleware

评论点评

打赏赞助
sponsor

感谢您的支持让我们更好的前行

分享

QRcode

https://www.webkt.com/article/7887