On this page
Pipes and validation with class-validator
What are pipes?
A pipe in NestJS is a class that implements PipeTransform and is used to transform or validate data flowing through the request pipeline. Pipes execute after middleware and guards but before the route handler.
The two main use cases for pipes are:
- Transformation — convert input data into the desired format (e.g., parse a string ID to a number)
- Validation — evaluate the input and throw an exception if it does not meet the requirements
Built-in pipes
NestJS ships with several built-in pipes:
| Pipe | Purpose |
|---|---|
ValidationPipe |
Validates request bodies using class-validator decorators |
ParseIntPipe |
Parses and validates a value as an integer |
ParseFloatPipe |
Parses and validates a value as a float |
ParseBoolPipe |
Parses and validates a value as a boolean |
ParseArrayPipe |
Parses and validates a value as an array |
ParseUUIDPipe |
Validates a value as a UUID |
ParseEnumPipe |
Validates a value against an enum |
DefaultValuePipe |
Provides a default value if the input is undefined |
Using built-in pipes on route parameters:
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.booksService.findOne(id);
}
@Get()
findAll(
@Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number,
@Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit: number,
) {
return this.booksService.findAll(page, limit);
}Validation with class-validator
The most common use of pipes in NestJS is validating request bodies with ValidationPipe and class-validator. Install the required packages:
npm install class-validator class-transformerThen define DTOs (Data Transfer Objects) with validation decorators:
import { IsString, IsNotEmpty, IsNumber, IsPositive } from 'class-validator';
export class CreateBookDto {
@IsString()
@IsNotEmpty()
title: string;
@IsNumber()
@IsPositive()
price: number;
}When a request arrives with an invalid body, ValidationPipe throws a BadRequestException with detailed error messages about which fields failed and why.
Registering ValidationPipe globally
You should register ValidationPipe globally in main.ts so it applies to all routes:
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}),
);The three options above form the recommended production configuration:
- whitelist — Strips any properties not defined in the DTO. If a client sends
{ title: 'Clean Code', _secret: 'hack' }, the_secretfield is silently removed. - forbidNonWhitelisted — Instead of silently stripping, throws a
400 Bad Requestwhen unknown properties are present. - transform — Automatically converts plain JSON objects into DTO class instances. Without this,
bodywould be a plain object and the validation decorators would still work, but your service would receive a plain object, not aCreateBookDtoinstance.
Useful class-validator decorators
String validators
@IsString() // Must be a string
@IsNotEmpty() // Cannot be empty string
@IsEmail() // Valid email format
@IsUrl() // Valid URL
@IsUUID() // Valid UUID v4
@MaxLength(200) // Max 200 characters
@MinLength(3) // Min 3 characters
@Matches(/^[A-Z]/) // Matches regexNumber validators
@IsNumber() // Must be a number
@IsInt() // Must be an integer
@IsPositive() // Greater than 0
@IsNegative() // Less than 0
@Min(0) // Greater than or equal to 0
@Max(100) // Less than or equal to 100Boolean and date validators
@IsBoolean() // Must be a boolean
@IsDate() // Must be a Date instance
@IsDateString() // Must be an ISO 8601 date stringArray and object validators
@IsArray() // Must be an array
@ArrayMinSize(1) // At least 1 element
@ArrayMaxSize(10) // At most 10 elements
@ArrayUnique() // All elements must be unique
@IsObject() // Must be an object
@ValidateNested({ each: true }) // Validate nested objects
@Type(() => CreateBookDto) // Required for ValidateNestedOptional and conditional
@IsOptional() // Skips validation if value is null/undefined
@ValidateIf((o) => o.type === 'ebook') // Conditional validationNested validation
When a DTO contains nested objects, use @ValidateNested together with @Type:
import { Type } from 'class-transformer';
export class CreateOrderDto {
@ValidateNested({ each: true })
@Type(() => OrderItemDto)
@IsArray()
items: OrderItemDto[];
}
export class OrderItemDto {
@IsNumber()
@IsPositive()
bookId: number;
@IsInt()
@Min(1)
quantity: number;
}Without @Type(() => OrderItemDto), class-transformer does not know the type of the nested object and ValidateNested has no decorators to check.
Creating custom pipes
When built-in pipes and class-validator are not enough, you can create your own:
@Injectable()
export class TrimPipe implements PipeTransform<string, string> {
transform(value: string): string {
if (typeof value !== 'string') return value;
return value.trim();
}
}Apply it at the parameter, method, or controller level:
// Parameter level
@Post()
create(@Body('title', TrimPipe) title: string) {}
// Method level
@Post()
@UsePipes(TrimPipe)
create(@Body() dto: CreateBookDto) {}
// Controller level
@Controller('books')
@UsePipes(TrimPipe)
export class BooksController {}ParseEnumPipe for query parameters
When you have a route that accepts a limited set of values, ParseEnumPipe provides clear validation:
enum BookSort {
NEWEST = 'newest',
PRICE_ASC = 'price_asc',
PRICE_DESC = 'price_desc',
}
@Get()
findAll(@Query('sort', new ParseEnumPipe(BookSort)) sort: BookSort) {
return this.booksService.findAll(sort);
}Requesting /books?sort=invalid returns 400 Bad Request: sort must be one of the following values: newest, price_asc, price_desc.
Custom validation decorators
You can create reusable custom validators with registerDecorator:
import {
registerDecorator,
ValidationOptions,
ValidatorConstraint,
ValidatorConstraintInterface,
} from 'class-validator';
@ValidatorConstraint({ async: false })
class IsIsbnConstraint implements ValidatorConstraintInterface {
validate(value: string): boolean {
// Custom ISBN-13 validation logic
return /^\d{13}$/.test(value.replace(/-/g, ''));
}
defaultMessage(): string {
return 'isbn must be a valid ISBN-13';
}
}
function IsValidIsbn(options?: ValidationOptions) {
return (object: object, propertyName: string) => {
registerDecorator({
target: object.constructor,
propertyName,
options,
constraints: [],
validator: IsIsbnConstraint,
});
};
}
// Usage in DTO
export class CreateBookDto {
@IsValidIsbn()
isbn: string;
}import {
IsString,
IsNotEmpty,
IsNumber,
IsPositive,
IsISBN,
IsOptional,
IsDateString,
MaxLength,
Min,
Max,
} from 'class-validator';
export class CreateBookDto {
@IsString()
@IsNotEmpty()
@MaxLength(200)
title: string;
@IsISBN()
isbn: string;
@IsNumber({ maxDecimalPlaces: 2 })
@IsPositive()
@Max(9999.99)
price: number;
@IsNumber()
@IsPositive()
authorId: number;
@IsNumber()
@IsOptional()
categoryId?: number;
@IsDateString()
@IsOptional()
publishedAt?: string;
}
Sign in to track your progress