中间件函数可以访问请求和响应对象,以及应用程序请求响应周期中的 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
| 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
| 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
| 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
| Request... { name: 'cat' }
|
那么我们能不能在中间件对请求数据进行修改呢?答案是可以。
1 2 3 4 5 6 7 8 9 10 11 12
| 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
| 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
| 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调试接口,可以看到接口返回内容变成了:
看到这里,是不是有人想到了通过中间件来统一返回内容的格式呢?
实不相瞒,我最开始也是想的通过中间件来实现在nestjs中的统一格式响应,但是这个方向是错误的哦
在Nestjs中:
对于错误请求,可以使用过滤器来统一格式返回
对于成功请求,可以使用拦截器来统一格式返回
关于过滤器和拦截器的内容,放到后边讲呗。
函数中间件
前边说过中间件还有函数形式呢,我们最初的那个logger中间件变成函数式是什么样子呢?
1 2 3 4 5
| export function logger(req, res, next) { console.log(`Request...`); next(); };
|
对,没看错,就是这么简单。中间件没有任何依赖关系时,我们可以考虑使用函数式中间件。
在这里顺便说一下全局中间件吧
想一次性将中间件绑定到每个注册路由,我们可以使用由INestApplication实例提供的 use()方法,在main.ts中注册。
1 2 3 4
| const app = await NestFactory.create(AppModule); app.use(logger); await app.listen(3000);
|
特别注意
在全局中间件访问DI容器是不可能的,所以全局中间件需要使用函数式中间件
全局中间件也可以指定多个哦