Transform or validate input data before it reaches the route handler.

Signature

import { ArgumentMetadata, BadRequestException, Injectable, PipeTransform } from "@nestjs/common"
 
@Injectable()
export class ParseIntPipe implements PipeTransform<string, number> {
  transform(value: string, metadata: ArgumentMetadata): number {
    const parsed = parseInt(value, 10)
    if (isNaN(parsed)) throw new BadRequestException()
    return parsed
  }
}

Generate with the CLI

nest generate pipe parse-int   # full form
nest g pi parse-int            # short alias → src/parse-int/parse-int.pipe.ts
nest g pi parse-int --flat     # no wrapping folder → src/parse-int.pipe.ts
nest g pi common/trim          # nested path → src/common/trim/trim.pipe.ts
nest g pi common/trim --flat   # nested + flat → src/common/trim.pipe.ts
nest g pi parse-int --no-spec  # skip the *.spec.ts test file
nest g pi parse-int --dry-run  # preview the file plan, write nothing

Creates <name>.pipe.ts (and <name>.pipe.spec.ts unless --no-spec). The nest CLI wraps the file in a folder named after the element by default; pass --flat to drop it directly in the target path. Source: @nestjs/cli generate command, Nest CLI usages.

Why a pipe, not middleware / a guard / an interceptor

  • Per-argument scope: a pipe receives one handler argument (@Body(), @Query('id'), …) plus its metatype, not the full Request. That is what makes ValidationPipe automatic: it looks up the DTO class from the metatype and runs class-validator against just that value.
  • Transform or reject: return a value to pass it on (optionally coerced/sanitized), throw to reject with 400 BadRequestException by default. There is no next(), no response stream, no Observable.
  • Wrong layer for other jobs: authorization belongs in a guard (boolean decision, 403); wrapping or timing belongs in an interceptor (sees the response); request-shape mutation across many routes belongs in middleware (handler args don’t exist there yet).

Source: Pipes intro.

Built-in pipes

All exported from @nestjs/common.

PipePurposeNotes
ValidationPipeDTO validation/transformationUses class-validator + class-transformer (peer deps you install)
ParseIntPipestring → integerRegex ^-?\d+$. Throws BadRequestException by default. See composing pipes
ParseFloatPipestring → floatparseFloat + isFinite check
ParseBoolPipe"true"/"false" → booleanOnly those two strings (or actual booleans) pass
ParseArrayPipestring → arraySplits on , by default; override with new ParseArrayPipe({ separator: ';' }). Wraps a ValidationPipe({ transform: true }) to coerce items
ParseUUIDPipeUUID string validationversion?: '3' | '4' | '5' | '7' (default: any version)
ParseEnumPipeenum membership checkConstructor requires the enum. See composing pipes
ParseDatePipestring/number → Datenew Date(value); supports default: () => Date
DefaultValuePipefallback when nilReturns default when value is null, undefined, or NaN. See the section below
ParseFilePipeupload validationCompose MaxFileSizeValidator + FileTypeValidator directly, or use the fluent ParseFilePipeBuilder. See File uploads recipe

Common options across Parse* pipes

OptionDefaultWhat it does
errorHttpStatusCode400Status used when validation fails
exceptionFactoryBadRequestExceptionBuild a custom exception from the error string
optionalfalseWhen true, nil values pass through instead of throw

Binding

ScopeHow
Globalapp.useGlobalPipes() or the [[nestjs/fundamentals/global-providers|APP_PIPE provider]]
Controller@UsePipes() on the class
Route@UsePipes() on the method
Param@Body(new ValidationPipe())

Pass the class to @UsePipes, not an instance

@UsePipes(MyPipe) is resolved by Nest’s DI container so the pipe’s constructor injections work. @UsePipes(new MyPipe()) skips DI: any injected dependency is undefined and the pipe crashes the first time it touches it. The param-level form @Body(new ValidationPipe({ whitelist: true })) is a deliberate exception — built-in pipes like ValidationPipe take a stateless options object rather than DI-resolved dependencies, so the instance form is idiomatic there. Same trap covered in detail at Guards > Binding.

The global-scope variant of the same DI question — useGlobalPipes(new X()) vs APP_PIPE — has its own dedicated note: Global pipes, guards, interceptors, and filters via DI. It covers the side-by-side comparison, request-scope and hybrid-app implications, and when to reach for useClass vs useFactory.

Order: the param level reversal

Standard order is global, controller, route. But at the route parameter level, pipes run from the last parameter to the first:

import { Body, Controller, Param, Patch, Query, UsePipes } from "@nestjs/common"
 
@UsePipes(GeneralValidationPipe)
@Controller("cats")
export class CatsController {
  @UsePipes(RouteSpecificPipe)
  @Patch(":id")
  updateCat(
    @Body() body: UpdateCatDTO,
    @Param() params: UpdateCatParams,
    @Query() query: UpdateCatQuery,
  ) {}
}
// GeneralValidationPipe runs on: query, then params, then body.
// Then RouteSpecificPipe runs in the same reversed order.

DefaultValuePipe

Returns its constructor argument when the incoming value is null, undefined, or NaN. Order matters when chaining:

import { Controller, DefaultValuePipe, Get, ParseIntPipe, Query } from "@nestjs/common"
 
@Controller("cats")
export class CatsController {
  @Get()
  list(
    @Query("page", new DefaultValuePipe(1), ParseIntPipe) page: number,
    @Query("size", new DefaultValuePipe(10), ParseIntPipe) size: number,
  ) {}
}

DefaultValuePipe runs first so ParseIntPipe receives a number, not undefined. Reverse the order and ParseIntPipe would throw on missing query params.

What "missing" means

The default kicks in for null, undefined, and NaN. An empty string (?page=) is not nil, so it passes through and ParseIntPipe will throw. If you need to treat empty strings as missing, normalize upstream (e.g., a custom pipe).

ValidationPipe

Deep dive lives in the validation recipe

This section is a reference for the option flags. For end-to-end DTO patterns — global setup, whitelist, transform, validation groups, nested objects, custom validators, exceptionFactory — see the recipe.

Install peer deps:

npm i class-validator class-transformer

Built-in options (from ValidationPipeOptions)

OptionDefaultWhat it does
transformfalseRun class-transformer to instantiate DTO classes from plain objects. Required if you want primitives coerced or DTO methods to work
transformOptionsundefinedForwarded to class-transformer. Common: enableImplicitConversion: true to coerce strings → number/boolean based on TS types
disableErrorMessagesfalseHide validation messages in the response (use in production)
errorHttpStatusCode400Status used when validation fails (e.g., set to 422)
exceptionFactoryflattens to BadRequestExceptionCustom exception shape
validateCustomDecoratorsfalseValidate args from custom param decorators too
expectedTypeundefinedForce the type to validate against (overrides metatype)

Inherited class-validator options (subset)

OptionDefaultWhat it does
whitelistfalseStrip properties without validation decorators
forbidNonWhitelistedfalseThrow instead of stripping
forbidUnknownValuesfalseReject unknown objects. Nest forces false even though class-validator’s own default is true (issue #10683)
skipMissingPropertiesfalseSkip validation for null/undefined props
stopAtFirstErrorfalseStop at the first failing decorator per property
groupsundefinedValidation groups — same DTO, different rules per route. See the validation recipe

Full table: Validation docs.

Alternative: Zod

zod is not built into Nest, but the official docs include a Zod-based custom pipe example.

Common recipes

Common errors

SymptomLikely cause
DTO instance methods are undefinedMissing transform: true — you got a plain object
Numbers arrive as stringsAdd transformOptions: { enableImplicitConversion: true } or use @Type(() => Number) from class-transformer
Extra fields appear in DTOEnable whitelist: true to strip them
Validation always passesPipe not bound globally, or DTO class lacks decorators
ParseIntPipe throws on optional paramEither provide a DefaultValuePipe first, or pass { optional: true } to ParseIntPipe

Gotchas

When to reach for it

  • DTO validation with class-validator and ValidationPipe.
  • String to number or string to UUID coercion.
  • Trim, lowercase, normalize input shape.

When not to

  • Authorization decisions: use a guard. Pipes run after guards in the lifecycle and have no concept of “deny this request”.
  • Mutating the raw request before any handler-level concern: use middleware — pipes only see one argument at a time, not the whole request object.
  • Wrapping the response or timing the handler: that’s an interceptor. Pipes don’t run on the way out.
  • Catching a thrown error to reshape it: use an exception filter. A pipe’s job ends at “throw”.

See also