在 NestJS 中使用 Keq
本文介绍如何在 NestJS 项目中集成 Keq,以及如何使用 OpenAPI 自动生成类型安全的客户端代码。
安装依赖
- npm
- pnpm
- yarn
注册 KeqModule
在应用的根模块中导入 KeqModule。KeqModule 是一个 @Global() 模块,提供全局的 KeqRequest 实例和 KeqMiddlewareConsumer:
import { Module } from '@nestjs/common'
import { KeqModule } from '@keq-request/nestjs'
@Module({
imports: [
KeqModule,
/* ...其他模块... */
]
})
export class AppModule {}注册后即可在 Service 中通过依赖注入使用 KeqRequest:
import { Injectable } from '@nestjs/common'
import { KeqRequest } from 'keq'
@Injectable()
export class AppService {
constructor(private readonly request: KeqRequest) {}
async getCats() {
const cats = await this.request.get('https://api.example.com/cats')
return cats
}
}中间件进阶配置
概述
Keq 提供了类似 NestJS MiddlewareConsumer 的编程式接口,支持按路由注册中间件以及排除特定路由。
中间件体系中有两种消费者(Consumer):
KeqMiddlewareConsumer:全局消费者,由KeqModule提供。通过它注册的中间件对所有KeqRequest发出的请求生效。KeqConsumer<T>:模块级消费者,通过@InjectKeqConsumer(ModuleClass)注入,绑定到特定的生成模块。通过它注册的中间件仅对该模块的请求生效。
两种消费者共用同一套中间件 API(apply()、forRoutes()、exclude() 和链式注册),详见下文各小节。
请求发送时,中间件按以下顺序逐层执行:
全局中间件 → register() 中的模块级中间件 → 通过 KeqConsumer 注册的中间件
无论使用哪种配置方式,这个三层顺序始终不变。
模块级中间件有两种配置方式,分别适用于不同场景:
| 方式 | 适用场景 |
|---|---|
register({ middlewares: [...] }) | 模块导入时静态配置,中间件在模块初始化时即生效 |
@InjectKeqConsumer() | 在其他 Service 中动态注册,支持依赖注入 NestJS Provider(如 AuthService、ConfigService) |
两种方式的详细用法和对比请参见第 4.3 节。
全局中间件 — KeqMiddlewareConsumer
KeqMiddlewareConsumer 是一个 @Injectable() 类,可直接注入到任意 Service 或 Controller 中。通过它注册的中间件会对所有 KeqRequest 发出的请求生效:
import { Injectable } from '@nestjs/common'
import { KeqMiddlewareConsumer, KEQ_ROUTES } from '@keq-request/nestjs'
import { validateStatusCode } from '@keq-request/exception'
@Injectable()
export class GlobalMiddlewareService {
constructor(consumer: KeqMiddlewareConsumer) {
// 全局应用 validateStatusCode 中间件
consumer.apply(validateStatusCode()).forRoutes(KEQ_ROUTES.ALL)
}
}KeqMiddlewareConsumer 由 KeqModule 全局提供,只要模块导入了 KeqModule(或任何依赖了 KeqModule 的模块),即可在任何地方注入。
apply() — 注册中间件
apply() 方法接收 Keq 中间件函数或 NestJS 风格的中间件类:
import { type KeqNestMiddleware, type KeqExecutionContext, type KeqNext } from '@keq-request/nestjs'
// NestJS 风格中间件类(支持依赖注入)
class AuthMiddleware implements KeqNestMiddleware {
use(ctx: KeqExecutionContext, next: KeqNext): Promise<void> {
// 鉴权逻辑
return next()
}
}
consumer.apply(AuthMiddleware, loggingMiddleware).forRoutes(KEQ_ROUTES.ALL)apply() 支持两种类型:
KeqMiddleware:普通函数中间件(ctx, next) => voidKeqNestMiddleware:NestJS 类中间件(支持依赖注入,可注入其他 NestJS Provider)
forRoutes() — 指定路由范围
forRoutes() 控制中间件对哪些请求生效:
// 全局生效
consumer.apply(loggingMiddleware).forRoutes(KEQ_ROUTES.ALL)
// 仅对特定路径生效(支持 picomatch 通配符)
consumer.apply(authMiddleware).forRoutes({
pathname: '/api/**', // 仅匹配 /api/* 路径
method: 'POST', // 仅 POST 请求
})forRoutes() 接收两种路由目标:
| 类型 | 说明 |
|---|---|
KEQ_ROUTES.ALL | 匹配所有请求 |
KeqRouteInfo | { host?, method?, pathname? } — 按 host / 方法 / 路径过滤 |
如需按模块(而非路由)限定中间件作用范围,请使用模块级消费者 KeqConsumer<T>,详见第 4.3 节。
KeqRouteInfo 使用 picomatch 作为路径匹配引擎。当同时指定多个条件时(如 pathname + method),它们为 AND 关系,即所有条件都满足时才命中。
exclude() — 排除特定路由
exclude() 可在 forRoutes() 之前调用,排除中间件作用范围内的特定路由:
// 全局应用 auth 中间件,但排除 health check 和公开接口
consumer.apply(AuthMiddleware)
.exclude(
{ pathname: '/health' },
{ pathname: '/public/**' },
{ method: 'OPTIONS' },
)
.forRoutes(KEQ_ROUTES.ALL)也可以在路由级中间件中使用,实现子路径排除:
// 对 /api/** 应用日志中间件,但排除 /api/internal/**
consumer.apply(LoggingMiddleware)
.exclude({ pathname: '/api/internal/**' })
.forRoutes({ pathname: '/api/**' })- 单条
KeqRouteInfo内的多个条件(如{ pathname: '/api/**', method: 'GET' })为 AND 关系 - 多次调用
exclude()或多条参数为 OR 累积关系(任何一条匹配即排除) exclude()返回 config proxy,支持在forRoutes()之前链式调用
链式注册
KeqMiddlewareConsumer 和 KeqConsumer<T> 均支持链式注册多个中间件规则:
@Injectable()
export class AppMiddleware {
constructor(consumer: KeqMiddlewareConsumer) {
consumer
.apply(validateStatusCode())
.forRoutes(KEQ_ROUTES.ALL)
consumer
.apply(AuthMiddleware)
.exclude({ pathname: '/public/**' })
.forRoutes({ pathname: '/api/**' })
consumer
.apply(LoggingMiddleware)
.forRoutes(KEQ_ROUTES.ALL)
}
}使用 OpenAPI 生成 NestJS 模块
@keq-request/cli 支持从 OpenAPI 文档生成 NestJS 模块,提供完整的类型定义和依赖注入支持。
配置生成模式
在 .keqrc.ts 中设置 mode 为 nestjs-module:
import { defineKeqConfig, FileNamingStyle } from '@keq-request/cli'
export default defineKeqConfig({
mode: 'nestjs-module',
outdir: "./src/apis",
rendering: {
fileNamingStyle: FileNamingStyle.snakeCase,
},
modules: {
catService: "./cat-service-swagger.json",
},
})运行生成命令:
- npm
- pnpm
- yarn
注册生成的模块
将生成的模块导入到应用中:
import { Module } from '@nestjs/common'
import { KeqModule } from '@keq-request/nestjs'
import { CatServiceModule } from './apis/cat_service/cat_service.module'
@Module({
imports: [
// 全局 KeqModule(提供 KeqRequest 和 KeqMiddlewareConsumer)
KeqModule,
// 注册生成的模块(fork 自全局 KeqRequest,继承全局中间件)
CatServiceModule.register({
// 模块级中间件(全局中间件之后执行,详见 3.1 节)
middlewares: [
setBaseUrl('https://cat-api.example.com'),
appendHeader('Authorization', 'Bearer YOUR_TOKEN_HERE'),
]
}),
]
})
export class AppModule {}模块级中间件的两种配置方式
为生成的模块配置模块级中间件,有静态配置(register())和动态注册(@InjectKeqConsumer())两种方式。无论使用哪种方式,中间件都遵循三层执行顺序:全局中间件 → register() 中间件 → KeqConsumer 中间件(详见3.1 节)。
方式一:register() 静态配置
在模块导入时通过 register({ middlewares: [...] }) 直接配置。适用于配置在模块导入时已知、不依赖其他 NestJS Provider 的场景:
CatServiceModule.register({
middlewares: [
setBaseUrl('https://cat-api.example.com'),
appendHeader('Authorization', 'Bearer YOUR_TOKEN_HERE'),
],
})方式二:@InjectKeqConsumer() 动态注册
当中间件需要依赖注入其他 NestJS Provider(如 AuthService、ConfigService 等)时,使用 @InjectKeqConsumer() 在独立的 Service 中动态注册:
import { Injectable } from '@nestjs/common'
import { InjectKeqConsumer, KeqConsumer } from '@keq-request/nestjs'
import { CatServiceModule } from './apis/cat_service/cat_service.module'
@Injectable()
export class CatServiceMiddleware {
constructor(
@InjectKeqConsumer(CatServiceModule) consumer: KeqConsumer<typeof CatServiceModule>,
) {
// 该中间件仅对 CatServiceModule 的请求生效
consumer.apply(authMiddleware).forRoutes('*')
}
}@InjectKeqConsumer() 要求目标模块类具有 KEQ_CONSUMER 静态属性。由 @keq-request/cli 生成的 NestJS 模块自动包含此属性,可直接用于注入。
两种方式对比
| 方式 | 适用场景 | 依赖注入 |
|---|---|---|
register({ middlewares: [...] }) | 模块导入时静态配置,中间件在模块初始化时即生效 | 不支持 |
@InjectKeqConsumer() | 在其他 Service 中动态注册,中间件在 Service 实例化时生效 | 支持(可注入 AuthService、ConfigService 等) |
隔离模式
如果你不希望生成的模块继承全局中间件,可以设置 isolate: true 创建独立的 KeqRequest 实例:
@Module({
imports: [
CatServiceModule.register({
isolate: true, // 不继承全局中间件,创建独立实例
middlewares: [
setBaseUrl('https://cat-api.example.com'),
appendHeader('Authorization', 'Bearer YOUR_TOKEN_HERE'),
]
}),
]
})
export class SomeFeatureModule {}- 未设置
isolate(默认):从全局KeqModule提供的KeqRequestfork,继承全局中间件 isolate: true:创建全新的KeqRequest实例,不继承任何全局配置
如果未注册 KeqModule 且未设置 isolate: true,模块会自动创建独立实例并输出警告。
使用生成的客户端
import { Injectable } from '@nestjs/common'
import { CatServiceClient } from './apis/cat_service/cat_service.client'
@Injectable()
export class CatService {
constructor(private readonly catServiceClient: CatServiceClient) {}
async getCats() {
const response = await this.catServiceClient.getCats<200>()
return response.body
}
async getCatById(id: string) {
const response = await this.catServiceClient.getCatById<200>({ id })
return response.body
}
}