中间件是一个在路由处理程序之前被调用的函数。中间件函数可以访问请求和响应对象,以及应用程序请求-响应周期中的next()
中间件函数。next中间件函数通常由一个名为next的变量表示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nfSI1Ac6-1690532232331)(https://raw.githubusercontent.com/lsy1998/pics/main/202307250929639.png)]
默认情况下,Nest中间件相当于express中间件。以下来自官方快速文档的描述描述了中间件的功能:
中间件函数可以执行以下任务:
- 执行任何代码。
- 对请求和响应对象进行更改。
- 调用堆栈中的下一个中间件函数。
- 如果当前中间件函数没有结束
请求-响应周期
,它必须调用next()将控制传递给下一个中间件函数。否则,请求将被挂起。
[!warning]
Express
和fastify
以不同的方式表达和处理中间件,并提供不同的方法签名。
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
。
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
enum来引用所需的请求方法类型。
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 });
}
}
[!hint]
configure()
方法可以使用async/await
实现异步(例如,你可以在configure()
方法体中等待异步操作的完成)。
[!warning]
当使用express
适配器时,默认情况下,NestJS应用会从包体解析器中注册json
和urlencoded
。这意味着,如果您想通过MiddlewareConsumer
定制中间件,那么在使用NestFactory.create()
创建应用程序时,需要通过将bodyParser
标志设置为false
来关闭全局中间件。
路由通配符
Nest也支持基于模式的路由。例如,星号用作通配符,并将匹配任何字符组合:
forRoutes({ path: 'ab*cd', method: RequestMethod.ALL });
'ab*cd'
路由路径将匹配abcd, ab_cd, abecd
,等等。字符?、+、*和()
可以在路由路径中使用,它们是对应正则表达式的子集。连字符(-)和点(.)由基于字符串的路径逐字解释。
[!warning]
fastify
包使用最新版本的path-to-regexp
包,它不再支持通配符*。相反,您必须使用参数(例如,(.),:splat)。
中间件消费者
MiddlewareConsumer
是一个helper类。它提供了几个内置的方法来管理中间件。所有这些都可以用流畅的样式简单地链接起来。forRoutes()
方法可以接受一个字符串、多个字符串、一个RouteInfo
对象、一个控制器类,甚至多个控制器类。在大多数情况下,您可能只是传递一个以逗号分隔的控制器列表。下面是单个控制器的示例:
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);
}
}
[!hint]
apply()
方法可以接受单个中间件,也可以接受多个参数来指定多个中间件。
排除路由
有时,我们希望从应用中间件中排除某些路由。我们可以使用exclude()
方法轻松地排除某些路由。这个方法可以接受一个字符串、多个字符串或一个RouteInfo
对象来标识要排除的路由,如下所示:
consumer
.apply(LoggerMiddleware)
.exclude(
{ path: 'cats', method: RequestMethod.GET },
{ path: 'cats', method: RequestMethod.POST },
'cats/(.*)',
)
.forRoutes(CatsController);
[!hint]
exclude()方法使用path-to-regexp包支持通配符参数。
在上面的例子中,LoggerMiddleware
将被绑定到Catscontroller
内部定义的所有路由,除了传递给exclude()
方法的三个路由。
功能性中间件
我们一直在使用的LoggerMiddleware类非常简单。它没有成员,没有额外的方法,也没有依赖关系。为什么我们不能在一个简单的函数而不是在类中定义它呢?事实上,我们可以。这种类型的中间件称为函数中间件。让我们将logger middleware
从基于类的转换为函数中间件来说明它们的区别:
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);
[!hint]
当您的中间件不需要任何依赖项时,请考虑使用更简单的函数中间件替代方案。
多中间件
如上所述,为了绑定顺序执行的多个中间件,只需在apply()方法中提供一个逗号分隔的列表:
consumer.apply(cors(), helmet(), logger).forRoutes(CatsController);
全局中间件
如果我们想一次将中间件绑定到每个注册路由,我们可以使用INestApplication
实例提供的use()
方法:
const app = await NestFactory.create(AppModule);
app.use(logger);
await app.listen(3000);
[!hint]
在全局中间件中访问DI
(Dependency Injection)容器是不可能的。你可以在使用app.use()
时使用函数中间件。或者,你可以使用类中间件,并在AppModule
(或任何其他模块)中使用.forRoutes('*')
来消费它。