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
- What is
class-validator? - Why Input Validation Matters
- Installation
- Basic Example: Email and Password Validation
- Popular Validation Decorators
- Advanced Validation: Arrays, Enums, Dates
- Nested Object Validation
- Conditional Validation with
@ValidateIf - Custom Validators
- Full Integration with NestJS
- Common Pitfalls and Mistakes
- Final Takeaways
- 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 🧪 Basic Example: Email and Password Validation
import { IsEmail, MinLength } from 'class-validator'; export class CreateUserDto { @IsEmail() email: string; @MinLength(6) password: string; } 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 📋 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[]; Validate Enums
enum Role { USER = 'user', ADMIN = 'admin' } @IsEnum(Role) role: Role; Validate Dates
@IsDate() @MinDate(new Date()) joinDate: Date; 🧱 Nested Object Validation
class Profile { @IsString() bio: string; } class User { @ValidateNested() @Type(() => Profile) profile: Profile; } Always include @Type() from class-transformer, or validation will silently fail.
⚖️ Conditional Validation with @ValidateIf
@ValidateIf(o => o.age > 18) @IsString() licenseNumber: string; 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.'; } } Use it like this:
@Validate(IsStrongPassword) password: string; 🚀 Full Integration with NestJS
main.ts Setup
app.useGlobalPipes( new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true, transformOptions: { enableImplicitConversion: true } }) ); Sample DTO and Controller
// create-user.dto.ts export class CreateUserDto { @IsEmail() email: string; @MinLength(6) password: string; @IsOptional() @IsInt() @Min(18) age?: number; } // users.controller.ts @Post() create(@Body() dto: CreateUserDto) { return this.userService.create(dto); } NestJS will automatically:
- Validate the body
- Convert string to numbers if needed
- Throw
400 Bad Requeston validation failure
❌ Common Pitfalls and Mistakes
1. Forgetting @Type() in Nested DTOs
@ValidateNested() profile: Profile; // ❌ Will not validate // ✅ Fix: @ValidateNested() @Type(() => Profile) profile: Profile; 2. Wrong Order of Decorators
@IsEmail() @IsNotEmpty() email: string; // Runs bottom-up: IsNotEmpty first, then IsEmail Always order carefully if decorators depend on each other.
3. Not Using plainToInstance()
const user = plainToInstance(CreateUserDto, req.body); await validate(user); 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
ValidationPipein 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-transformerfor 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:
📚 Official Repo: github.com/typestack/class-validator
📬 Share your thoughts in comments
Want a quick reference? I’m posting a class-validator cheatsheet next! Follow me to stay updated.
Top comments (0)