
NestJS API Development Guide 06: Custom Pipes
Video
Watch the full video on YouTube:
Introduction
In this sixth installment of the NestJS guide, we’ll explore the internal workings of Pipes (PipeTransform), the fundamental tool in NestJS for validating and transforming input data.
What is a Pipe?
A Pipe is a class that implements the PipeTransform interface. It has a single method called transform() that receives two parameters:
value- The input datametadata- Information about the argument being processed
PipeTransform Interface
interface PipeTransform<T = any, R = any> {
transform(value: T, metadata: ArgumentMetadata): R;
}
Understanding the Transform Method
The transform() method receives:
value- The actual data being processedmetadata- An object containing:type- The type of argument (body, query, param, etc.)metatype- The class type of the argumentdata- The name of the parameter
Example Metadata
{
type: 'body',
metatype: SavePetBodyDto,
data: undefined
}
Creating a Custom Pipe
Let’s create a custom pipe for validation:
@Injectable()
export class ValidateMinPipe implements PipeTransform {
transform(value: unknown, metadata: ArgumentMetadata) {
if (typeof value !== 'number') {
throw new BadRequestException('Value must be a number');
}
// value is now narrowed to number
return value;
}
}
Throwing Exceptions
When validation fails, you should throw an exception:
throw new UnprocessableEntityException('Validation failed');
This will return a 422 status code to the client.
Pipe Based on Classes
The most common way to create pipes is using classes:
@Injectable()
export class ValidateMinPipe implements PipeTransform {
constructor(private minValue: number) {}
transform(value: unknown, metadata: ArgumentMetadata) {
if (typeof value !== 'number') {
throw new BadRequestException('Value must be a number');
}
if (value < this.minValue) {
throw new UnprocessableEntityException(
`Value must be at least ${this.minValue}`
);
}
return value;
}
}
Lightweight Pipes
You can also create lightweight pipes using object literals:
const validateMinPipe = {
transform: (value: unknown, metadata: ArgumentMetadata) => {
if (typeof value !== 'number') {
throw new BadRequestException('Value must be a number');
}
if (value < 10) {
throw new UnprocessableEntityException('Value must be at least 10');
}
return value;
}
};
Using Pipes
You can use pipes in several ways:
At Parameter Level
@Get('pets/:id')
getPet(@Param('id', ValidateMinPipe) id: number) {
return `Pet ID: ${id}`;
}
At Body Level
@Post('pets')
createPet(@Body(ValidateMinPipe) body: unknown) {
return body;
}
Global Level
app.useGlobalPipes(new ValidationPipe());
Transforming Data
Pipes can also transform data:
@Injectable()
export class ParseIntPipe implements PipeTransform {
transform(value: string, metadata: ArgumentMetadata) {
const val = parseInt(value, 10);
if (isNaN(val)) {
throw new BadRequestException('Validation failed');
}
return val;
}
}
Best Practices
- Keep pipes focused on a single responsibility
- Use the appropriate exception for validation errors
- Always return a value or throw an exception
- Use class-based pipes for reusability
- Consider using built-in pipes before creating custom ones
Conclusion
Custom pipes give you full control over data validation and transformation in NestJS. They are essential for building robust APIs.
Repository
💻 Repository: https://github.com/radagva/guia-de-nestjs-example
Next Steps
🔔 Subscribe to the channel to not miss the next chapters of the NestJS guide!
