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 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;
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
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:
📚 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)