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)