Nest 中间件

回顾 express, koa 中间件
在学习 nest 中的中间件之前,先来回顾一下 express 和 koa 中的中间件。
针对上面两个框架,对中间件的理解就是:
给创建的 app 传入一个回调函数,传入的回调函数就被称之为中间件(middleware)
。
中间件就是一个回调函数,express 的回调函数接收三个参数(request, response, next), koa 的回调函数接收两个参数(ctx, next)。
express 中注册中间件几种方式
// 普通中间件注册:app.use(fn)
app.use((req, res, next) => {
console.log('中间件');
})
// 路径匹配的中间件: app.use(path, fn)
app.use('/home', (req, res, next) => {
console.log('第一个中间件');
})
// 路径+请求方法匹配的中间件:app.method(path, fn)
app.get('/home', (req, res, next) => {
console.log('第一个中间件');
})
// 注册多个中间件:app.use(fn1, fn2, fn3, ...)
app.get('/home', (req, res, next) => {
console.log('第一个中间件');
}, (req, res, next) => {
console.log('第二个中间件')
}, (req, res, next) => {
console.log('第三个中间件')
})
koa 注册中间件(轻量级,只有最普通的注册中间件)
// 注册中间件:app.use(fn)
app.use((ctx, next) => {
console.log('中间件')
})
当存在多个中间件时,必须调用 next 方法,才能走到下一个中间件。
express 和 koa 的中间件都是采用的洋葱模型,同步代码一层一层的向下执行,遇到异步代码,又一层一层的向上执行,它们之间无差异。唯一有差异的地方就是 next 函数内部实现不一样,当处理某种场景逻辑时,两者会有不同的写法。
- express 中的 next 函数,没有返回值,但是能接受参数(可以进行错误统一处理)
- koa 中的 next 函数,返回一个 promise(当遇到异步代码时,可以 await next(),等待异步代码执行完成,而 express 是做不到的),但是不能接口参数。
express 和 koa 的中间件,你了解了吗?
是什么
上面了解完 express 和 koa 的中间件之后,知道中间件是一个函数。
其实 nest 的中间件(middleware)也是一个函数(类也是函数), 一个在路由处理程序之前调用的函数。
什么是路由处理程序?
当一个 HTTP 请求到达服务器时,服务器会检查请求的 URL 和 HTTP 方法,并将其映射到一个预定义的函数上,那么这个函数就是路由处理程序。更简单的描述,也就是 controller 上的一个方法。
中间件可以对请求对象或响应对象进行操作或者进行一些预处理工作。
nest 中间件默认情况下与 express 中间件等效,也具有 express 的中间件特性:
- 执行任何代码。
- 对请求和响应对象进行更改。
- 结束请求-响应周期。
- 调用堆栈中的下一个中间件函数。
- 如果当前中间件函数未结束请求-响应周期,则必须调用
next()
将控制权传递给下一个中间件函数。否则,请求将被搁置。
但 nest 中间也具有它独特的特性:
- 支持依赖注入
- 支持模块化,可以在全局或者局部注册,而 express 通常是在应用层面上注册。
总的来说,nest 中间件在功能上与 express 中间件等价,但 nest 通过提供额外的抽象和功能,使得中间件的使用更加强大和灵活。也需要注意的是,nest 也可以切换成别的 http 请求,那么这时候的中间件与 express 的中间件有所不同。
借用神光的一个 AOP 架构图,middleware 是最先执行的。

何时使用
中间件在 nest 中扮演中重要的角色,它可以实现:
- 身份验证:检查用户是否已经登录,以及是否拥有访问特定资源的权限。
- 日志记录:记录请求的详细信息,如时间、IP 地址、请求方法、URL、状态码等,用于监控和调试。
- 请求修改:在将请求传递给路由处理程序之前,修改请求头、请求体或查询参数。
- 响应修改:在发送响应给客户端之前,添加或修改响应头、响应体或状态码。
- 错误处理:捕获并处理请求处理过程中发生的错误,统一错误响应格式。
- 跨域资源共享(CORS) :处理跨域请求,允许来自不同源的请求访问服务器资源。
- API 版本控制:根据请求的版本号,将请求路由到不同的 API 版本。
- ......
可以借助中间件可以实现很多功能,但常见的功能表现在鉴权、记录日志,错误处理,请求和响应对象的修改。
怎么用
nest 的中间件可以定义为两种形式:类和函数(其实本质上都是函数)。
在 nest 项目中创建一个中间件(使用 nest 指令)
nest g middleware test --no-spec --flat
就会生成代码
会发现请求对象和响应对象的类型都为 any,因为这时候并不知道你用的 express 还是 fastify,所以 request、response 是 any。如果想使用 express , 手动指定类型(nest 内部会自动推断),并在前后加上一个打印日志。
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response } from 'express';
@Injectable()
export class TestMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: () => void) {
console.log('中间件前面逻辑');
next();
console.log('中间件后面逻辑');
}
}
把 testMiddleware
中间件应用在 nest 程序中
当发送一个 http 请求时,看看打印日志,发现中间件的逻辑都执行了
来具体看看注册中间件的语法呢
注册中间件的模块需要实现NestModule
接口,也就是使用 configure()
方法来进行注册中间件
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer): any {
consumer.apply(xxxx).forRoutes(yyy)
}
}
MiddlewareConsumer
是一个辅助类,它提供了几种内置方法来管理中间件,所有这些方法都可以链式调用链接在一起。
apply()
方法可以接受单个中间件,或多个参数来指定多个中间件。
forRoutes()
方法可以接受一个字符串、多个字符串、一个 RouteInfo
对象、一个控制器类,甚至是多个控制器类。
那么中间件利用 RouteInfo
就可以实现精确的指定路由(限定路由名称、请求方法)
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer): any {
consumer
.apply(TestMiddleware)
.forRoutes({ path: '/app', method: RequestMethod.GET }); // 精确 /app GER 请求
consumer
.apply(TestMiddleware)
.forRoutes({ path: 'user*', method: RequestMethod.GET }); // 匹配 user 开头的所有 GET 请求
consumer
.apply(TestMiddleware)
.forRoutes({ path: '/auth', method: RequestMethod.ALL }); // 匹配 /auth 所有类型的请求方式
}
}
Nest 路由匹配是支持路由通配符。但是
fastify
包使用了最新版本的path-to-regexp
包,不再支持通配符*
。
也可以注册中间件特指控制器 Controller
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer): any {
consumer
.apply(TestMiddleware)
.forRoutes(AppController);
}
}
使用 exclude
甚至还可以排除一些路由。该方法可以接受单个字符串、多个字符串或 RouteInfo
对象,用于标识要排除的路由。
consumer
.apply(TestMiddleware)
.exclude(
{ path: 'auth', method: RequestMethod.GET },
{ path: 'user*', method: RequestMethod.POST },
'cats/(.*)',
.forRoutes(AppController);
相对于 express 中间件来说,nest 的中间件还是比较强大的。
nest 中间件可以设计为两种形式:类和函数,那么该如何选择呢?依赖注入
如果不需要注入依赖,那可以写函数形式的 middleware,没啥区别。
如果需要注入依赖,那就写 class 形式的 middleware,可以用 nest 的依赖注入能力。
上面使用了属性注入,当然也可以使用构造函数注入。
当再次发送一个请求时,看打印日志:
看到中间件成功调用了 AppService 中的方法。
也来看看函数形式的中间件是怎么写呢
import { Request, Response, NextFunction } from 'express';
export function TestMiddleware(
req: Request,
res: Response,
next: NextFunction,
) {
console.log('中间件前面逻辑');
next();
console.log('中间件后面逻辑');
}
注入之后,也是能够正常使用的。
但是针对函数中间件,也可以在 main.ts 中进行注册,但是不推荐,不能进行任何配置(跟 express 一样)
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(testMiddleware); // 注册全局中间件
await app.listen(3000);
}
bootstrap();
中间件大致就这些知识了。
总结
- 知道 koa,express,nest 的中间件的本质是什么:中间件就是函数。
- nest 的中间件使用场景大部分就是鉴权和统一逻辑处理(比如说日志,修改请求响应对象等)
- nest 中定义中间件可以是类也可以是函数,如果不涉及到依赖注册,函数可能更加简洁,反之,就需要使用类的形式
- nest 在注入中间件时,可以针对所有路由,也可以精确指定某个路由,某种方法,甚至某种控制器;也支持排除一些路由
- 类形式的依赖注入也是支持属性注入和构造函数注入的
转载自:https://juejin.cn/post/7380357384775942194