class-validator Decorators

Adding Rules

src/products/dto/create-product.dto.ts

import { Type } from 'class-transformer';
import { IsIn, IsInt, IsNotEmpty, IsOptional, IsPositive, IsString, Length, Max, Min } from 'class-validator';

export const CATEGORIES = ['audio', 'mobiles', 'laptops', 'wearables', 'accessories'] as const;

export class CreateProductDto {
  @IsString()
  @Length(3, 120)
  name: string;

  @IsOptional()
  @IsString()
  @Length(0, 2000)
  description?: string;

  @Type(() => Number)
  @IsPositive()
  @Max(1_000_000)
  price: number;

  @IsInt()
  @Min(0)
  stock: number;

  @IsNotEmpty()
  @IsIn(CATEGORIES)
  category: string;
}

Common Decorators

DecoratorChecks
@IsString(), @IsInt(), @IsNumber(), @IsBoolean()Type
@IsEmail(), @IsUrl(), @IsUUID(), @IsMobilePhone('en-IN')Format
@Min(), @Max(), @IsPositive(), @Length()Range and length
@IsOptional()Skip other rules if the value is missing
@IsIn([...]), @IsEnum(Enum)Allowed values
@Matches(/regex/)Pattern (PIN code, GSTIN)
@ValidateNested() + @Type(() => Dto)Nested objects and arrays
@IsArray(), @ArrayMinSize(1)Arrays

How Decorator Validation Works

Example

class-validator stores rules per property when the class is defined, then checks an object against them. A mini version with legacy property decorators:

type Rule = { property: string; check: (v: unknown) => boolean; message: string };
const rules = new Map<Function, Rule[]>();

function addRule(check: (v: unknown) => boolean, message: (p: string) => string): PropertyDecorator {
  return (target, key) => {
    const list = rules.get(target.constructor) ?? [];
    list.push({ property: String(key), check, message: message(String(key)) });
    rules.set(target.constructor, list);
  };
}
const IsString = () => addRule((v) => typeof v === 'string', (p) => p + ' must be a string');
const IsPositive = () => addRule((v) => typeof v === 'number' && v > 0, (p) => p + ' must be a positive number');
const Min = (n: number) => addRule((v) => typeof v === 'number' && v >= n, (p) => p + ' must not be less than ' + n);

class CreateProductDto {
  @IsString() name!: string;
  @IsPositive() price!: number;
  @Min(0) stock!: number;
}

function validate(cls: Function, body: Record<string, unknown>): string[] {
  return (rules.get(cls) ?? []).filter((r) => !r.check(body[r.property])).map((r) => r.message);
}

console.log(validate(CreateProductDto, { name: 'JBL Go 3', price: 2999, stock: 10 }));
console.log(validate(CreateProductDto, { name: 42, price: -5, stock: -1 }));

Output

[]
[
  'name must be a string',
  'price must be a positive number',
  'stock must not be less than 0'
]