中间件

中间件是一个在路由处理器之前被调用的函数。 中间件功能可以访问请求和响应对象,以及应用程序请求响应周期中的下一个中间件功能。下一个中间件函数通常由名为 next 的变量表示。

图1

Nest 中间件实际上等价于 express 中间件。 从 Express 官方文档复制的中间件功能有很多:

中间件函数可以执行以下任务:

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

Nest 中间件可以是一个函数,也可以是一个带有 @Injectable() 装饰器的类。 这个类应该实现 NestMiddleware 接口。 我们来创建一个例子,LoggerMiddleware 类:

logger.middleware.ts

  1. import { Injectable, NestMiddleware, MiddlewareFunction } from '@nestjs/common';
  2. @Injectable()
  3. export class LoggerMiddleware implements NestMiddleware {
  4. resolve(...args: any[]): MiddlewareFunction {
  5. return (req, res, next) => {
  6. console.log('Request...');
  7. next();
  8. };
  9. }
  10. }

resolve() 方法必须返回常规的特定库的中间件 (req, res, next) => any

依赖注入

说到中间件, 也不例外。与提供者和控制器相同, 它们能够注入属于统一模块的依赖项(通过constructor)。

中间件放在哪里

中间件不能在 @Module() 装饰器中列出。我们必须使用 configure() 模块类的方法来设置它们。包含中间件的模块必须实现 NestModule 接口。让我们设置 LoggerMiddlewareApplicationModule 关卡上。

app.module.ts

  1. import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
  2. import { LoggerMiddleware } from './common/middlewares/logger.middleware';
  3. import { CatsModule } from './cats/cats.module';
  4. @Module({
  5. imports: [CatsModule],
  6. })
  7. export class ApplicationModule implements NestModule {
  8. configure(consumer: MiddlewareConsumer) {
  9. consumer
  10. .apply(LoggerMiddleware)
  11. .forRoutes('/cats');
  12. }
  13. }

在上面的例子中, 我们已经设置了 LoggerMiddleware/cats 的路由处理程序 CatsController 。MiddlewareConsumer 是一个帮助类。它提供了几种使用中间件的方法。他们都可以简单地链接。让我们来看看这些方法。

forRoutes() 可采取单个对象,多个对象,控制器类和甚至多个控制器类。在大多数情况下,你可能只是通过控制器,并用逗号分隔。以下是单个控制器的示例

app.module.ts

  1. import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
  2. import { LoggerMiddleware } from './common/middlewares/logger.middleware';
  3. import { CatsModule } from './cats/cats.module';
  4. @Module({
  5. imports: [CatsModule],
  6. })
  7. export class ApplicationModule implements NestModule {
  8. configure(consumer: MiddlewareConsumer): void {
  9. consumer
  10. .apply(LoggerMiddleware)
  11. .forRoutes(CatsController);
  12. }
  13. }

?> 该 apply() 方法可以采用单个中间件或一组中间件。

将参数传递给中间件

有时中间件的行为取决于自定义值,例如用户角色数组,选项对象等。我们可以将其他参数传递给 resolve() 来使用 with() 方法。看下面的例子:

app.moudle.ts

  1. import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
  2. import { LoggerMiddleware } from './common/middlewares/logger.middleware';
  3. import { CatsModule } from './cats/cats.module';
  4. import { CatsController } from './cats/cats.controller';
  5. @Module({
  6. imports: [CatsModule],
  7. })
  8. export class ApplicationModule implements NestModule {
  9. configure(consumer: MiddlewareConsumer): void {
  10. consumer
  11. .apply(LoggerMiddleware)
  12. .with('ApplicationModule')
  13. .forRoutes(CatsController);
  14. }
  15. }

我们已经通过了一个自定义字符串 - ApplicationModulewith() 方法。现在我们必须调整 LoggerMiddlewareresolve() 方法。

logger.middleware.ts

  1. import { Injectable, NestMiddleware, MiddlewareFunction } from '@nestjs/common';
  2. @Injectable()
  3. export class LoggerMiddleware implements NestMiddleware {
  4. resolve(name: string): MiddlewareFunction {
  5. return (req, res, next) => {
  6. console.log(`[${name}] Request...`); // [ApplicationModule] Request...
  7. next();
  8. };
  9. }
  10. }

name 的属性值将是 ApplicationModule

异步中间件

resolve() 方法中返回异步函数没有禁忌。所以,resolve() 方法也可以写成 async 的。这种模式被称为 延迟中间件

logger.middleware.ts

  1. import { Injectable, NestMiddleware, MiddlewareFunction } from '@nestjs/common';
  2. @Injectable()
  3. export class LoggerMiddleware implements NestMiddleware {
  4. async resolve(name: string): Promise<MiddlewareFunction> {
  5. await someAsyncJob();
  6. return async (req, res, next) => {
  7. await someAsyncJob();
  8. console.log(`[${name}] Request...`); // [ApplicationModule] Request...
  9. next();
  10. };
  11. }
  12. }

函数式中间件

LoggerMiddleware 很短。它没有成员,没有额外的方法,没有依赖关系。为什么我们不能只使用一个简单的函数?这是一个很好的问题,因为事实上 - 我们可以做到。这种类型的中间件称为函数式中间件。让我们把 logger 转换成函数。

logger.middleware.ts

  1. export function logger(req, res, next) {
  2. console.log(`Request...`);
  3. next();
  4. };

现在在 ApplicationModule 中使用它。

app.module.ts

  1. import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
  2. import { logger } from './common/middlewares/logger.middleware';
  3. import { CatsModule } from './cats/cats.module';
  4. import { CatsController } from './cats/cats.controller';
  5. @Module({
  6. imports: [CatsModule],
  7. })
  8. export class ApplicationModule implements NestModule {
  9. configure(consumer: MiddlewareConsumer) {
  10. consumer
  11. .apply(logger)
  12. .forRoutes(CatsController);
  13. }
  14. }

?> 当您的中间件没有任何依赖关系时,我们可以考虑使用函数式中间件。

多个中间件

如前所述,为了绑定顺序执行的多个中间件,我们可以在 apply() 方法内用逗号分隔它们。

  1. export class ApplicationModule implements NestModule {
  2. configure(consumer: MiddlewareConsumer) {
  3. consumer
  4. .apply(cors(), helmet(), logger)
  5. .forRoutes(CatsController);
  6. }
  7. }

全局中间件

为了一次将中间件绑定到每个注册路由,我们可以利用实例 use() 提供的方法 INestApplication

  1. const app = await NestFactory.create(ApplicationModule);
  2. app.use(logger);
  3. await app.listen(3000);