We use cookies to enhance your experience on the site
CodeWorlds

Custom Validators — Creating Your Own Guards

The built-in class-validator decorators cover most cases. But what if you need a specialized guard that checks unique rules of your empire? It's time to learn how to create your own gate guards!

@ValidatorConstraint — full custom validator

The most flexible approach is to create your own validator class:

1import {
2  ValidatorConstraint,
3  ValidatorConstraintInterface,
4  ValidationArguments,
5} from 'class-validator';
6
7@ValidatorConstraint({ name: 'isRomanName', async: false })
8export class IsRomanNameConstraint implements ValidatorConstraintInterface {
9  validate(value: string, args: ValidationArguments): boolean {
10    // Check if the name starts with an uppercase letter
11    // and contains only Latin letters
12    return /^[A-Z][a-z]+$/.test(value);
13  }
14
15  defaultMessage(args: ValidationArguments): string {
16    return 'Name ($value) is not a valid Roman name!';
17  }
18}

The class must implement the

ValidatorConstraintInterface
with two methods:

  • validate()
    — returns
    true
    if the value is valid
  • defaultMessage()
    — returns the error message

Usage with @Validate()

1import { Validate } from 'class-validator';
2
3export class CreateLegionaryDto {
4  @Validate(IsRomanNameConstraint)
5  name: string;
6}

Creating a Decorator — a cleaner approach

Instead of using

@Validate()
, you can create your own decorator:

1import {
2  registerDecorator,
3  ValidationOptions,
4  ValidatorConstraint,
5  ValidatorConstraintInterface,
6} from 'class-validator';
7
8@ValidatorConstraint({ async: false })
9class IsLegionCodeConstraint implements ValidatorConstraintInterface {
10  validate(value: string): boolean {
11    return /^LEG-[IVXLCDM]+-\d{3}$/.test(value);
12  }
13
14  defaultMessage(): string {
15    return 'Legion code must have the format LEG-[Roman numerals]-[3 digits]';
16  }
17}
18
19export function IsLegionCode(validationOptions?: ValidationOptions) {
20  return function (object: object, propertyName: string) {
21    registerDecorator({
22      target: object.constructor,
23      propertyName: propertyName,
24      options: validationOptions,
25      constraints: [],
26      validator: IsLegionCodeConstraint,
27    });
28  };
29}

Now you can use it like any other decorator:

1export class CreateLegionaryDto {
2  @IsLegionCode({ message: 'Invalid legion code!' })
3  legionCode: string;
4}

Asynchronous Validators

Sometimes validation requires database access (e.g., checking if a name is unique). In that case, you create an asynchronous validator:

1@ValidatorConstraint({ name: 'isUniqueRank', async: true })
2export class IsUniqueRankConstraint implements ValidatorConstraintInterface {
3  constructor(private readonly legionService: LegionService) {}
4
5  async validate(rank: string): Promise<boolean> {
6    // Check in the database if the rank is available
7    const existing = await this.legionService.findByRank(rank);
8    return !existing;
9  }
10
11  defaultMessage(): string {
12    return 'This rank is already taken in the legion!';
13  }
14}

Note: Asynchronous validators require integration with the NestJS Dependency Injection system through

useContainer
.

Cross-Field Validation

You can create validators that check relationships between fields:

1@ValidatorConstraint({ async: false })
2class IsAgeValidForRankConstraint implements ValidatorConstraintInterface {
3  validate(value: any, args: ValidationArguments): boolean {
4    const object = args.object as CreateLegionaryDto;
5    // Legatus must be at least 30 years old
6    if (object.rank === 'legatus' && object.age < 30) {
7      return false;
8    }
9    // Centurio must be at least 25 years old
10    if (object.rank === 'centurio' && object.age < 25) {
11      return false;
12    }
13    return true;
14  }
15
16  defaultMessage(args: ValidationArguments): string {
17    return 'Age does not meet the required rank!';
18  }
19}

With custom validators, you have full control over the validation logic. Your gate guards can now check any rules, even the most complex ones!

Go to CodeWorlds