Nest.js 中间件 Middleware
中间件是在路由处理程序之前调用的函数。 中间件函数可以访问请求和响应对象,以及应用程序请求-响应周期中的 next()
中间件函数。 next 中间件函数通常由名为 next
的变量表示。
默认情况下,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('*') 来使用它。