NestJS自定义ValidatorConstraint注入DataSource返回undefined
问题描述
实现自定义ValidatorConstraint校验数据表指定列是否存在目标值时,计划注入TypeORM的DataSource实例执行数据库查询,但验证器内访问this.dataSource返回undefined。
相关代码
DTO 代码
import { ApiProperty } from '@nestjs/swagger'; import { IsInt, IsNotEmpty, IsString, Max, Min } from 'class-validator'; import { Exists } from "../../common/decorators/exists.decorator"; export class CreateTeacherReviewDto { @ApiProperty() @IsNotEmpty() @IsInt() @Exists(['teacher:id']) readonly teacherId: number; }
自定义验证器代码
import { BadRequestException, Injectable } from '@nestjs/common'; import { registerDecorator, ValidationArguments, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface, } from 'class-validator'; import { DataSource } from "typeorm"; export function Exists(property: any, validationOptions?: ValidationOptions) { return (object: any, propertyName: string) => { registerDecorator({ target: object.constructor, propertyName, options: validationOptions, constraints: [property], validator: ExistsConstraint, }); }; } @ValidatorConstraint({ name: 'Exists', async: true }) @Injectable() export class ExistsConstraint implements ValidatorConstraintInterface { constructor(protected dataSource: DataSource) {} async validate(value: number | string, args: ValidationArguments) { const { constraints } = args; if (constraints.length === 0) { throw new BadRequestException(`Failed validating ${value} exists.`); } const str = constraints[0][0].split(':'); const tableName = str[0]; const columnName = str[1]; console.log(this.dataSource); // 此处输出 undefined return false; } defaultMessage(args: ValidationArguments) { const { property, value } = args; return `${property} ${value} is already taken.`; } }
package.json 依赖配置
"dependencies": { "@nestjs/common": "^8.0.0", "@nestjs/config": "^2.1.0", "@nestjs/core": "^8.0.0", "@nestjs/jwt": "^8.0.1", "@nestjs/mapped-types": "*", "@nestjs/passport": "^8.2.2", "@nestjs/platform-express": "^8.0.0", "@nestjs/swagger": "^5.2.1", "@nestjs/typeorm": "^8.1.4", "bcrypt": "^5.0.1", "class-transformer": "^0.5.1", "class-validator": "^0.13.2", "mysql2": "^2.3.3", "passport": "^0.6.0", "passport-jwt": "^4.0.0", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", "rxjs": "^7.2.0", "swagger-ui-express": "^4.4.0", "typeorm": "^0.3.6" },
tsconfig.json 配置
{ "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } }
问题原因
class-validator 默认自行实例化验证器类,不会走 NestJS 的依赖注入容器,即使给验证器类加了@Injectable()装饰器,构造函数声明的DataSource也不会被注入,访问时自然返回undefined。
解决方法
一共两步,缺一个都不行:
- 把
ExistsConstraint验证器注册到对应Nest模块的providers数组里,让Nest的DI容器接管这个类的实例化,完成DataSource的注入。如果要全局使用,还要加到exports数组里。 - 应用启动时调用class-validator提供的
useContainer方法,把验证器的实例化权限交给Nest DI容器,否则class-validator还是会自己new实例,注入逻辑不会生效。
具体操作
首先在存放验证器的模块(比如全局通用的CommonModule)中注册验证器:
import { Module } from '@nestjs/common'; import { ExistsConstraint } from './decorators/exists.decorator'; @Module({ providers: [ExistsConstraint], exports: [ExistsConstraint] }) export class CommonModule {}
然后在项目入口main.ts中添加useContainer配置:
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { useContainer } from 'class-validator'; import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); // 核心配置,将class-validator的实例化权交给Nest容器 useContainer(app.select(AppModule), { fallbackOnErrors: true }); app.useGlobalPipes(new ValidationPipe()) await app.listen(3000); } bootstrap();
配置完成后重启服务,this.dataSource就能正常获取到注入的TypeORM实例。
内容的提问来源于stack exchange,提问作者Rodrigo
相关产品推荐
相关产品推荐

