Nest探索(四)Nest 中间件

249 阅读3分钟

写原创好文,瓜分万元现金大奖 💰 |3月金石计划

前言

说起中间件,很容易想到Koa 的中间件,也就是洋葱模型。Koa的洋葱模型是通过koa-compose库实现的,该库将多个中间件函数组合成一个组合函数,并按照指定的顺序执行每个中间件,并等待每个中间件的Promise解决后继续执行下一个中间件,直到所有中间件执行完毕。这种机制使得Koa的中间件执行顺序符合洋葱模型的特性。

那么,在 Nest 中是否也有中间件的概念呢?答案是肯定的。

根据官方文档,Nest 中间件是在路由处理函数之前调用的一个函数。

Middleware is a function which is called before the route handler. Middleware functions have access to the request and response objects, and the next() middleware function in the application’s request-response cycle. The next middleware function is commonly denoted by a variable named next.

新建一个中间件

运行命令nest g middleware aaa --no-spec --flat,可以看到src目录下生成了 aaa.middleware.ts 文件。我们在 next() 上下添加两行打印。

// src\aaa.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response } from 'express';
​
@Injectable()
export class AaaMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: () => void) {
    console.log('111');
    next();
    console.log('112');
  }
}

同时,在module文件中引入该中间件。其中.forRoutes('*')表示匹配所有路由。

// src\app.module.ts
import { Module, NestModule, MiddlewareConsumer, } from '@nestjs/common';
// ...
import { AaaMiddleware } from './aaa.middleware';
​
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(AaaMiddleware)
      .forRoutes('*');
  }
}

最后,运行项目pnpm start:dev,并访问 http://localhost:3000/, 可以看到打印了 111 和 112 。说明中间件生效了。

路由匹配规则

在上面的代码中,我们设置了匹配任意路由:.forRoutes('*')

此外,我们可以设置精确的路由匹配规则,比如在下面的例子中,只有 /app, /app1 等路由可以匹配,而 / 等路由不匹配。

export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(AaaMiddleware)
      .forRoutes({
        path: 'app*',
        method: RequestMethod.GET,
      });
  }
}

还有,可以设置排除路由,app 路由的get请求和post请求不匹配,app/* 的路由都不匹配。

export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(AaaMiddleware)
      .exclude(
        { path: 'app', method: RequestMethod.GET },
        { path: 'app', method: RequestMethod.POST },
        'app/(.*)',
      )
      .forRoutes('*');
  }
}

类中间件

在 Nest 中,为什么要把 middleware 设置成 class 呢?这是为了依赖注入。比如,我们可以通过 @Inject(AppService) 将 AppService 注入到中间件 AaaMiddleware 里,这样就可以使用 AppService 中的方法了。

import { Injectable, Inject, NestMiddleware } from '@nestjs/common';
import { Request, Response } from 'express';
import { AppService } from './app.service';
​
@Injectable()
export class AaaMiddleware implements NestMiddleware {
  @Inject(AppService)
  private readonly appService: AppService;
​
  use(req: Request, res: Response, next: () => void) {
    console.log('111');
    console.log(this.appService.getHello());
    next();
    console.log('112');
  }
}

当我们访问 /app 路由时,可以看到打印了:

111
Hello World!
112

函数中间件

如果中间件不需要依赖注入,我们可以写成函数的方式。

// src\aaa.middleware.ts
import { Request, Response } from 'express';
​
export function AaaMiddleware(req: Request, res: Response, next: () => void) {
  console.log('111');
  next();
  console.log('112');
}

中间件的使用

  • 多个中间件

如果我们需要按顺序地使用多个中间件,那么在apply()方法中依次引入,用逗号隔开即可:

consumer
  .apply(cors(), helmet(), logger)
  .forRoutes(CatsController);
  • 全局中间件

全局中间件只需要在 main.ts 中注册即可,而且只支持函数中间件。

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

如果是类中间件,相应地设置.forRoutes('*')也可以实现。

后记

Nest 也有中间件,带有 request、response、next 参数。对于中间件,我们可以实现依赖注入,还可以指定中间件作用于哪些路由等等。

参考