Skip to main content

中间件

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

Nest.js 中间件

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

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

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

您可以在函数或带有@Injectable()装饰器的类中实现自定义Nest中间件。该类应该实现NestMiddleware接口, 而函数没有任何特殊要求。让我们首先使用类方法实现一个简单的中间件功能。

warning

Express和fastify以不同的方式处理中间件并提供不同的方法签名。

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();
}
}

依赖注入

Nest中间件完全支持依赖注入。 就像提供者和控制器一样,它们能够注入同一模块中可用的依赖项。 像往常一样,这是通过构造函数(constructor)完成的

应用中间件

@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 });
}
}
tip

可以使用async/await使configure()方法异步(例如,您可以等待configure()方法体内异步操作的完成)

warning

当使用express适配器时,NestJS应用程序将默认从包body-parser注册jsonurlencoded。 这意味着如果您想通过MiddlewareConsumer自定义该中间件,则需要在使用NestFactory.create() 创建应用程序时将bodyParser标志设置为false来关闭全局中间件。

路由通配符

还支持基于模式的路由。例如,星号(asterisk)用作通配符,将匹配任意字符组合:

forRoutes({ path: 'ab*cd', method: RequestMethod.ALL })

ab*cd路由路径将匹配abcdab_cdabecd等。字符?,+,*,()可以再路由路径中使用, 并且是其正则表达式对应项的子集。连字符(-)和点(.)按字面意思解释为基于字符串的路径。

warning

fastify包使用最新版本的path-to-regexp包,不再支持通配符星号(asterisk)。相反,您必须使用参数 (例如,(.*), :splat*)。

中间件消费者

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);
}
}
tip

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

排除路由

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

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

exclude()方法使用path-to-regexp包支持通配符参数

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

函数式中间件

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

logger.middleware.ts
import { Request, Response, NextFunction } from 'express';

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

并在AppModule中使用:

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

当您的中间件不需要任何依赖项时,请考虑使用更简单的函数式中间件替代方案。

多中间件

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

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

全局中间件

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

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

无法访问全局中间件中的DI容器。使用app.use()时,您可以使用函数式中间件。 或者,您可以使用类中间件并在AppModule(或任何其他模块)中使用.forRoutes('*')来使用它。