NestJS自定义验证器中请求作用域服务注入失败及ModuleRef报错问题
在NestJS自定义验证器中正确注入请求作用域服务的解决方案
问题背景
在NestJS自定义装饰器中结合使用moduleRef与请求作用域的自定义提供者时遇到问题:调用moduleRef.resolve()获取作用域服务时,服务始终为undefined,且触发“Cannot read property 'resolve' of undefined”错误。尝试过官方文档的请求作用域依赖注入方式,但未解决。
现有代码实现
请求作用域服务(RequestService)
import { Injectable, Scope } from '@nestjs/common'; @Injectable({ scope: Scope.REQUEST }) export class RequestService { private isCoffee: boolean; setIsCoffee(drink: string) { this.isCoffee = ['mocha', 'espresso', 'latte'].includes(drink); } getIsCoffee(): boolean { return this.isCoffee; } }
RequestModule
import { Module } from '@nestjs/common'; import { RequestService } from './request.service'; @Module({ exports: [RequestService], providers: [RequestService], }) export class RequestModule {}
自定义验证器(IsCoffeeValidator)
import {/*...*/} @ValidatorConstraint({ name: 'IsCoffeeValidator', async: true }) @Injectable() export class IsCoffeeValidator implements ValidatorConstraintInterface, OnModuleInit { private requestService: RequestService; constructor( @Inject(REQUEST) private request: Request, // @Inject(RequestService) private requestService: RequestService, // 常规DI方式无效 private moduleRef: ModuleRef, ) {} async onModuleInit() { const contextId = ContextIdFactory.getByRequest(this.request); console.log('In onModuleInit'); // 未打印,因依赖请求作用域服务 this.requestService = await this.moduleRef.resolve( RequestService, contextId, { strict: false }, ); } async validate(type: string, { object }: ValidationArguments) { const contextId = ContextIdFactory.getByRequest(this.request); // 报错:“cannot read property resolve of undefined” this.requestService = await this.moduleRef.resolve( RequestService, contextId, { strict: false }, ); if (this.requestService.getIsCoffee()) { return true; } } }
CoffeeModule
import {/*...*/} @Module({ controllers: [CoffeeController], imports: [RequestModule], providers: [IsCoffeeValidator, RequestService], }) export class CoffeeModule {}
CoffeeDto
import { IsCoffee } from '../../is-coffee.decorator'; export class CoffeeDto { @IsCoffee() drink: string; }
CoffeeController
import {/*...*/} @Controller('coffee') export class CoffeeController { constructor( @Inject(RequestService) private readonly requestService: RequestService, ) {} @Post() checkDrink(@Body() body: CoffeeDto) { console.log(body); return this.requestService.getIsCoffee(); // 验证通过后应始终返回true } }
问题原因与解决步骤
核心问题
- 自定义验证器默认是单例作用域,但依赖了请求作用域的
REQUEST对象,导致NestJS会将验证器隐式转为请求作用域,但onModuleInit在模块初始化阶段执行,此时请求上下文还未创建,无法获取有效contextId和moduleRef。 moduleRef未被正确注入,或者在请求上下文外调用导致undefined。
正确实现方式
步骤1:将验证器标记为请求作用域
显式将IsCoffeeValidator设置为请求作用域,确保每次请求都创建新实例,且能获取当前请求的上下文:
import { Injectable, Scope } from '@nestjs/common'; import { ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments } from 'class-validator'; import { RequestService } from './request.service'; import { REQUEST, Inject } from '@nestjs/core'; import { Request } from 'express'; @ValidatorConstraint({ name: 'IsCoffeeValidator', async: true }) @Injectable({ scope: Scope.REQUEST }) // 显式标记为请求作用域 export class IsCoffeeValidator implements ValidatorConstraintInterface { constructor( private readonly requestService: RequestService, // 直接注入请求作用域服务 @Inject(REQUEST) private readonly request: Request, ) {} async validate(drink: string, args: ValidationArguments) { // 先设置当前饮品是否为咖啡 this.requestService.setIsCoffee(drink); // 返回验证结果 return this.requestService.getIsCoffee(); } }
步骤2:调整模块配置
在CoffeeModule中移除重复的RequestService提供者(已通过RequestModule导入并导出):
@Module({ controllers: [CoffeeController], imports: [RequestModule], providers: [IsCoffeeValidator], // 移除重复的RequestService }) export class CoffeeModule {}
步骤3:修正验证逻辑
原代码中验证器先调用getIsCoffee但未设置值,需先通过setIsCoffee传入当前drink参数,再返回结果。
补充说明
- 当验证器被标记为请求作用域后,常规构造函数注入
RequestService即可生效,无需再通过moduleRef.resolve()手动获取。 onModuleInit生命周期钩子不适用于请求作用域的提供者,因为它在模块启动时执行,而请求作用域的实例是每次请求时才创建的。
内容的提问来源于stack exchange,提问作者Alex Goncalves
相关产品推荐
相关产品推荐

