NestJS Pipes
A pipe transforms or validates data before it reaches the controller method. Pipes sit between the raw request data and the handler parameters. They receive the incoming value, apply a transformation or validation, and either return the cleaned value or throw an exception that stops the request.
Two Jobs of a Pipe
Transformation:
Input: "42" (string from URL)
Pipe: ParseIntPipe
Output: 42 (number)
Validation:
Input: { name: '', email: 'bad' }
Pipe: ValidationPipe
Output: 400 Bad Request (throws)
Built-In Pipes
Pipe | Purpose ----------------------|------------------------------------------ ValidationPipe | Validates request data against a DTO class ParseIntPipe | Converts string to integer; throws if invalid ParseFloatPipe | Converts string to float ParseBoolPipe | Converts 'true'/'false' string to boolean ParseArrayPipe | Parses a comma-separated string to an array ParseUUIDPipe | Validates and passes a UUID string ParseEnumPipe | Validates that value is in an enum DefaultValuePipe | Provides a default if the value is undefined
Using Built-In Pipes
// ParseIntPipe — route parameter
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.usersService.findOne(id);
}
// ParseBoolPipe — query string
@Get()
findAll(@Query('active', ParseBoolPipe) active: boolean) {
return this.usersService.findAll({ active });
}
// DefaultValuePipe — optional query with fallback
@Get()
findAll(
@Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number,
@Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit: number,
) {
return this.usersService.findAll({ page, limit });
}
Creating a Custom Pipe
A pipe is a class decorated with @Injectable() that implements the PipeTransform interface:
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
@Injectable()
export class PositiveIntPipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata) {
const parsed = parseInt(value, 10);
if (isNaN(parsed)) {
throw new BadRequestException(`'${value}' is not a valid number`);
}
if (parsed <= 0) {
throw new BadRequestException(`Value must be a positive number`);
}
return parsed;
}
}
// Usage
@Get(':id')
findOne(@Param('id', PositiveIntPipe) id: number) {
return this.usersService.findOne(id);
}
Pipe Execution Position in the Pipeline
HTTP Request
|
v
Middleware
|
v
Guards
|
v
Interceptors (before)
|
v
Pipes ← transform and validate parameters HERE
|
v
Controller Method
|
v
Interceptors (after)
|
v
HTTP Response
Applying Pipes at Different Scopes
// Parameter level — affects only this parameter
@Param('id', ParseIntPipe) id: number
// Method level — affects all parameters on this route
@Post()
@UsePipes(new ValidationPipe())
create(@Body() dto: CreateUserDto) { ... }
// Controller level — affects all methods in the controller
@Controller('users')
@UsePipes(ValidationPipe)
export class UsersController { ... }
// Global — affects every route in the application
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
Validation Pipe With ParseEnumPipe
enum UserRole {
Admin = 'admin',
User = 'user',
Manager = 'manager',
}
@Get()
findByRole(@Query('role', new ParseEnumPipe(UserRole)) role: UserRole) {
return this.usersService.findByRole(role);
}
// GET /users?role=admin → works
// GET /users?role=superuser → 400 Bad Request
Pipes vs ValidationPipe Internals
The built-in ValidationPipe is itself a pipe. It uses class-validator decorators on your DTO and class-transformer to convert the raw object into a typed class instance before running validation. Understanding that ValidationPipe is a pipe clarifies why it applies through the same @UsePipes() decorator or global registration as any other pipe.
ParseArrayPipe for Comma-Separated Inputs
// GET /users?ids=1,2,3,4
@Get()
findByIds(
@Query('ids', new ParseArrayPipe({ items: Number, separator: ',' }))
ids: number[],
) {
return this.usersService.findByIds(ids);
}
// ids = [1, 2, 3, 4]
Pipes enforce the contract between the raw HTTP world and your typed TypeScript code. Every route parameter arrives as a string. Every query value arrives as a string. Pipes bridge that gap — converting strings to numbers, booleans, enums, and arrays, and rejecting values that do not conform to the expected type before any business logic runs.
