In NestJS, pipes are a powerful and flexible mechanism for handling data transformations and validation. They allow you to manipulate data, validate inputs, and provide custom logic for incoming request data. By combining validation and transformation in pipes, you can build highly reusable and customizable data-handling logic for your application.
In this article, we’ll explore how to use NestJS pipes for both transformation and validation, two essential features that help ensure your application’s robustness and data integrity.
Table of Contents
- Introduction
- What are Pipes in NestJS?
- Setting Up Validation and Transformation with Pipes
- Creating a Custom Pipe for Validation and Transformation
- Using Built-in ValidationPipe
- Using Custom Pipes for Transformation
- Applying Pipes Globally and Locally
- Best Practices for Using Pipes
- Handling Validation Errors
- Conclusion
Introduction
In web development, data validation ensures that incoming data is correctly structured and valid. Transformation is used to convert data from one format to another, such as transforming plain JavaScript objects into class instances. In NestJS, pipes are the primary tool for both validation and transformation, making them a key part of data handling in your application.
This article will show you how to use NestJS pipes for both validation and transformation, ensuring that your incoming data is both properly formatted and valid before it is processed.
What are Pipes in NestJS?
In NestJS, pipes are classes that operate on incoming data before it reaches your controller’s route handlers. They can be used to validate, transform, or even handle errors in the data.
Pipes in NestJS can be applied globally, at the controller level, or at the individual route handler level. They are incredibly useful for creating reusable logic that can handle common operations such as data validation or transformation.
Key Concepts of Pipes:
- Transformation: Modifying the format of incoming data. For example, converting strings to numbers, or transforming plain objects into class instances.
- Validation: Ensuring the incoming data matches the expected format, such as verifying that a field is a valid email or that an integer is within a specific range.
Setting Up Validation and Transformation with Pipes
To use pipes for validation and transformation in NestJS, we typically use the ValidationPipe
and ClassSerializerInterceptor
with the class-transformer
library.
Install Dependencies
If you haven’t already installed class-validator
and class-transformer
, you can do so by running:
bashCopyEditnpm install class-validator class-transformer
These libraries allow you to define validation rules and transform data as needed.
Creating a Custom Pipe for Validation and Transformation
In some cases, you may need to create your own custom pipes to perform specific data transformations or validations.
Custom Pipe Example
Let’s create a custom pipe that validates whether an age is above 18 and transforms the incoming data by multiplying the age
by 2 before passing it to the controller:
typescriptCopyEditimport { Injectable, PipeTransform, ArgumentMetadata, BadRequestException } from '@nestjs/common';
@Injectable()
export class AgeValidationPipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata) {
// Check if age is present and is a number
if (!value.age || typeof value.age !== 'number') {
throw new BadRequestException('Age must be a number');
}
// Validate age is above 18
if (value.age < 18) {
throw new BadRequestException('Age must be at least 18');
}
// Transform: Multiply age by 2
value.age = value.age * 2;
return value;
}
}
Explanation:
- Validation: We check if the
age
field is present and ensure it is a number. If it is missing or invalid, we throw aBadRequestException
. - Transformation: After validating, we modify the
age
field by multiplying it by 2 before passing it to the controller.
You can use this custom pipe in a controller like this:
typescriptCopyEditimport { Controller, Post, Body, UsePipes } from '@nestjs/common';
import { AgeValidationPipe } from './pipes/age-validation.pipe';
@Controller('users')
export class UserController {
@Post()
@UsePipes(AgeValidationPipe)
createUser(@Body() body: any) {
return `User with age ${body.age} created successfully!`;
}
}
Using Built-in ValidationPipe
NestJS provides a built-in ValidationPipe
that combines both validation and transformation in one pipe. It automatically validates the data based on your DTO (Data Transfer Object) classes and transforms them into class instances.
Here’s how you can use the ValidationPipe
:
- Enable Global Validation in
main.ts
:
typescriptCopyEditimport { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();
- Define a DTO with Validation Rules:
typescriptCopyEditimport { IsString, IsInt, IsEmail, Min, Max } from 'class-validator';
export class CreateUserDto {
@IsString()
firstName: string;
@IsString()
lastName: string;
@IsEmail()
email: string;
@IsInt()
@Min(18)
@Max(100)
age: number;
}
- Use the DTO with Validation in the Controller:
typescriptCopyEditimport { Body, Controller, Post } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
@Controller('users')
export class UserController {
@Post()
createUser(@Body() createUserDto: CreateUserDto) {
return `User ${createUserDto.firstName} created successfully!`;
}
}
In this example, the ValidationPipe
automatically validates the incoming request based on the validation rules defined in the CreateUserDto
.
Benefits of ValidationPipe
:
- Automatic Transformation: It transforms the incoming data into an instance of the DTO class.
- Automatic Validation: It validates the incoming data according to the rules defined in the DTO.
- Global Use: You can apply the
ValidationPipe
globally, making it easier to ensure validation throughout your app.
Using Custom Pipes for Transformation
While the ValidationPipe
handles most validation needs, you may want to create custom transformation logic for specific use cases. For example, you might want to convert all incoming strings to lowercase before processing.
Here’s an example of a custom pipe that transforms an incoming string to lowercase:
typescriptCopyEditimport { Injectable, PipeTransform } from '@nestjs/common';
@Injectable()
export class LowercasePipe implements PipeTransform {
transform(value: any) {
if (typeof value === 'string') {
return value.toLowerCase();
}
return value;
}
}
You can then use the LowercasePipe
in your controller:
typescriptCopyEditimport { Controller, Post, Body, UsePipes } from '@nestjs/common';
import { LowercasePipe } from './pipes/lowercase.pipe';
@Controller('users')
export class UserController {
@Post()
@UsePipes(LowercasePipe)
createUser(@Body() body: any) {
return `User email: ${body.email} created successfully!`;
}
}
Applying Pipes Globally and Locally
You can apply pipes globally or locally in NestJS.
Global Pipes
To apply a pipe globally, use the useGlobalPipes()
method in your main.ts
file:
typescriptCopyEditapp.useGlobalPipes(new ValidationPipe());
This will apply the ValidationPipe
to all incoming requests, ensuring that validation and transformation happen automatically for all routes.
Local Pipes
To apply a pipe to a specific route handler, use the @UsePipes()
decorator:
typescriptCopyEditimport { UsePipes } from '@nestjs/common';
@Controller('users')
export class UserController {
@Post()
@UsePipes(new ValidationPipe())
createUser(@Body() createUserDto: CreateUserDto) {
return `User ${createUserDto.firstName} created successfully!`;
}
}
Best Practices for Using Pipes
- Use Built-in ValidationPipe for Standard Validation: Use the built-in
ValidationPipe
for most validation and transformation needs. - Create Custom Pipes for Specific Needs: For custom validation or transformation logic, create your own pipes.
- Apply Validation Globally: To ensure consistent validation across your application, apply validation globally in the
main.ts
file. - Keep Pipes Reusable: Make your custom pipes reusable by keeping the logic simple and focused on a single task.
Handling Validation Errors
When validation fails, the ValidationPipe
will automatically throw a BadRequestException
with a detailed message. For example:
jsonCopyEdit{
"statusCode": 400,
"message": ["age must be a number", "age must be at least 18"],
"error": "Bad Request"
}
You can customize error handling by extending the ValidationPipe
or using custom exception filters.
Conclusion
Pipes in NestJS provide a clean and efficient way to handle data validation and transformation. Whether using the built-in ValidationPipe
for standard validation or creating custom pipes for complex logic, they ensure that your incoming data is well-formed and ready for processing.