应用中间件
@Module()
装饰器中不能直接设置中间件。- 使用模块类的
configure()
方法设置中间件。 - 包含中间件的模块必须实现
NestModule
接口。
使用模块类的 configure()
方法设置中间件
typescript
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()
方法主体内 await
完成异步操作)。
例如,你可能需要从数据库或配置服务中异步加载一些配置信息,然后根据这些信息来动态地决定应用哪些中间件。
typescript
// 引入依赖和LoggerMiddleware
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方法为异步方法
async configure(consumer: MiddlewareConsumer) {
// 假设的异步配置加载函数
const asyncConfigLoader = (): Promise<any> => {
// 使用Promise来模拟异步操作,例如从远程服务获取配置
return new Promise(resolve => {
setTimeout(() => {
console.log('Config loaded async.');
resolve();
}, 1000); // 延迟1秒模拟异步加载
});
};
// 等待异步配置加载完成
await asyncConfigLoader();
// 设置中间件
consumer
.apply(LoggerMiddleware)
.forRoutes({ path: 'cats', method: RequestMethod.GET });
}
}
虽然 configure
方法可以是异步的,但NestJS中间件本身必须是同步的,因为它们是直接与Node.js的HTTP服务器集成的,而HTTP服务器的中间件机制是同步的。因此,任何异步操作都应该在设置中间件之前完成。
通过提供异步支持,NestJS使得在应用程序启动前进行一些异步操作(如数据库连接、配置加载等)成为可能,从而提高了应用程序的灵活性和可配置性。
NestJS中body-parser
中间件的自动注册
警告: 当使用 express 适配器时,NestJS 应用将默认从 body-parser 包中注册 json 和 urlencoded。这意味着如果你想通过 MiddlewareConsumer 自定义该中间件,则需要在使用 NestFactory.create() 创建应用时通过将 bodyParser 标志设置为 false 来关闭全局中间件。
Express适配器是NestJS默认使用的HTTP平台适配器,默认注册了body-parser
中间件。
所以,如果你在使用Express作为你的HTTP服务器,并且想要自定义body-parser
中间件的行为(比如设置不同的解析选项),那么你需要关闭NestJS的自动注册行为,以免出现冲突。
javascript
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as bodyParser from 'body-parser';
async function bootstrap() {
// 创建应用时设置bodyParser为false禁用自动注册
const app = await NestFactory.create(AppModule, { bodyParser: false });
// 手动注册body-parser中间件并自定义配置
app.use(bodyParser.json({ limit: '50mb' })); // 设置JSON请求体的大小限制
app.use(bodyParser.urlencoded({ extended: true, limit: '50mb' })); // 设置URL编码请求体的大小限制
await app.listen(3000);
}
bootstrap();
通过禁用默认注册,你可以完全控制中间件的实现细节。
路由通配符
php
forRoutes({ path: 'ab*cd', method: RequestMethod.ALL });
-
*
(星号): 匹配任意数量的任意字符(包括零个)。{ path: 'ab*cd', method: RequestMethod.ALL }
将匹配abcd
、ab_cd
、abecd
、abXYZcd
等。
-
?
(问号): 匹配前面的字符零次或一次。{ path: 'ab?cd', method: RequestMethod.ALL }
将匹配abcd
或abxcd
,其中x
是任意单一字符。
-
+
(加号): 匹配前面的字符一次或多次。{ path: 'ab+cd', method: RequestMethod.ALL }
将匹配abcd
或abbcd
、abbbcd
等(b
出现一次或多次)。
-
()
(括号): 将几个字符组合在一起,当作一个单元进行匹配。{ path: 'ab(cd)?e', method: RequestMethod.ALL }
将匹配abe
或abcde
(cd
出现零次或一次)。
-
-
(连字符)和.
(点): 在基于字符串的路径中,这些字符没有特殊含义,会被逐字解释。{ path: 'ab-cd.ef', method: RequestMethod.ALL }
仅匹配ab-cd.ef
。
中间件消费者 MiddlewareConsumer
scss
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes(CatsController);
}
MiddlewareConsumer 内置方法
apply(...middlewares: any[])
: 这个方法用来将一个或多个中间件应用到指定的路由上。它接受一个参数列表,每个参数都是一个中间件,可以是一个函数或者一个实现了NestMiddleware
接口的类。
scss
//单个中间件
consumer.apply(LoggerMiddleware).forRoutes(CatsController);
//多个中间件
consumer.apply(cors(), helmet(), logger).forRoutes(CatsController);
forRoutes(...routes: (string | RouteInfo | Type<any>)[])
: 这个方法用来指定中间件应用到哪些路由。它可以接受字符串、RouteInfo
对象或者控制器类。可以是单个路由或多个路由的数组。
arduino
// 使用单个字符串
consumer.apply(LoggerMiddleware).forRoutes('/cats');
//使用多个字符串
consumer.apply(LoggerMiddleware).forRoutes('/cats', '/dogs', '/birds');
// 使用 RouteInfo 对象
consumer.apply(LoggerMiddleware).forRoutes({ path: '/cats', method: RequestMethod.GET });
// 使用单个控制器类
consumer.apply(LoggerMiddleware).forRoutes(CatsController);
// 使用多个控制器类
consumer.apply(LoggerMiddleware).forRoutes(CatsController, DogsController, BirdsController);
//混合使用
consumer
.apply(LoggerMiddleware)
.forRoutes(
{ path: '/cats', method: RequestMethod.GET },
{ path: '/cats/:id', method: RequestMethod.PUT },
DogsController,
BirdsController
);
exclude(...routes: (string | RouteInfo)[])
: 这个方法用来排除一些路由,使得通过apply
指定的中间件不会应用到这些路由上。它也可以接受字符串或RouteInfo
对象。
php
consumer.apply(LoggerMiddleware).exclude({ path: '/cats/login', method: RequestMethod.POST }).forRoutes(CatsController);
功能中间件 函数式中间件
没有成员,没有额外的方法,也没有依赖的中间件可以用一个简单的函数来定义,而不是使用类。
这种类型的中间件称为函数式中间件。
让我们将日志器中间件从基于类的中间件转换为函数式中间件来说明差异:
vbscript
import { Request, Response, NextFunction } from 'express';
export function logger(req: Request, res: Response, next: NextFunction) {
console.log(`Request...`);
next();
};
在Module中使用:
scss
consumer
.apply(logger)
.forRoutes(CatsController);
为什么是apply(logger),而不是apply(logger())
当你使用 apply
方法来设置中间件时,你应该传递中间件函数本身,而不是调用它(即传递函数的引用,而不是函数调用的结果)。这是因为NestJS需要对中间件进行自己的调用逻辑,确保在适当的时间执行中间件函数,并且能够传递正确的参数(req
, res
, next
)
apply(logger)
表示你正在注册 logger
函数作为中间件。此时你不需要调用 logger
函数(即不使用 logger()
),因为你不是在执行它,而是在告诉 NestJS "这是一个中间件函数,请在请求处理流程中适当的位置调用它"。
如果你写成 apply(logger())
,那么你实际上是在传递 logger
函数执行的结果,而不是函数本身。因为 logger
函数预期要接受 req
, res
, next
作为参数,而在你调用 logger()
时并没有提供这些参数,所以它会导致错误。
调用一个返回中间件函数的工厂函数 apply(cors())
而在官网文档介绍多个中间件时有个示例代码:
scss
consumer.apply(cors(), helmet(), logger).forRoutes(CatsController);
在大多数情况下,当你在NestJS中通过 consumer.apply()
注册中间件时,你应该传递中间件函数的引用,而不是执行中间件函数。然而,有些中间件库,如 cors
和 helmet
,提供了工厂函数,当调用这些函数时,它们会返回一个新的中间件函数。
因此,当你看到 cors()
或 helmet()
这样的调用时,它们实际上是在调用一个返回中间件函数的工厂函数 。这些工厂函数通常允许你传递配置对象来自定义中间件的行为。
javascript
// cors和helmet都是接受可选配置的工厂函数
import * as cors from 'cors';
import * as helmet from 'helmet';
consumer.apply(cors(), helmet(), logger).forRoutes(CatsController);
cors()
调用cors
工厂函数并返回一个新的中间件函数,该函数会处理跨源资源共享(CORS)。helmet()
调用helmet
工厂函数并返回一个新的中间件函数,用于设置各种HTTP头以帮助保护你的应用免受一些众所周知的Web漏洞的影响。logger
是直接传递的中间件函数,因为它不需要通过工厂函数来创建。
这就是为什么在NestJS文档中,你可能会看到 cors()
和 helmet()
被作为函数调用传递给 apply()
的原因。这些工厂函数最终返回的是一个可以直接用作中间件的函数。而 logger
作为一个已经定义好的中间件函数,只需直接传递它的引用即可。
全局中间件
main.ts 中使用use设置
ini
const app = await NestFactory.create(AppModule);
app.use(logger);