Nest.js 中间件 Middleware

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

Nest.js 中间件
Nest.js 中间件

默认情况下,Nest 中间件等同于 express 中间件。 以下来自官方 express 文档的描述,它描述了中间件的功能:

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

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

我们可以在函数中或在带有 @Injectable() 装饰器的类中实现自定义 Nest 中间件。 该类应实现 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...');
    next();
  }
}

应用中间件

@Module() 装饰器中没有中间件的位置。 相反,我们使用模块类的 configure() 方法来设置它们。 包含中间件的模块必须实现 NestModule 接口。 让我们在 AppModule 级别设置 LoggerMiddleware

app.module.ts

import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { CatsModule } from './cats/cats.module';

@Module({
  imports: [CatsModule],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('cats');
  }
}

在上面的示例中,我们为之前在 CatsController 中定义的 /cats 路由处理程序设置了 LoggerMiddleware。 在配置中间件时,我们还可以通过将包含路由路径和请求方法的对象传递给 forRoutes() 方法来进一步将中间件限制为特定的请求方法。 在下面的示例中,请注意我们导入 RequestMethod 枚举来引用所需的请求方法类型。

app.module.ts

import { Module, NestModule, RequestMethod, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { CatsModule } from './cats/cats.module';

@Module({
  imports: [CatsModule],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes({ path: 'cats', method: RequestMethod.GET });
  }
}

提示 :可以使用 async/await 使 configure() 方法异步(例如,可以在 configure() 方法主体内等待异步操作的完成)。


中间件消费者

MiddlewareConsumer 是一个辅助类。 它提供了几种内置方法来管理中间件。 所有这些都可以简单地以流畅的风格链接起来。 forRoutes() 方法可以采用单个字符串、多个字符串、一个 RouteInfo 对象、一个控制器类甚至多个控制器类。 在大多数情况下,我们可能只会传递以逗号分隔的控制器列表。 下面是一个带有单个控制器的示例:

app.module.ts

import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { CatsModule } from './cats/cats.module';
import { CatsController } from './cats/cats.controller';

@Module({
  imports: [CatsModule],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes(CatsController);
  }
}

提示 : apply() 方法可以采用单个中间件,也可以采用多个参数来指定多个中间件。


排除路由

有时我们希望排除某些路由应用中间件。 我们可以使用 exclude() 方法轻松排除某些路由。 该方法可以采用单个字符串、多个字符串或标识要排除的路由的 RouteInfo 对象,如下所示:

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

提示 : exclude() 方法支持使用 path-to-regexp 包的通配符参数。

在上面的示例中,LoggerMiddleware 将绑定到 CatsController 中定义的所有路由,除了传递给 exclude() 方法的三个路由。


功能中间件

我们一直使用的 LoggerMiddleware 类非常简单。 它没有成员,没有其他方法,也没有依赖项。 为什么我们不能在一个简单的函数而不是一个类中定义它? 事实上,我们可以。 这种类型的中间件称为功能中间件。 让我们将 logger 中间件从基于类的中间件转换为函数式中间件来说明区别:

logger.middleware.ts

import { Request, Response, NextFunction } from 'express';

export function logger(req: Request, res: Response, next: NextFunction) {
  console.log(`Request...`);
  next();
};

并在 AppModule 中使用它:

app.module.ts

consumer
  .apply(logger)
  .forRoutes(CatsController);

提示 : 当我们的中间件不需要任何依赖项时,可以考虑使用更简单的功能中间件作为替代方案。


多个中间件

如上所述,为了绑定多个顺序执行的中间件,只需在 apply() 方法中提供一个逗号分隔的列表:

consumer.apply(cors(), helmet(), logger).forRoutes(CatsController);

全局中间件

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

const app = await NestFactory.create(AppModule);
app.use(logger);
await app.listen(3000);

提示 : 无法访问全局中间件中的 DI 容器。 使用 app.use() 时,我们可以使用功能性中间件。 或者,可以使用类中间件并在 AppModule(或任何其他模块)中使用 .forRoutes('*') 来使用它。

查看笔记

扫码一下
查看教程更方便