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)