import { registerDecorator, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments } from 'class-validator'; @ValidatorConstraint({ async: false }) export class IsAlphaOnlyConstraint implements ValidatorConstraintInterface { validate(text: string, args: ValidationArguments) { return /^[A-Za-z]+$/.test(text); } defaultMessage(args: ValidationArguments) { return 'Text ($value) must contain only letters'; } } export function IsAlphaOnly(validationOptions?: ValidationOptions) { return function (object: Object, propertyName: string) { registerDecorator({ target: object.constructor, propertyName: propertyName, options: validationOptions, constraints: [], validator: IsAlphaOnlyConstraint, }); }; } class SampleDto { @IsAlphaOnly({ message: 'Only letters allowed!' }) name: string; } // Input: { name: 'abc123' }
The regex /^[A-Za-z]+$/ only allows letters from A to Z (uppercase or lowercase). The input 'abc123' contains digits, so the validation fails. The custom message provided in the decorator options is used, so the error message is 'Only letters allowed!'.
The registerDecorator function requires the target to be the constructor of the class, which is object.constructor. The propertyName and validator class are also required. The options are optional but recommended for custom messages.
import { ValidatorConstraint, ValidatorConstraintInterface } from 'class-validator'; @ValidatorConstraint({ async: false }) export class AlwaysTrueValidator implements ValidatorConstraintInterface { validate(value: any) { return true; } defaultMessage() { return 'This should never fail'; } } // Used in a decorator but validation never fails
The validate method always returns true, which means the validation always passes no matter the input.
import { registerDecorator, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments } from 'class-validator'; @ValidatorConstraint({ async: false }) export class IsAdultConstraint implements ValidatorConstraintInterface { validate(age: number, args: ValidationArguments) { return age >= 18; } defaultMessage(args: ValidationArguments) { return `Age must be at least 18, but got ${args.value}`; } } export function IsAdult(validationOptions?: ValidationOptions) { return function (object: Object, propertyName: string) { registerDecorator({ target: object.constructor, propertyName: propertyName, options: validationOptions, constraints: [], validator: IsAdultConstraint, }); }; } class UserDto { @IsAdult({ message: 'User must be adult!' }) age: number; } // Input: { age: 17 }
The custom message passed in the decorator options ('User must be adult!') overrides the defaultMessage method. So the validation error shows the custom message.
The validate method can be synchronous or asynchronous. If asynchronous, the @ValidatorConstraint decorator must have async: true. The defaultMessage method is optional. Custom validators can use constructor injection if properly configured.