Nestjs的中间件使用

中间件函数可以访问请求和响应对象,以及应用程序请求响应周期中的 next() 中间件函数。

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

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

搭建简单中间件

Nest中间件的要求:

  • 函数或具有 @Injectable() 装饰器的类
  • 类应该实现 NestMiddleware 接口, 而函数没有特殊的要求

比如我们使用类的方式实现一个logger中间件logger.middleware.ts,在请求之前打印Request...

NestMiddleware接口要求我们实现use方法,其类型定义为

1
2
3
export interface NestMiddleware<TRequest = any, TResponse = any> {
use(req: TRequest, res: TResponse, next: (error?: Error | any) => void): any;
}
1
2
3
4
5
6
7
8
9
10
11
// 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();
}
}

中间件不能在 @Module() 装饰器中列出。我们必须使用模块类的 configure() 方法来设置它们。包含中间件的模块必须实现 NestModule 接口。

现在我们在app.module.ts设置中间件

为了方便后边的演示,我们继续使用之前创建的cats模块。演示过程我们只会传递一个name字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 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');
}
}

在这里,我们在配置中间件时将包含路由路径的对象和请求方法传递给forRoutes()方法。

中间件还可以限制请求方法

1
forRoutes({ path: 'cats', method: RequestMethod.GET })

直接把一个控制器扔进去也是可以的

1
forRoutes(CatsController)

也可以同时使用多个中间件,不论是自定义中间件还是第三方中间件:

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

测试中间件

我们对logger.middleware.ts进行一些修改,看一下在中间件中我们能获得什么

1
2
3
4
5
6
7
8
9
10
11
12
// 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...', req.body)
next()
}
}

此时我们通过postman调试接口:

1
2
3
4
// localhost:3000/nest/cats/create
{
"name": "cat"
}

可以看到控制台打印了如下内容:

1
Request... { name: 'cat' }

那么我们能不能在中间件对请求数据进行修改呢?答案是可以。

1
2
3
4
5
6
7
8
9
10
11
12
// 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...', req.body)
+ req.body.name = 'named by middleware'
next()
}
}

cats.controller.ts中,我们也打印一下请求体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// cats.controller.ts
import { Controller, Get, Post, Body, Param } from '@nestjs/common'
import { CatsService } from './cats.service'
import { CreateCatDto } from './dto/create-cat.dto'
import { UpdateCatDto } from './dto/update-cat.dto'

@Controller('cats')
export class CatsController {
constructor(private readonly catsService: CatsService) {}

@Post('create')
createCat(@Body() createCatDto: CreateCatDto) {
console.log('controller:', createCatDto)
return this.catsService.createCat(createCatDto)
}
}

再次通过postman调试接口,可以在控制台看到如下内容:

1
controller: { name: 'named by middleware' }

前边说到中间件可以对请求和响应对象进行更改,现在我们已经看到了对请求对象的修改,那么响应对象呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
// 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...', req.body)
+ req.body.name = 'named by middleware'
next()
res.send('cats接口返回的内容')
}
}

再次通过postman调试接口,可以看到接口返回内容变成了:

1
cats接口返回的内容

看到这里,是不是有人想到了通过中间件来统一返回内容的格式呢?

实不相瞒,我最开始也是想的通过中间件来实现在nestjs中的统一格式响应,但是这个方向是错误的哦

在Nestjs中:

对于错误请求,可以使用过滤器来统一格式返回

对于成功请求,可以使用拦截器来统一格式返回

关于过滤器和拦截器的内容,放到后边讲呗。

函数中间件

前边说过中间件还有函数形式呢,我们最初的那个logger中间件变成函数式是什么样子呢?

1
2
3
4
5
// logger.middleware.ts
export function logger(req, res, next) {
console.log(`Request...`);
next();
};

对,没看错,就是这么简单。中间件没有任何依赖关系时,我们可以考虑使用函数式中间件。

在这里顺便说一下全局中间件

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

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

特别注意

在全局中间件访问DI容器是不可能的,所以全局中间件需要使用函数式中间件

全局中间件也可以指定多个哦

作者

胡兆磊

发布于

2023-02-19

更新于

2023-02-19

许可协议