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)