Pipes

⭐ Interview Importance: HIGH
⏱️ Revision Time: 14 min

Pipes operate on the arguments being processed by a controller route handler. They intercept method invocations just before they are executed.

Overview

A pipe is a class annotated with the @Injectable() decorator, which implements the PipeTransform interface.

Pipes have two typical use cases in NestJS:

  1. Transformation: transform input data to the desired form (e.g., from a string to an integer).
  2. Validation: evaluate input data and, if valid, simply pass it through unchanged; otherwise, throw an exception.

In both cases, pipes operate on the arguments being processed by a controller route handler. Nest interposes a pipe just before a method is invoked, and the pipe receives the arguments destined for the method.

Key Concepts

  • PipeTransform Interface: Every pipe must implement this interface, which mandates the transform() method.
  • Auto-Exception Handling: If a pipe throws an error (like BadRequestException), NestJS automatically catches it and returns a formatted JSON error response to the client. The controller method is completely skipped.
  • Execution Context: Pipes are executed after Middleware, Guards, and Interceptors, but right before the Controller method.

Code Examples

How Pipes are Applied

Pipes are most commonly used in conjunction with the @Body(), @Query(), and @Param() decorators.

import { Controller, Get, Param, ParseIntPipe, Body, ValidationPipe } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';

@Controller('users')
export class UsersController {
  
  // 1. TRANSFORMATION: Converting the string 'id' into a number
  @Get(':id')
  findOne(@Param('id', ParseIntPipe) id: number) {
    return `Finding user with ID ${id}`;
  }

  // 2. VALIDATION: Validating the payload against a DTO
  @Post()
  create(@Body(new ValidationPipe()) createUserDto: CreateUserDto) {
    return 'This only executes if createUserDto is valid!';
  }
}

Passing Classes vs Instances

You can pass the pipe class itself, or an instance of the pipe.

// Passing the class leaves instantiation up to NestJS (Recommended, uses less memory)
@Param('id', ParseIntPipe) id: number

// Passing an instance allows you to customize the pipe's behavior
@Param('id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE })) id: number

Best Practices

  • Global Pipes for Validation: Instead of manually applying ValidationPipe to every single @Body() in your app, apply it globally in your main.ts file using app.useGlobalPipes(new ValidationPipe()).
  • Don’t Trust User Input: Always use ParseIntPipe or ParseUUIDPipe for @Param and @Query variables that you expect to be numbers or UUIDs. Without them, you are vulnerable to bugs and potential injection attacks because URL parameters are always strings by default.