You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 12:54:04