Skip to content

NestJS Pipes

Pipes operate on the arguments passed to a route handler, transforming or validating them before the handler runs. This lesson covers the PipeTransform interface and NestJS's built-in pipes.

What Is a Pipe?

A pipe is a class implementing PipeTransform, with a single transform() method that receives the incoming value and metadata about it, and returns the (possibly converted) value, or throws an exception to reject it.

import { PipeTransform, ArgumentMetadata, BadRequestException } from '@nestjs/common';

export class ParsePositiveIntPipe implements PipeTransform {
  transform(value: string, metadata: ArgumentMetadata): number {
    const parsed = Number(value);
    if (!Number.isInteger(parsed) || parsed <= 0) {
      throw new BadRequestException(`${metadata.data} must be a positive integer`);
    }
    return parsed;
  }
}

ArgumentMetadata tells the pipe what kind of argument it's processing (body, query, param, or custom) and the parameter's name, useful for building descriptive error messages.

Applying a Pipe

@Get(':id')
findOne(@Param('id', ParsePositiveIntPipe) id: number) {}

@UsePipes(ValidationPipe)
@Post()
create(@Body() dto: CreateCatDto) {}

app.useGlobalPipes(new ValidationPipe());
  • Pipes can be applied per-parameter, per-route, per-controller, or globally.
  • Nest ships several built-in pipes: ValidationPipe, ParseIntPipe, ParseBoolPipe, ParseUUIDPipe, ParseArrayPipe, DefaultValuePipe.
  • Throwing inside a pipe automatically produces the corresponding HTTP error response.
  • Pipes run after guards and interceptors' pre-handler logic, but before the route handler itself.

Built-in Pipes Cheatsheet

Pipes shipped with @nestjs/common for common conversions.

Pipe Purpose
ValidationPipe Validates a value against a DTO's class-validator rules
ParseIntPipe Converts and validates a value as an integer
ParseFloatPipe Converts and validates a value as a float
ParseBoolPipe Converts "true"/"false" strings to booleans
ParseUUIDPipe Validates a value as a UUID
ParseArrayPipe Parses and validates an array of values
DefaultValuePipe Supplies a default when a value is undefined

Transformation Pipes vs. Validation Pipes

Pipes serve two overlapping purposes: transformation (converting a string to a number, applying a default) and validation (rejecting values that don't meet certain rules). Many built-in pipes, like ParseIntPipe, do both at once.

Combining Multiple Pipes

A single parameter can pass through multiple pipes in sequence, each receiving the previous pipe's output, useful for combining a default value with type conversion.

@Get()
findAll(@Query('limit', new DefaultValuePipe(20), ParseIntPipe) limit: number) {
  return this.catsService.findAll(limit);
}

Common Mistakes

  • Confusing pipes (data transformation/validation) with guards (authorization decisions).
  • Applying ValidationPipe only at the route level when most projects benefit from it globally.
  • Forgetting DefaultValuePipe needs to run before ParseIntPipe in the pipe list to supply a fallback first.
  • Writing a custom pipe that mutates its input in place instead of returning a new transformed value.

Key Takeaways

  • Pipes implement PipeTransform and either transform or validate handler arguments.
  • Pipes can be scoped to a parameter, route, controller, or the whole application.
  • Built-in pipes cover the most common numeric, boolean, UUID, and array conversions.
  • Multiple pipes can be chained, each receiving the previous pipe's output.

Pro Tip

Reach for a built-in pipe (ParseIntPipe, ParseUUIDPipe, DefaultValuePipe) before writing a custom one, the vast majority of parameter conversion needs are already covered, and custom pipes are best reserved for domain-specific rules.