Introduction to Nest.js “Pipe”
Pipes in NestJS are special components used to transform and validate data before it reaches the controller or controller methods. Pipes are useful when we want to ensure that the data we receive in API requests meets certain conditions or is transformed in a specific way.
Nest.js comes with built-in pipes that can be used to transform and validate data. Built-in pipes provide fast and efficient solutions for basic tasks such as validation and transformation. Here are some built-in pipes:
- ValidationPipe: Automatic data validation using decorators.
- ParseIntPipe: Automatic string to number conversion.
- ParseBoolPipe: Automatic string to boolean conversion.
- ParseArrayPipe: Automatic string to array conversion.
- ParseUUIDPipe: Automatic UUID format validation.
- DefaultValuePipe: Setting the default value if no value is defined.
However, if your requirements are specific, you can create “custom pipes” using the “PipeTransform” interface.

ValidationPipe
If “ValidationPipe” is used for DTO validation then we need to install additional libraries (with other built-in pipes this is not necessary). These are the two necessary libraries:
- class-validator: Enables defining and checking validation rules.
- class-transformer: Transforms input data into instances of DTO classes.
Use the following command to install:
|
1 |
npm install class-validator class-transformer |
What is a “class-transformer”?
“class-transformer” is a TypeScript library that enables the conversion (transformation) of plain JavaScript objects into class instances and vice versa. These are usually simple JavaScript objects (obtained, for example, from JSON) into instances of defined classes. It also allows adding logic for custom transformations and working with nested object structures.
Example: JS object to class instance
|
1 2 3 4 5 6 7 8 9 10 11 |
import { plainToClass } from 'class-transformer'; class User { firstName: string; lastName: string; } const plainObject = { firstName: 'John', lastName: 'Doe' }; const user = plainToClass(User, plainObject); console.log(user instanceof User); // true |
Example: class into plain JS object
|
1 2 3 4 5 6 7 8 |
import { classToPlain } from 'class-transformer'; const user = new User(); user.firstName = 'John'; user.lastName = 'Doe'; const plainObject = classToPlain(user); console.log(plainObject); // { firstName: 'John', lastName: 'Doe' } |
What is “class-validator”?
“class-validator” is a TypeScript library that allows defining and checking validation rules for JavaScript objects. This library uses decorators to define validation rules, such as type checking, length, minimum and maximum values, required fields, regular expressions and many other rules.
These are the most commonly used:
- @IsString(): Checks if the value is a string.
- @IsInt(): Checks if the value is an integer.
- @IsBoolean(): Checks if the value is boolean.
- @IsEmail(): Checks if the value is a valid email address.
- @IsNotEmpty(): Checks if the value is empty.
- @MinLength(): Checks the minimum length of a string.
- @MaxLength(): Checks the maximum length of a string.
- @Min(): Checks the minimum value of a number.
- @Max(): Checks the maximum value of a number.
- @Matches(): Checks if a value matches a regular expression.
You can find a list of all decorators at this link.
Example
|
1 2 3 4 5 6 |
import { IsString } from 'class-validator'; export class CreateUserDto { @IsString() name: string; } |
ValidationPipe options
The Validation Pipe in NestJS has several useful options for customizing the validation behavior. These options provide more granular control over how validation will be performed and help handle unnecessary or invalid data. The main options you can use with Validation Pipe are explained below.
a) Whitelist option
The whitelist option is used to automatically remove all properties that are not defined in the DTO (Data Transfer Object) class. When this option is enabled, any additional data not explicitly defined in the DTO class will be ignored, ensuring that only the desired properties are processed.
Example:
|
1 2 3 4 5 |
@UsePipes(new ValidationPipe({ whitelist: true })) @Post() createUser(@Body() createUserDto: CreateUserDto) { return createUserDto; } |
b) Option forbidNonWhitelisted
The option forbidNonWhitelisted works in combination with whitelist. When enabled, this option throws an error whenever a field that is not defined in the DTO class occurs, instead of just ignoring it. This is useful if you want to strictly control the data and prevent passing any additional information that does not belong to the required structure.
Example:
|
1 2 3 4 5 |
@UsePipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true })) @Post() createUser(@Body() createUserDto: CreateUserDto) { return createUserDto; } |
c) Transform option
The transform option enables automatic conversion of input data into instances of DTO classes. When enabled, input data will automatically be transformed into an instance of the DTO class, allowing easier access to methods and types within the object.
Example:
|
1 2 3 4 5 |
@UsePipes(new ValidationPipe({ transform: true })) @Post() createUser(@Body() createUserDto: CreateUserDto) { return createUserDto; } |
ValidatePipe at parameter level
ValidationPipe can be applied to an individual route parameter to validate input data.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import { Controller, Get, Param, ValidationPipe } from '@nestjs/common'; import { IsUUID } from 'class-validator'; class FindUserDto { @IsUUID() id: string; } @Controller('users') export class UsersController { @Get(':id') findOne(@Param('id', new ValidationPipe()) id: string) { return `User with ID: ${id}`; } } |
If the user sends a request with an invalid id (eg not a UUID), the API returns an error:
|
1 2 3 4 5 |
{ "statusCode": 400, "message": ["id must be a UUID"], "error": "Bad Request" } |
ValidatePipe at method level
The @UsePipes decorator is used to implement ValidationPipe at the controller method level.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import { Controller, Post, Body, UsePipes, ValidationPipe } from '@nestjs/common'; import { IsString, IsInt, Min } from 'class-validator'; class CreateUserDto { @IsString() name: string; @IsInt() @Min(1) age: number; } @Controller('users') export class UsersController { @Post() @UsePipes(ValidationPipe) createUser(@Body() createUserDto: CreateUserDto) { return `User created with name: ${createUserDto.name}, age: ${createUserDto.age}`; } } |
Valid request:
|
1 2 3 4 |
{ "name": "John", "age": 25 } |
Invalid request:
|
1 2 3 4 |
{ "name": "", "age": -1 } |
The API responds with an error:
|
1 2 3 4 5 6 7 8 |
{ "statusCode": 400, "message": [ "name should not be empty", "age must not be less than 1" ], "error": "Bad Request" } |
ValidatePipe at the class level
The @UsePipes decorator can be applied to a controller class, making ValidationPipe apply to all methods within that class.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
import { Controller, Post, Body, UsePipes, ValidationPipe } from '@nestjs/common'; import { IsString, IsInt, Min } from 'class-validator'; class CreateUserDto { @IsString() name: string; @IsInt() @Min(1) age: number; } @UsePipes(ValidationPipe) @Controller('users') export class UsersController { @Post() createUser(@Body() createUserDto: CreateUserDto) { return `User created with name: ${createUserDto.name}, age: ${createUserDto.age}`; } @Post('update') updateUser(@Body() updateUserDto: CreateUserDto) { return `User updated with name: ${updateUserDto.name}, age: ${updateUserDto.age}`; } } |
Advantage: All requests within the controller are automatically validated, which reduces the need for individual declarations.
ValidatePipe globally
ValidationPipe can be registered as a global Pipe, thus applying it to all routes in the application.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); // ValidationPipe global registration app.useGlobalPipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true, })); await app.listen(3000); } bootstrap(); |
Options for ValidationPipe:
- whitelist: true – Automatically removes properties that are not defined in the DTO class.
- forbidNonWhitelisted: true – Throws an error if unknown properties occur.
- transform: true – Transforms input data into instances of DTO classes.
Advantage: Validation is applied automatically to all requests, without the need to declare it in each controller.
Custom pipes
If your requirements are specific, then it is not necessary to create “custom pipes”. To create pipes, you need:
- You create a new class that implements the PipeTransform interface.
- You implement the transform() method, which receives two parameters:
- value: The input value coming from the request.
- metadata: Value information (optionally contains type and decorator data).
Example 1: Pipes for number validation
We create a Pipe that checks if the input string is a valid number. If not, it throws an error.
|
1 2 3 4 5 6 7 8 9 10 11 12 |
import { PipeTransform, Injectable, BadRequestException } from '@nestjs/common'; @Injectable() export class ParseIntPipe implements PipeTransform { transform(value: any): number { const val = parseInt(value, 10); if (isNaN(val)) { throw new BadRequestException('Validation failed: Not a number'); } return val; } } |
Usage in controller:
|
1 2 3 4 5 6 7 8 9 10 |
import { Controller, Get, Param } from '@nestjs/common'; import { ParseIntPipe } from './parse-int.pipe'; @Controller('users') export class UsersController { @Get(':id') findOne(@Param('id', ParseIntPipe) id: number) { return `User with ID ${id}`; } } |
If the client sends a request to /users/123, the value 123 is successfully converted to a number.
If it sends /users/abc, the API throws an error:
|
1 2 3 4 5 |
{ "statusCode": 400, "message": "Validation failed: Not a number", "error": "Bad Request" } |
Example 2: Pipes for allowed values
Pipe checks if the value is from the list of allowed values.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import { PipeTransform, Injectable, BadRequestException } from '@nestjs/common'; @Injectable() export class AllowedValuesPipe implements PipeTransform { constructor(private readonly allowedValues: any[]) {} transform(value: any): any { if (!this.allowedValues.includes(value)) { throw new BadRequestException(`Value '${value}' is not allowed.`); } return value; } } |
Usage in controller:
|
1 2 3 4 5 6 7 8 9 10 |
import { Controller, Get, Query } from '@nestjs/common'; import { AllowedValuesPipe } from './allowed-values.pipe'; @Controller('products') export class ProductsController { @Get() findByType(@Query('type', new AllowedValuesPipe(['electronics', 'furniture'])) type: string) { return `Products of type: ${type}`; } } |
Valid claim: /products?type=electronics
Invalid request: /products?type=clothing
|
1 2 3 4 5 |
{ "statusCode": 400, "message": "Value 'clothing' is not allowed.", "error": "Bad Request" } |
Example 3: Pipes for transformation to large text
Pipe transforms the string to uppercase.
|
1 2 3 4 5 6 7 8 |
import { PipeTransform, Injectable } from '@nestjs/common'; @Injectable() export class UppercasePipe implements PipeTransform { transform(value: string): string { return value.toUpperCase(); } } |
Usage in controller:
|
1 2 3 4 5 6 7 8 9 10 |
import { Controller, Post, Body } from '@nestjs/common'; import { UppercasePipe } from './uppercase.pipe'; @Controller('messages') export class MessagesController { @Post() createMessage(@Body('text', UppercasePipe) text: string) { return `Message: ${text}`; } } |
Input: {“text”: “hello world”}
Output: Message: HELLO WORLD
