
Class Validator
- 40 installs
- 27 repo stars
- Updated July 17, 2026
- claude-dev-suite/claude-dev-suite
Validate TypeScript DTOs with class-validator decorators, the standard validation approach in NestJS via ValidationPipe.
About
Reference for class-validator covering decorator-based DTO validation, class-transformer integration, and the NestJS ValidationPipe. A developer uses it when validating request DTOs in NestJS or class-based TypeScript apps.
- Decorator-based DTO validation
- NestJS ValidationPipe integration
Class Validator by the numbers
- 40 all-time installs (skills.sh)
- Ranked #3,286 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Jul 29, 2026 (Skillselion catalog sync)
npx skills add https://github.com/claude-dev-suite/claude-dev-suite --skill class-validatorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 40 |
|---|---|
| repo stars | ★ 27 |
| Last updated | July 17, 2026 |
| Repository | claude-dev-suite/claude-dev-suite ↗ |
What it does
Validate TypeScript DTOs with class-validator decorators, the standard validation approach in NestJS via ValidationPipe.
Files
Class-validator - Quick Reference
Full Reference: See advanced.md for custom validators, cross-field validation, validation groups, error formatting, and class-transformer integration.
Deep Knowledge: Usemcp__documentation__fetch_docswith technology:class-validatorfor comprehensive documentation.
Setup
npm install class-validator class-transformer// tsconfig.json
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}Basic Usage
import {
validate,
IsString,
IsEmail,
IsInt,
Min,
Max,
Length,
IsOptional,
} from 'class-validator';
import { plainToInstance } from 'class-transformer';
class CreateUserDto {
@IsString()
@Length(2, 50)
name: string;
@IsEmail()
email: string;
@IsInt()
@Min(18)
@Max(120)
age: number;
@IsOptional()
@IsString()
bio?: string;
}
// Validate
async function validateUser(data: unknown) {
const user = plainToInstance(CreateUserDto, data);
const errors = await validate(user);
if (errors.length > 0) {
throw new Error(errors.map((e) => Object.values(e.constraints || {})).flat().join(', '));
}
return user;
}Common Decorators
String Validators
import {
IsString, IsNotEmpty, Length, MinLength, MaxLength,
Matches, IsUUID, IsEmail, IsUrl, IsIP,
} from 'class-validator';
class StringValidationDto {
@IsString()
@IsNotEmpty()
required: string;
@Length(5, 20)
username: string;
@MinLength(8)
@Matches(/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/, {
message: 'Password must contain letters and numbers',
})
password: string;
@IsEmail()
email: string;
@IsUrl({ require_protocol: true })
website: string;
@IsUUID('4')
id: string;
}Number Validators
import { IsNumber, IsInt, IsPositive, Min, Max, IsDivisibleBy } from 'class-validator';
class NumberValidationDto {
@IsNumber()
decimal: number;
@IsInt()
integer: number;
@IsPositive()
positive: number;
@Min(0)
@Max(100)
percentage: number;
}Date Validators
import { IsDate, MinDate, MaxDate, IsISO8601 } from 'class-validator';
import { Type } from 'class-transformer';
class DateValidationDto {
@IsDate()
@Type(() => Date)
date: Date;
@MinDate(new Date('2020-01-01'))
@Type(() => Date)
afterDate: Date;
@IsISO8601()
isoString: string;
}Array Validators
import {
IsArray, ArrayMinSize, ArrayMaxSize, ArrayUnique,
ArrayNotEmpty, ValidateNested,
} from 'class-validator';
import { Type } from 'class-transformer';
class ItemDto {
@IsString()
name: string;
}
class ArrayValidationDto {
@IsArray()
@ArrayNotEmpty()
@ArrayMinSize(1)
@ArrayMaxSize(10)
@IsString({ each: true }) // Validate each item
tags: string[];
@IsArray()
@ValidateNested({ each: true })
@Type(() => ItemDto)
items: ItemDto[];
}Nested Object Validation
import { IsObject, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
class AddressDto {
@IsString()
street: string;
@IsString()
city: string;
}
class UserWithAddressDto {
@IsString()
name: string;
@ValidateNested()
@Type(() => AddressDto)
address: AddressDto;
}Other Validators
import {
IsBoolean, IsEnum, IsIn, IsOptional,
IsCreditCard, IsPhoneNumber,
} from 'class-validator';
enum UserRole {
ADMIN = 'admin',
USER = 'user',
GUEST = 'guest',
}
class MixedValidationDto {
@IsBoolean()
active: boolean;
@IsEnum(UserRole)
role: UserRole;
@IsIn(['draft', 'published', 'archived'])
status: string;
@IsOptional()
@IsCreditCard()
creditCard?: string;
}NestJS Integration
// main.ts
import { ValidationPipe } from '@nestjs/common';
app.useGlobalPipes(
new ValidationPipe({
whitelist: true, // Strip non-decorated properties
forbidNonWhitelisted: true, // Throw on unknown properties
transform: true, // Auto-transform payloads to DTO instances
transformOptions: {
enableImplicitConversion: true,
},
})
);// users.controller.ts
@Controller('users')
export class UsersController {
@Post()
create(@Body() createUserDto: CreateUserDto) {
// createUserDto is already validated and transformed
return this.usersService.create(createUserDto);
}
@Get()
findAll(@Query() query: PaginationQueryDto) {
// Query params also validated
return this.usersService.findAll(query);
}
@Get(':id')
findOne(@Param('id', ParseUUIDPipe) id: string) {
return this.usersService.findOne(id);
}
}Partial DTOs (for updates)
import { PartialType, OmitType, PickType, IntersectionType } from '@nestjs/mapped-types';
// All fields optional
export class UpdateUserDto extends PartialType(CreateUserDto) {}
// Omit specific fields
export class CreateUserWithoutPasswordDto extends OmitType(CreateUserDto, ['password']) {}
// Pick specific fields
export class LoginDto extends PickType(CreateUserDto, ['email', 'password']) {}
// Combine DTOs
export class ExtendedUserDto extends IntersectionType(CreateUserDto, AdditionalFieldsDto) {}Custom Validator (Basic)
import {
registerDecorator,
ValidationOptions,
ValidatorConstraint,
ValidatorConstraintInterface,
ValidationArguments,
} from 'class-validator';
@ValidatorConstraint({ async: false })
export class IsStrongPasswordConstraint implements ValidatorConstraintInterface {
validate(password: string, args: ValidationArguments) {
const hasUppercase = /[A-Z]/.test(password);
const hasLowercase = /[a-z]/.test(password);
const hasNumber = /\d/.test(password);
const minLength = password.length >= 8;
return hasUppercase && hasLowercase && hasNumber && minLength;
}
defaultMessage(args: ValidationArguments) {
return 'Password must contain uppercase, lowercase, and number';
}
}
export function IsStrongPassword(validationOptions?: ValidationOptions) {
return function (object: Object, propertyName: string) {
registerDecorator({
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
constraints: [],
validator: IsStrongPasswordConstraint,
});
};
}
// Usage
class RegisterDto {
@IsStrongPassword()
password: string;
}When NOT to Use This Skill
- Client-side validation - Use Zod with React Hook Form
- Non-NestJS backends - Zod is more framework-agnostic
- Functional programming patterns - Decorators require classes
- Simple validation - Zod has simpler API
Comparison: class-validator vs Zod
| Feature | class-validator | Zod |
|---|---|---|
| Style | Decorators | Functional |
| Framework | NestJS standard | Framework agnostic |
| Transform | class-transformer | Built-in |
| Bundle size | Larger | Smaller |
| Type inference | Manual | Automatic |
Anti-Patterns
| Anti-Pattern | Why It's Bad | Correct Approach |
|---|---|---|
| Missing @Type() decorator | Nested validation fails | Use @Type() for nested objects |
| No whitelist in ValidationPipe | Accepts unknown properties | Set whitelist: true |
| Using any in DTOs | Bypasses validation | Use proper types |
| Manual validation in controller | Code duplication | Use ValidationPipe globally |
| Missing experimentalDecorators | Decorators don't work | Enable in tsconfig.json |
| Not using class-transformer | Plain objects not validated | Use plainToInstance() |
Quick Troubleshooting
| Issue | Cause | Solution |
|---|---|---|
| Decorators not working | tsconfig misconfigured | Enable experimentalDecorators: true |
| Nested validation fails | Missing @Type() | Add @Type(() => NestedClass) |
| Validation not running | ValidationPipe not set | Add ValidationPipe globally in main.ts |
| Unknown properties accepted | No whitelist | Set whitelist: true in ValidationPipe |
| Transform not working | Missing transform option | Set transform: true in ValidationPipe |
| Async validators fail | Wrong setup | Use async: true in @ValidatorConstraint |
Checklist
- [ ] tsconfig with decorators enabled
- [ ] class-transformer for nested/transform
- [ ] Global ValidationPipe in NestJS
- [ ] whitelist and forbidNonWhitelisted
- [ ] Custom validators for specific logic
- [ ] Error formatting for API responses
Reference
Deep Knowledge: Usemcp__documentation__fetch_docswith technology:class-validator
- class-validator GitHub
Class-validator Advanced Patterns
Custom Validators
Synchronous Validator
import {
registerDecorator,
ValidationOptions,
ValidatorConstraint,
ValidatorConstraintInterface,
ValidationArguments,
} from 'class-validator';
// Custom constraint
@ValidatorConstraint({ async: false })
export class IsStrongPasswordConstraint implements ValidatorConstraintInterface {
validate(password: string, args: ValidationArguments) {
const hasUppercase = /[A-Z]/.test(password);
const hasLowercase = /[a-z]/.test(password);
const hasNumber = /\d/.test(password);
const hasSpecial = /[!@#$%^&*]/.test(password);
const minLength = password.length >= 8;
return hasUppercase && hasLowercase && hasNumber && hasSpecial && minLength;
}
defaultMessage(args: ValidationArguments) {
return 'Password must contain uppercase, lowercase, number, and special character';
}
}
// Decorator factory
export function IsStrongPassword(validationOptions?: ValidationOptions) {
return function (object: Object, propertyName: string) {
registerDecorator({
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
constraints: [],
validator: IsStrongPasswordConstraint,
});
};
}
// Usage
class RegisterDto {
@IsStrongPassword()
password: string;
}Async Validator (Database Check)
@ValidatorConstraint({ async: true })
export class IsEmailUniqueConstraint implements ValidatorConstraintInterface {
constructor(private userService: UserService) {}
async validate(email: string) {
const user = await this.userService.findByEmail(email);
return !user;
}
defaultMessage() {
return 'Email already exists';
}
}---
Cross-field Validation
Conditional Validation
import { ValidateIf } from 'class-validator';
class PaymentDto {
@IsIn(['credit_card', 'bank_transfer', 'paypal'])
method: string;
@ValidateIf((o) => o.method === 'credit_card')
@IsCreditCard()
cardNumber?: string;
@ValidateIf((o) => o.method === 'bank_transfer')
@IsString()
@IsNotEmpty()
iban?: string;
@ValidateIf((o) => o.method === 'paypal')
@IsEmail()
paypalEmail?: string;
}Password Confirmation
import { IsString, MinLength, Validate } from 'class-validator';
@ValidatorConstraint({ name: 'MatchPasswords', async: false })
export class MatchPasswordsConstraint implements ValidatorConstraintInterface {
validate(confirmPassword: string, args: ValidationArguments) {
const object = args.object as any;
return object.password === confirmPassword;
}
defaultMessage() {
return 'Passwords do not match';
}
}
class ChangePasswordDto {
@IsString()
@MinLength(8)
password: string;
@IsString()
@Validate(MatchPasswordsConstraint)
confirmPassword: string;
}---
Validation Groups
class CreateUserDto {
@IsNotEmpty({ groups: ['create'] })
@IsOptional({ groups: ['update'] })
@IsString()
name: string;
@IsEmail({}, { groups: ['create', 'update'] })
email: string;
@IsString({ groups: ['create'] })
@MinLength(8, { groups: ['create'] })
password: string;
}
// Validate with specific group
const errors = await validate(user, { groups: ['update'] });
// NestJS with groups
@UsePipes(new ValidationPipe({ groups: ['create'] }))
@Post()
create(@Body() dto: CreateUserDto) {}---
Error Formatting
import { validate, ValidationError } from 'class-validator';
interface FormattedError {
field: string;
constraints: string[];
}
function formatErrors(errors: ValidationError[]): FormattedError[] {
return errors.flatMap((error) => {
if (error.constraints) {
return [{
field: error.property,
constraints: Object.values(error.constraints),
}];
}
// Handle nested validation errors
if (error.children?.length) {
return formatErrors(error.children).map((child) => ({
...child,
field: `${error.property}.${child.field}`,
}));
}
return [];
});
}---
Class-transformer Integration
import { Transform, Expose, Exclude, Type } from 'class-transformer';
class UserResponseDto {
@Expose()
id: string;
@Expose()
name: string;
@Expose()
@Transform(({ value }) => value.toLowerCase())
email: string;
@Exclude()
password: string;
@Expose()
@Type(() => Date)
@Transform(({ value }) => value.toISOString())
createdAt: Date;
}
// Transform plain object to class instance
const userDto = plainToInstance(UserResponseDto, user, {
excludeExtraneousValues: true,
});
// Transform class to plain object (for response)
const plainUser = instanceToPlain(userDto);---
NestJS Partial DTOs
import { PartialType, OmitType, PickType, IntersectionType } from '@nestjs/mapped-types';
// All fields optional
export class UpdateUserDto extends PartialType(CreateUserDto) {}
// Omit specific fields
export class CreateUserWithoutPasswordDto extends OmitType(CreateUserDto, ['password']) {}
// Pick specific fields
export class LoginDto extends PickType(CreateUserDto, ['email', 'password']) {}
// Combine DTOs
export class ExtendedUserDto extends IntersectionType(CreateUserDto, AdditionalFieldsDto) {}---
Nested Object Validation
import {
IsObject,
IsNotEmptyObject,
ValidateNested,
} from 'class-validator';
import { Type } from 'class-transformer';
class AddressDto {
@IsString()
street: string;
@IsString()
city: string;
}
class UserWithAddressDto {
@IsString()
name: string;
@ValidateNested()
@Type(() => AddressDto)
address: AddressDto;
}---
Array Validation
import {
IsArray,
ArrayMinSize,
ArrayMaxSize,
ArrayUnique,
ArrayNotEmpty,
ValidateNested,
} from 'class-validator';
import { Type } from 'class-transformer';
class ItemDto {
@IsString()
name: string;
}
class ArrayValidationDto {
@IsArray()
@ArrayNotEmpty()
@ArrayMinSize(1)
@ArrayMaxSize(10)
@IsString({ each: true }) // Validate each item
tags: string[];
@IsArray()
@ArrayUnique()
@IsInt({ each: true })
uniqueNumbers: number[];
@IsArray()
@ValidateNested({ each: true })
@Type(() => ItemDto)
items: ItemDto[];
}---
Comparison: class-validator vs Zod
| Feature | class-validator | Zod |
|---|---|---|
| Style | Decorators | Functional |
| Framework | NestJS standard | Framework agnostic |
| Transform | class-transformer | Built-in |
| Bundle size | Larger | Smaller |
| Type inference | Manual | Automatic |
Related skills
Backend & APIsbackend