While basic type checking and libraries like Zod provide robust validation, advanced scenarios may require more sophisticated techniques. This guide explores using class-validator
and class-transformer
for object-oriented validation, as well as crafting custom type guards for complex logic.
Object-Oriented Validation with class-validator
and class-transformer
class-validator
and class-transformer
work synergistically to provide powerful object-oriented validation. class-validator
uses decorators to define validation rules, while class-transformer
handles object transformations.
Installation
npm install class-validator class-transformer reflect-metadata
This installs:
class-validator
: For decorator-based validationclass-transformer
: For object transformationreflect-metadata
: Required for decorator metadata
Enable Decorator Metadata:
Ensure reflect-metadata
is imported at the top of your entry file (e.g., index.ts
):
import 'reflect-metadata'; // Import reflect-metadata once in your app
Also, ensure that your tsconfig.json
has the following options enabled:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
// ... other options
}
}
Define Validation Classes:
Create classes that represent your request bodies, and use decorators from class-validator
to define validation rules.
import { IsString, IsEmail, MinLength, IsOptional, IsInt, Min, Max } from 'class-validator';
export class CreateUserDto {
@IsString()
@MinLength(3)
username: string;
@IsEmail()
email: string;
@IsString()
@MinLength(8)
password: string;
@IsOptional()
@IsInt()
@Min(18)
@Max(120)
age?: number;
}
Validation Middleware:
Create middleware to validate incoming requests using class-validator
and class-transformer
.
import { Request, Response, NextFunction } from 'express';
import { validate, plainToClass } from 'class-transformer';
import { CreateUserDto } from './dtos/create-user.dto'; // Assuming your DTO is in dtos/create-user.dto
export async function validateDto(req: Request, res: Response, next: NextFunction) {
const dto = plainToClass(CreateUserDto, req.body);
const errors = await validate(dto);
if (errors.length > 0) {
return res.status(400).json({ errors: errors.map((e) => e.constraints) });
}
req.body = dto; // Replace req.body with the transformed and validated DTO
next();
}
Using Validation Middleware in Routes:
Apply the middleware to your routes.
import express, { Request, Response } from 'express';
import { validateDto } from './middleware/validate.dto'; // Assuming your middleware is in middleware/validate.dto
import { CreateUserDto } from './dtos/create-user.dto';
const app = express();
app.use(express.json());
app.post('/users', validateDto, (req: Request<{}, {}, CreateUserDto>, res: Response) => {
// req.body is now a validated instance of CreateUserDto
const { username, email, password, age } = req.body;
// ... create user logic
res.status(201).json({ message: 'User created' });
});
Custom Type Guards for Complex Validation
For validation logic that goes beyond simple decorators, custom type guards offer flexibility.
Example: Validating a Custom Date Format
function isValidCustomDate(value: any): value is string {
if (typeof value !== 'string') {
return false;
}
const regex = /^d{4}-d{2}-d{2}Td{2}:d{2}:d{2}.d{3}Z$/;
return regex.test(value);
}
interface EventRequest {
startTime: string; // Must be a custom date format
}
function validateEventRequest(req: Request, res: Response, next: NextFunction) {
const { startTime } = req.body as EventRequest;
if (!isValidCustomDate(startTime)) {
return res.status(400).json({ error: 'Invalid start time format' });
}
next();
}
app.post('/events', validateEventRequest, (req: Request<any, any, EventRequest>, res: Response) => {
// req.body.startTime is now guaranteed to be a valid custom date string
res.json({ message: 'Event created' });
});
Benefits
- Object-Oriented Validation:
class-validator
andclass-transformer
provide a structured approach to validation. - Reusability: Validation classes and middleware can be reused across your application.
- Flexibility: Custom type guards handle complex validation logic.
- Readability: Decorators and type guards improve code readability.