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适合更通用的处理逻辑。

总结

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

相关推荐
葫芦和十三8 小时前
图解 MongoDB 23|两地三中心:跨可用区部署怎么扛机房故障
后端·mongodb·agent
勇哥java实战分享10 小时前
PaddleOCR 太慢?我换成 RapidOCR 后,速度直接起飞
后端
kyriewen11 小时前
Anthropic 估值逼近万亿美元,Claude Sonnet 5 + Claude Science 一天两连发
前端·ai编程·claude
小徐_233312 小时前
Wot UI 2.2.0 发布:Button 新增 subtle,VideoPreview 预览体验继续增强
前端·微信小程序·uni-app
苏三说技术14 小时前
LangChain4j 和 LangGraph4j,哪个更好?
后端
天蓝色的鱼鱼15 小时前
关于 CSS 你可能不知道的属性,但关键时刻很有用
前端·css
ServBay15 小时前
7 个AI开发中真正用得上的 MCP Server,配合Claude Code食用效果更佳
后端·claude·mcp
泯泷15 小时前
第 2 篇:设计第一套字节码:Opcode、Instruction 与 Constant Pool
前端·javascript·安全
妙码生花15 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(十五):优化细节、网络请求封装
前端·后端·ai编程