DEV Community

Cover image for Mastering Validation in TypeScript with class-validator – A Complete Beginner's Guide
Subhash Adhikari
Subhash Adhikari

Posted on

Mastering Validation in TypeScript with class-validator – A Complete Beginner's Guide

Learn how to validate user input in TypeScript and NestJS using class-validator. This in-depth tutorial covers everything from basic usage to advanced decorators, real-world use cases, and integration with NestJS — all optimized for clean code, security, and SEO.


📌 Table of Contents

  1. What is class-validator?
  2. Why Input Validation Matters
  3. Installation
  4. Basic Example: Email and Password Validation
  5. Popular Validation Decorators
  6. Advanced Validation: Arrays, Enums, Dates
  7. Nested Object Validation
  8. Conditional Validation with @ValidateIf
  9. Custom Validators
  10. Full Integration with NestJS
  11. Common Pitfalls and Mistakes
  12. Final Takeaways
  13. Connect with Me

🧠 What is class-validator?

class-validator is a powerful library that allows you to use decorators to validate TypeScript class properties. It's most commonly used with frameworks like NestJS or in pure Node.js apps to enforce runtime validation.


❗ Why Input Validation Matters

  • 🔐 Prevents invalid or malicious data
  • 🧼 Ensures clean, predictable input
  • 📉 Reduces bugs in business logic
  • 🔧 Improves API documentation and usability

⚙️ Installation

Install it along with class-transformer:

npm install class-validator class-transformer
Enter fullscreen mode Exit fullscreen mode

🧪 Basic Example: Email and Password Validation

import { IsEmail, MinLength } from 'class-validator';

export class CreateUserDto {
  @IsEmail()
  email: string;

  @MinLength(6)
  password: string;
}
Enter fullscreen mode Exit fullscreen mode
import { validate } from 'class-validator';

const user = new CreateUserDto();
user.email = 'wrong-format';
user.password = '123';

const errors = await validate(user);
console.log(errors); // -> array of error messages
Enter fullscreen mode Exit fullscreen mode

📋 Popular Validation Decorators

Decorator Use Case
@IsString() Ensure field is a string
@IsEmail() Validate email format
@IsInt() Ensure field is an integer
@Min() / @Max() Number boundaries
@MinLength() / @MaxLength() String length
@IsBoolean() Must be true/false
@IsOptional() Skips validation if not present

💡 Advanced Validation: Arrays, Enums, Dates

Validate Arrays

@IsArray()
@IsInt({ each: true })
scores: number[];
Enter fullscreen mode Exit fullscreen mode

Validate Enums

enum Role { USER = 'user', ADMIN = 'admin' }

@IsEnum(Role)
role: Role;
Enter fullscreen mode Exit fullscreen mode

Validate Dates

@IsDate()
@MinDate(new Date())
joinDate: Date;
Enter fullscreen mode Exit fullscreen mode

🧱 Nested Object Validation

class Profile {
  @IsString()
  bio: string;
}

class User {
  @ValidateNested()
  @Type(() => Profile)
  profile: Profile;
}
Enter fullscreen mode Exit fullscreen mode

Always include @Type() from class-transformer, or validation will silently fail.


⚖️ Conditional Validation with @ValidateIf

@ValidateIf(o => o.age > 18)
@IsString()
licenseNumber: string;
Enter fullscreen mode Exit fullscreen mode

This only validates licenseNumber if age > 18.


🔧 Custom Validators

@ValidatorConstraint({ name: 'IsStrongPassword', async: false })
class IsStrongPassword implements ValidatorConstraintInterface {
  validate(value: string) {
    return value.length >= 8 && /[A-Z]/.test(value);
  }

  defaultMessage() {
    return 'Password must have at least 8 characters and one uppercase letter.';
  }
}
Enter fullscreen mode Exit fullscreen mode

Use it like this:

@Validate(IsStrongPassword)
password: string;
Enter fullscreen mode Exit fullscreen mode

🚀 Full Integration with NestJS

main.ts Setup

app.useGlobalPipes(
  new ValidationPipe({
    whitelist: true,
    forbidNonWhitelisted: true,
    transform: true,
    transformOptions: {
      enableImplicitConversion: true
    }
  })
);
Enter fullscreen mode Exit fullscreen mode

Sample DTO and Controller

// create-user.dto.ts
export class CreateUserDto {
  @IsEmail()
  email: string;

  @MinLength(6)
  password: string;

  @IsOptional()
  @IsInt()
  @Min(18)
  age?: number;
}
Enter fullscreen mode Exit fullscreen mode
// users.controller.ts
@Post()
create(@Body() dto: CreateUserDto) {
  return this.userService.create(dto);
}
Enter fullscreen mode Exit fullscreen mode

NestJS will automatically:

  • Validate the body
  • Convert string to numbers if needed
  • Throw 400 Bad Request on validation failure

❌ Common Pitfalls and Mistakes

1. Forgetting @Type() in Nested DTOs

@ValidateNested()
profile: Profile; // ❌ Will not validate

// ✅ Fix:
@ValidateNested()
@Type(() => Profile)
profile: Profile;
Enter fullscreen mode Exit fullscreen mode

2. Wrong Order of Decorators

@IsEmail()
@IsNotEmpty()
email: string;
// Runs bottom-up: IsNotEmpty first, then IsEmail
Enter fullscreen mode Exit fullscreen mode

Always order carefully if decorators depend on each other.

3. Not Using plainToInstance()

const user = plainToInstance(CreateUserDto, req.body);
await validate(user);
Enter fullscreen mode Exit fullscreen mode

Without this, validation may silently fail if using plain JS objects.


✅ Final Takeaways

  • Use @IsOptional() when fields aren't required
  • Always use @Type() in nested objects
  • Use ValidationPipe in NestJS for automatic validation
  • Validate arrays with { each: true }
  • Use custom validators for complex business rules


🔄 What About Zod?

While this guide focuses on class-validator, many developers also consider zod for validating data in TypeScript. If you're wondering how it compares, here’s a quick breakdown to help you choose the best tool for your needs:

🥊 class-validator vs Zod: Which Should You Use?

Feature / Use Case class-validator 🔥 zod
TypeScript Decorator Support ✅ Yes (@IsEmail(), @MinLength(), etc.) ❌ No decorators — schema-based only
OOP / Class-based structure ✅ Excellent fit (DTOs, NestJS) ❌ Functional only
Functional/Composable validation 😐 Limited ✅ Designed for functional composition
Schema inference from types ❌ Manual type declarations z.infer<typeof Schema> supported
Runtime type validation ✅ Yes ✅ Yes
Integration with NestJS ✅ Native via ValidationPipe ❌ Requires custom adapters or wrappers
Performance ⚠️ Slightly heavier (uses decorators + reflection) ⚡ Very fast and lightweight
Custom validators ✅ Class-based rules ✅ Function-based rules
Serialization support ✅ via class-transformer ❌ Not included
Learning curve 😐 Slightly steeper (due to decorators) ✅ Beginner-friendly

✅ When to Use class-validator

  • You're using NestJS
  • You prefer OOP and decorators
  • You need class-transformer for serialization
  • Your architecture uses DTO classes

⚡ When to Use Zod

  • You're building with functional programming style
  • You're using tRPC, Express, or Fastify
  • You want fast runtime validation and type inference
  • You prefer schema-based design

🧠 Final Word

Both libraries are excellent. Your choice depends on:

  • Your framework (NestJS vs Express/tRPC)
  • Your programming style (OOP vs functional)
  • Your team’s preference for decorators vs schemas

If you prefer decorators and structured classes, go with class-validator.

If you prefer schema inference and functional composition, go with zod.

🙌 Connect with Me

If this helped you, please:


Want a quick reference? I’m posting a class-validator cheatsheet next! Follow me to stay updated.

Top comments (0)