0PricingLogin
NestJS Enterprise Backend APIs · Lesson

Custom Validators and Async Constraints

Write reusable @ValidatorConstraint rules including async checks against the database.

Why Custom Validators?

The class-validator decorators that NestJS ships with (@IsEmail, @Min, @Length) cover generic shapes, but enterprise rules are domain-specific: "this coupon code must exist and not be expired" or "this email must be unique in the users table".

  • Custom validators let you encapsulate such rules behind a single reusable decorator.
  • They keep DTOs declarative and your business logic out of controllers.
  • Two flavors exist: inline (@Validate / registerDecorator) and constraint classes (@ValidatorConstraint).

In this lesson we focus on the @ValidatorConstraint class approach, including async checks that hit a database.

Anatomy of a ValidatorConstraint

A constraint class implements ValidatorConstraintInterface and is decorated with @ValidatorConstraint. It exposes two methods:

  • validate(value, args) returns boolean (or Promise<boolean> for async).
  • defaultMessage(args) returns the error string when validation fails.

The name option is the rule identifier, and async: true tells class-validator to await the result.

import {
  ValidatorConstraint,
  ValidatorConstraintInterface,
  ValidationArguments,
} from 'class-validator';

@ValidatorConstraint({ name: 'isStrongPassword', async: false })
export class IsStrongPasswordConstraint
  implements ValidatorConstraintInterface
{
  validate(value: string, _args: ValidationArguments): boolean {
    if (typeof value !== 'string') return false;
    const hasUpper = /[A-Z]/.test(value);
    const hasDigit = /[0-9]/.test(value);
    return value.length >= 8 && hasUpper && hasDigit;
  }

  defaultMessage(args: ValidationArguments): string {
    return `${args.property} must be 8+ chars with an uppercase letter and a digit`;
  }
}

All lessons in this course

  1. Nested and Array DTO Validation
  2. Custom Validators and Async Constraints
  3. Response Shaping with ClassSerializerInterceptor
  4. Conditional Validation and Dynamic Groups
← Back to NestJS Enterprise Backend APIs