小白NestJS官网文档祥读之中间件

应用中间件

  • @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 });
  1. *(星号): 匹配任意数量的任意字符(包括零个)。

    • { path: 'ab*cd', method: RequestMethod.ALL } 将匹配 abcdab_cdabecdabXYZcd 等。
  2. ?(问号): 匹配前面的字符零次或一次。

    • { path: 'ab?cd', method: RequestMethod.ALL } 将匹配 abcdabxcd,其中 x 是任意单一字符。
  3. +(加号): 匹配前面的字符一次或多次。

    • { path: 'ab+cd', method: RequestMethod.ALL } 将匹配 abcdabbcdabbbcd 等(b 出现一次或多次)。
  4. ()(括号): 将几个字符组合在一起,当作一个单元进行匹配。

    • { path: 'ab(cd)?e', method: RequestMethod.ALL } 将匹配 abeabcdecd 出现零次或一次)。
  5. -(连字符)和 .(点): 在基于字符串的路径中,这些字符没有特殊含义,会被逐字解释。

    • { path: 'ab-cd.ef', method: RequestMethod.ALL } 仅匹配 ab-cd.ef

中间件消费者 MiddlewareConsumer

scss 复制代码
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes(CatsController);
  }

MiddlewareConsumer 内置方法

  1. apply(...middlewares: any[]): 这个方法用来将一个或多个中间件应用到指定的路由上。它接受一个参数列表,每个参数都是一个中间件,可以是一个函数或者一个实现了 NestMiddleware 接口的类。
scss 复制代码
//单个中间件
consumer.apply(LoggerMiddleware).forRoutes(CatsController);
//多个中间件
consumer.apply(cors(), helmet(), logger).forRoutes(CatsController);
  1. 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
  );
  1. 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() 注册中间件时,你应该传递中间件函数的引用,而不是执行中间件函数。然而,有些中间件库,如 corshelmet,提供了工厂函数,当调用这些函数时,它们会返回一个新的中间件函数。

因此,当你看到 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);
相关推荐
在下千玦12 小时前
#管理Node.js的多个版本
node.js
你的人类朋友13 小时前
MQTT协议是用来做什么的?此协议常用的概念有哪些?
javascript·后端·node.js
还是鼠鼠16 小时前
Node.js中间件的5个注意事项
javascript·vscode·中间件·node.js·json·express
南通DXZ19 小时前
Win7下安装高版本node.js 16.3.0 以及webpack插件的构建
前端·webpack·node.js
你的人类朋友20 小时前
浅谈Object.prototype.hasOwnProperty.call(a, b)
javascript·后端·node.js
前端太佬20 小时前
暂时性死区(Temporal Dead Zone, TDZ)
前端·javascript·node.js
Mintopia20 小时前
Node.js 中 http.createServer API 详解
前端·javascript·node.js
你的人类朋友20 小时前
CommonJS模块化规范
javascript·后端·node.js
Mintopia2 天前
Node.js 中 fs.readFile API 的使用详解
前端·javascript·node.js
咖啡教室2 天前
nodejs开发后端服务详细学习笔记
后端·node.js