前言
说起中间件,很容易想到Koa 的中间件,也就是洋葱模型。Koa的洋葱模型是通过koa-compose库实现的,该库将多个中间件函数组合成一个组合函数,并按照指定的顺序执行每个中间件,并等待每个中间件的Promise解决后继续执行下一个中间件,直到所有中间件执行完毕。这种机制使得Koa的中间件执行顺序符合洋葱模型的特性。
那么,在 Nest 中是否也有中间件的概念呢?答案是肯定的。
根据官方文档,Nest 中间件是在路由处理函数之前调用的一个函数。
Middleware is a function which is called before the route handler. Middleware functions have access to the request and response objects, and the
next()middleware function in the application’s request-response cycle. The next middleware function is commonly denoted by a variable namednext.
新建一个中间件
运行命令nest g middleware aaa --no-spec --flat,可以看到src目录下生成了 aaa.middleware.ts 文件。我们在 next() 上下添加两行打印。
// src\aaa.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response } from 'express';
@Injectable()
export class AaaMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: () => void) {
console.log('111');
next();
console.log('112');
}
}
同时,在module文件中引入该中间件。其中.forRoutes('*')表示匹配所有路由。
// src\app.module.ts
import { Module, NestModule, MiddlewareConsumer, } from '@nestjs/common';
// ...
import { AaaMiddleware } from './aaa.middleware';
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(AaaMiddleware)
.forRoutes('*');
}
}
最后,运行项目pnpm start:dev,并访问 http://localhost:3000/, 可以看到打印了 111 和 112 。说明中间件生效了。
路由匹配规则
在上面的代码中,我们设置了匹配任意路由:.forRoutes('*')。
此外,我们可以设置精确的路由匹配规则,比如在下面的例子中,只有 /app, /app1 等路由可以匹配,而 / 等路由不匹配。
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(AaaMiddleware)
.forRoutes({
path: 'app*',
method: RequestMethod.GET,
});
}
}
还有,可以设置排除路由,app 路由的get请求和post请求不匹配,app/* 的路由都不匹配。
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(AaaMiddleware)
.exclude(
{ path: 'app', method: RequestMethod.GET },
{ path: 'app', method: RequestMethod.POST },
'app/(.*)',
)
.forRoutes('*');
}
}
类中间件
在 Nest 中,为什么要把 middleware 设置成 class 呢?这是为了依赖注入。比如,我们可以通过 @Inject(AppService) 将 AppService 注入到中间件 AaaMiddleware 里,这样就可以使用 AppService 中的方法了。
import { Injectable, Inject, NestMiddleware } from '@nestjs/common';
import { Request, Response } from 'express';
import { AppService } from './app.service';
@Injectable()
export class AaaMiddleware implements NestMiddleware {
@Inject(AppService)
private readonly appService: AppService;
use(req: Request, res: Response, next: () => void) {
console.log('111');
console.log(this.appService.getHello());
next();
console.log('112');
}
}
当我们访问 /app 路由时,可以看到打印了:
111
Hello World!
112
函数中间件
如果中间件不需要依赖注入,我们可以写成函数的方式。
// src\aaa.middleware.ts
import { Request, Response } from 'express';
export function AaaMiddleware(req: Request, res: Response, next: () => void) {
console.log('111');
next();
console.log('112');
}
中间件的使用
- 多个中间件
如果我们需要按顺序地使用多个中间件,那么在apply()方法中依次引入,用逗号隔开即可:
consumer
.apply(cors(), helmet(), logger)
.forRoutes(CatsController);
- 全局中间件
全局中间件只需要在 main.ts 中注册即可,而且只支持函数中间件。
// main.ts
const app = await NestFactory.create(AppModule);
app.use(logger);
await app.listen(3000);
如果是类中间件,相应地设置.forRoutes('*')也可以实现。
后记
Nest 也有中间件,带有 request、response、next 参数。对于中间件,我们可以实现依赖注入,还可以指定中间件作用于哪些路由等等。