Nest —— Middleware

1,509 阅读7分钟

前言

       之前匆匆写了一篇关于Nest官文的翻译,然而写的时候比较着急,所以很多地方的翻译比较马虎,甚至直接丢进翻译器在丢出来....(有的时候我会被丢进翻译器之后被丢出来的译文吓到,原因是...译文比自己翻译的好/(ㄒoㄒ)/~~)但还是有很多部分是翻译器解决不了的,语句通顺固然优雅锦上添花,但一点小的错误却是致命的。
       现在手头比较悠闲,也打算重新修改一份比较优雅的中文文档。有人会问花这么多时间写这个东西,是不是真的有用。百度上面也有一些关于Nest的文档,完全也不会有人来看你写的翻译。我的感受是,可能这就是我的学习方式吧。其实平时阅读文档,大多数情况下都是脑子说会了,手说不会。一边翻译英文文档,一遍理解框架的含义,还有助于提高阅读英文文档的能力。手敲过一遍和眼睛看过一遍真的不太一样,而且这种方式会增加使用时的自信。之后打算将每一章节分开书写,最后再通过链接汇总到一篇Nest妲己大记中去。就算没有人看,自己想要翻阅文档的时候,也可以拿出来看看,还能改改。这是一种乐趣,就好像养成游戏一样。



正文 Middleware

中间件是一个在路由处理函数之前调用的函数。 中间件函数可以访问到请求和响应对象,next()方法可以访问应用的请求-响应周期。next中间件函数通常由以个叫做next的变量表示。

Nest中间件默认是等同于express的中间件。下面来自express官方文档的描述展示了中间件的能力。

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

  • 执行任意代码
  • 对请求、响应对象作出修改
  • 结束请求-响应周期
  • 在调用栈中next中间件函数
  • 如果当前中间件函数没有结束请求-响应周期,那么他必须执行next()方法来将控制权交于下一个中间件。否则请求将会被挂起。

你可以通过使用装饰器 @Injectable 的函数或者类来实现一个自定义的中间件。如果是使用类就要实现NestMiddleware接口,函数不需要特殊的需求。

// logger.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common'
import { Request, Response } from 'express' 

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: Function) {
    console.log('Request')
    next()
  }
}



Dependency injection 依赖注入

Nest中间件完全支持依赖注入。就像是providers,controllers一样。他们能注入在同一个模块中可以使用的依赖。通常来说在构造器中实现。



Applying Middleware 应用中间件

装饰器 @Module() 中没有地方放中间件了,所以我们在模块的类中使用 configure() 方法来设置他们。包含了中间件的模块需要实现NestModule接口。

// 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')
  }
}

在上面的例子中,我们为先前定义的 CatsController中的 /cats 路由处理函数设置了 LoggerMiddleware 中间件。我们可能也需要将中间件限制到一个特定的请求方法,通过在配置中间件时传入一个包含路由路径path和请求方法method的对象给 forRoutes() 方法就可以。在下面的例子中,注意引入的 RequestMethod 类型枚举了所需的请求类型参数。

// app.module.ts
import { Module, NestModule, RequestMethod, 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({ path: 'cats', method: RequestMethod.GET })
  }
}

Hints
configure()方法可以使用async/await异步方法。(例如你可以await一个在configure()方法中实现的异步操作的结果)



Route wildcards 路由通配符

支持基于路由的表达式,例如*用作通配符,将匹配任意字符串的结合。

forRoutes({ path: 'ab*cd', method: RequestMethod.ALL })

路由的路径ab*cd将匹配到abcd,ab_cd,abecd等等。字符?,+,*()也可以再路径中使用,还有对应的正则的子集。连字符-和点.会以字面意思解析为字符串路径。



Middleware consumer 中间件消费者

MiddlewareConsumer 是一个辅助类。它提供了一些内置的方法来管理中间件。所有这些方法都可以链式调用。 forRoutes 方法可以接受单个字符,多个字符,一个 RouteInfo 对象,一个控制器的类,或者甚至是多个控制器的类。在大多数情况下你可能需要传一个用逗号隔开的控制器的列表。

// app.module.ts
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common'
import { LoggerMiddlware } from './common/middleware/logger.middleware'
import { CatsModule } from './cats/cats.module'
import { CatsController } from './cats/cats.controller.ts'

@Module({
  imports: [CatsModule].
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes(CatsController)
  }
}

Hints
apply()方法可以接受单个中间件,也可以接收多个参数来指定多个中间件。

我们经常希望在应用的中间件中排除某个特定的路由。当用类来定义中间件时(不建议使用函数式中间件),我们可以用 exclude() 方法排除某个特定的路由。这个方法接受一个或多个对象来确认要排除的路径和方法。

consumer
  .apply(LoggerMiddleware)
  .exclude(
    { path: 'cats', method: RequestMethod.GET },
    { path: 'cats', method: RequestMethod.POST },
  )
  .forRoutes(CatsController)

LoggerMiddleware 中间件将会被绑定到所有在 CatsController 中定义的路由,除了两个传入 exclude() 方法的路由。注意 exclude() 方法在函数式中间件中并不会工作。此外,这个方法不会排除更通用的路由(通配符)。如果你需要通用层级的控制,你应当将约束路由的逻辑放在中间件中。



Fuctional middleware 函数式中间件

我们使用的 LoggerMiddlware 类中间件非常简单。没有成员,没有额外的方法,没有依赖。为什么不将它定义外一个简单的函数而不是类?实际上是可以的。 这种类型的中间件叫做函数式中间件。

// logger.middleware.ts
export function logger(req, res, next) {
  console.log('Request...')
  next()
}

然后再 AppModule 中使用

// app.module.ts
consumer
  .apply(logger)
  .forRoutes(CatsController)

Hints
当你的中间件不需要任何依赖时再考虑使用更加简单的函数式中间件。



Multiple middleware 多个中间件

如上面提及,为了绑定多个按照顺序执行的中间件,只要再 apply() 方法中提供一个逗号分隔的列表即可。

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



Global middleware 全局中间件

如果我们想要立刻在每个注册的路由上绑定中间件,可以使用 INestApplication 接口提供的 use() 方法。

const app = await NestFactory.create(AppModule)
app.use(logger)
await app.listen(3000)





后记

原文地址: docs.nestjs.com/middleware


关于本文

  • 文章非复制黏贴,经浏览文档,以自己的理解进行,代码测试,手打书写。本篇为翻译+意译。
  • 用作记录自己曾经学习、思考过的问题的一种笔记。
  • 用作前端技术交流分享。
  • 阅读本文时欢迎随时质疑本文的准确性,将错误的地方告诉我。本人会积极修改,避免文章对读者的误导。

关于我

  • 是一只有梦想的肥柴。
  • 觉得算法、数据结构、函数式编程、js底层原理等十分有趣的小前端。
  • 志同道合的朋友请关注我,一起交流技术,在前端之路上共同成长。
  • 如对本人有任何意见建议尽管告诉我哦~ 初为肥柴,请多多关照~
  • 前端路漫漫,技术学不完。今天也是美(diao)好(fa)的一天( 跪了...orz