如何在NestJS+GraphQL中创建可调用服务的自定义字段验证装饰器
错误原因
@UsePipes()是Nest提供的控制器、路由方法、路由参数级别的装饰器,不支持直接修饰DTO类的属性,所以才会抛出以下类型不匹配错误:
The return type of a property decorator function must be either 'void' or 'any'.
Unable to resolve signature of property decorator when called as an expression.
Type 'TypedPropertyDescriptor' is not assignable to type 'void'.
要实现字段级自定义校验、且支持依赖注入调用服务(比如查询数据库校验ID合法性),可以基于Nest集成的class-validator自定义异步校验器,方案如下:
实现步骤
1. 编写自定义校验约束类
该类支持依赖注入,可直接注入其他业务服务实现数据库查询等校验逻辑:
import { Injectable } from '@nestjs/common'; import { ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments } from 'class-validator'; // 示例:注入后续需要用到的业务服务,比如医生服务、用户服务 // import { DoctorService } from 'src/doctor/doctor.service'; @ValidatorConstraint({ name: 'NameWithoutA', async: true }) @Injectable() export class NameWithoutAConstraint implements ValidatorConstraintInterface { // 构造函数注入依赖的服务即可使用 // constructor(private readonly doctorService: DoctorService) {} async validate(value: string, args: ValidationArguments) { // 基础校验逻辑:判断name是否包含字母a if (value?.includes('a')) { return false; } // 扩展场景示例:校验医生ID是否存在于数据库 // const doctorExist = await this.doctorService.isExist(args.object.idDoctor); // return doctorExist; return true; } defaultMessage(args: ValidationArguments) { return 'The name cannot contain the letter "a"'; } }
2. 注册校验约束到Nest容器
在app.module.ts的providers数组中加入上面定义的约束类,让Nest可以管理该类的实例,支持依赖注入:
import { Module } from '@nestjs/common'; import { NameWithoutAConstraint } from './dto/name-without-a.constraint'; @Module({ providers: [NameWithoutAConstraint /* 其他业务服务 */], // 其余模块配置保持不变 }) export class AppModule {}
3. 封装自定义校验装饰器
封装易用的装饰器,方便在DTO中直接调用:
import { registerDecorator, ValidationOptions } from 'class-validator'; export function IsNameWithoutA(validationOptions?: ValidationOptions) { return function (object: any, propertyName: string) { registerDecorator({ target: object.constructor, propertyName: propertyName, options: validationOptions, constraints: [], validator: NameWithoutAConstraint, }); }; }
4. 在InputType中使用装饰器
修改你的AppointmentInput类,直接在name字段上加上自定义装饰器即可:
@InputType() export class AppointmentInput { @Field(of => String) @IsNotEmpty() @IsNameWithoutA() // 新增自定义校验装饰器 name: string; // 其余字段保持原有逻辑不变 @Field(of => String) @IsNotEmpty() @IsDateString() dateStart: string; @Field(of => String) @IsNotEmpty() @IsDateString() dateEnd: string; @Field(of => Boolean) @IsBoolean() paid: boolean; @Field(of => Int) idDoctor: number; @Field(of => Int) idPatient: number; @Field(of => Int) idService: number; }
注意事项
- 确保你项目中全局启用的
ValidationPipe没有关闭异步校验支持,Nest默认的ValidationPipe配置已经兼容该场景 - 后续需要新增其他字段校验规则(比如校验医生ID合法性),只要按照上述流程新增对应的约束类和装饰器即可
内容的提问来源于stack exchange,提问作者Paul Miranda
相关产品推荐
相关产品推荐

