Nest.js 系列——自定义中间件

前言

在前面的文章中简单介绍了一下中间件是什么,并简单使用了下中间件,这里就看下如何自定义一个中间件以及需要注意的地方。

先用 cli 新建一个中间件文件

shell 复制代码
nest g middleware aaa --no-spec --flat

执行这个命令会生成一个中间件文件aaa.middleware.ts

ts 复制代码
import { Injectable, NestMiddleware } from '@nestjs/common'

@Injectable()
export class AaaMiddleware implements NestMiddleware {
  use(req: any, res: any, next: () => void) {
    next()
  }
}

因为不知道你的底层框架使用的是 express 还是 fastify,所以这里的参数类型是 any,你可以根据你使用的框架手动去改。一般来说使用的都是 express

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) {
    next()
  }
}

中间件中使用了@Injectable装饰器,由此看出中间件也类似提供一个服务的提供者。可以在 next()方法的前后加一些处理逻辑,这个会在中间件的开始和结束时候触发。next 方法会把控制权交给下一个中间件。

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('brefore')
    next()
    console.log('after')
  }
}

使用中间件

上面简单定义了一个中间件, 接下来我们在app.module.ts中使用它。

ts 复制代码
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'
import { AppController } from './app.controller'
import { AppService } from './app.service'
import { AaaMiddleware } from './aaa.middleware'

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService]
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(AaaMiddleware).forRoutes('*')
  }
}

启动项目看下执行效果

这里是对所有路由启用,你也可以指定精确一点的路由。先定义几个路由作为区分

ts 复制代码
import { Controller, Get } from '@nestjs/common'
import { AppService } from './app.service'

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get('hello')
  getHello(): string {
    console.log('hello')
    return this.appService.getHello()
  }
  @Get('hello2')
  getHello2(): string {
    console.log('hello2')

    return this.appService.getHello()
  }

  @Get('world1')
  getWorld1(): string {
    console.log('world1')

    return this.appService.getHello()
  }

  @Get('world2')
  getWorld2(): string {
    console.log('world2')

    return this.appService.getHello()
  }
}

app.module.ts 中指定路由

ts 复制代码
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'
import { AppController } from './app.controller'
import { AppService } from './app.service'
import { AaaMiddleware } from './aaa.middleware'

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService]
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(AaaMiddleware).forRoutes({
      path: 'hello*',
      method: RequestMethod.GET
    })
    consumer.apply(AaaMiddleware).forRoutes({
      path: 'world*',
      method: RequestMethod.GET
    })
  }
}

浏览器访问对应的路由,看下执行结果。

这是中间件的简单使用,但是这样的使用方式和 express 差不多,但是 nest 中装饰器可以是类 class,因为如果是类的话,就可以使用依赖注入来注入服务进行一些逻辑使用。

ts 复制代码
import { AppService } from './app.service'
import { Inject, Injectable, NestMiddleware } from '@nestjs/common'
import { Request, Response } from 'express'

@Injectable()
export class AaaMiddleware implements NestMiddleware {
  @Inject(AppService)
  private readonly appService: AppService

  use(req: Request, res: Response, next: () => void) {
    console.log('brefore')
    console.log('-------' + this.appService.getHello())
    next()
    console.log('after')
  }
}

这里也可以使用构造函数注入,前面有提到过,这里的提供者注入有很多方式,忘记的可以去看看

ts 复制代码
import { AppService } from './app.service'
import { Inject, Injectable, NestMiddleware } from '@nestjs/common'
import { Request, Response } from 'express'

@Injectable()
export class AaaMiddleware implements NestMiddleware {
  constructor(private readonly appService: AppService) {}

  use(req: Request, res: Response, next: () => void) {
    console.log('brefore')
    console.log('-------' + this.appService.getHello())
    next()
    console.log('after')
  }
}

访问下路由看下控制台输出

如果全局使用中间件可以使用app.use()方法,这个就和 express 差不多了。

中间件和拦截器

从表现上看,感觉中间件和拦截器的处理时机是差不多的。但是它俩还有一些细微的区别的。interceptor 是能从 ExecutionContext 里拿到目标 classhandler,进而通过 reflector 拿到它的 metadata 等信息的,这些 middleware 就不可以。

再就是 interceptor 里是可以用 rxjs 的操作符来组织响应处理流程的。middleware 里也不可以。

它们都是 Nest AOP 思想的实现,但是 interceptor 更适合处理与具体业务相关的逻辑,而 middleware适合更通用的处理逻辑。

总结

本文简单实现了自定义中间件的编写和使用,当然在业务中不会有这么简单的中间件。而且也对比了和拦截器的区别,希望对你有帮助。

相关推荐
徐_三岁11 分钟前
127.0.0.1 和 localhost 有什么区别?
前端
韩立学长19 分钟前
基于Springboot流浪动物救助系统o8g44kwc(程序、源码、数据库、调试部署方案及开发环境)系统界面展示及获取方式置于文档末尾,可供参考。
数据库·spring boot·后端
沛沛老爹24 分钟前
Web开发者转型AI:Agent Skills版本控制与管理实战——从Git到AI技能仓库
java·前端·人工智能·git·架构·rag
我命由我1234527 分钟前
充血模型与贫血模型
java·服务器·后端·学习·架构·java-ee·系统架构
yyt36304584129 分钟前
TypeScript { [key: string]: unknown } 索引签名写法和 Record 替代
前端·javascript·vue.js·typescript·ecmascript·es6
小镇学者1 小时前
【other】Goofy Node
后端
揽昕1 小时前
判断对象是否含有某个属性
开发语言·前端·javascript
颜淡慕潇1 小时前
动态代理赋能:高效爬取沃尔玛海量商品信息与AI分析实战
人工智能·后端
前端小趴菜052 小时前
TypeScript
前端·typescript
getapi2 小时前
在宝塔面板中部署 Vue 项目打包后的 dist 文件作为前端
前端·javascript·vue.js