Pipes: Data Transformation and Validation
Using built-in pipes and creating custom pipes to transform and validate request data.
NestJS Pipes: Data Transformation and Validation
Introduction to Pipes
In NestJS, Pipes are a powerful feature used for transforming and validating request data before it reaches your route handler. They act as a bridge between the client request and your application logic, ensuring data integrity and consistency. Essentially, a pipe takes input data, operates on it, and then returns it, either transformed or with validation errors if the data doesn't meet the defined criteria.
Pipes provide several benefits:
- Data Validation: Enforce data integrity by checking if the input data conforms to expected types, formats, and constraints.
- Data Transformation: Modify the input data, such as converting strings to numbers, applying date formatting, or sanitizing user input.
- Code Reusability: Pipes can be reused across multiple routes and controllers, promoting DRY (Don't Repeat Yourself) principles.
- Declarative Syntax: Define validation and transformation logic in a clear and concise manner.
- Error Handling: Gracefully handle validation errors and provide informative feedback to the client.
Using Built-in Pipes
NestJS provides several built-in pipes that can handle common validation and transformation tasks. Some of the most frequently used built-in pipes include:
ValidationPipe
: Uses class-validator and class-transformer to validate incoming request bodies based on DTO (Data Transfer Object) classes with validation decorators.ParseIntPipe
: Parses a string into an integer. Throws an exception if the parsing fails.ParseFloatPipe
: Parses a string into a floating-point number. Throws an exception if the parsing fails.ParseBoolPipe
: Parses a string into a boolean value. Throws an exception if the parsing fails.ParseArrayPipe
: Parses a string into an array.ParseUUIDPipe
: Validates if a string is a valid UUID.DefaultValuePipe
: Provides a default value if the input is undefined.ParseEnumPipe
: Validates if the input is a valid enum value.
Example: Using ParseIntPipe
The following example demonstrates how to use the ParseIntPipe
to ensure that a route parameter is a valid integer:
import { Controller, Get, Param, ParseIntPipe } from '@nestjs/common';
@Controller('items')
export class ItemsController {
@Get(':id')
getItem(@Param('id', ParseIntPipe) id: number): string {
return `Item ID: ${id}`;
}
}
In this example, the ParseIntPipe
is applied to the id
parameter. If the value passed in the request is not a valid integer, NestJS will automatically throw a BadRequestException
.
Example: Using ValidationPipe
with DTOs
To use the ValidationPipe
, you'll typically define a DTO (Data Transfer Object) class and use validation decorators from the class-validator
library.
// src/dto/create-item.dto.ts
import { IsString, IsNotEmpty, IsNumber, Min } from 'class-validator';
export class CreateItemDto {
@IsString()
@IsNotEmpty()
name: string;
@IsNumber()
@Min(0)
price: number;
@IsString()
description: string;
}
Now, you can use the ValidationPipe
in your controller:
import { Controller, Post, Body, UsePipes, ValidationPipe } from '@nestjs/common';
import { CreateItemDto } from './dto/create-item.dto';
@Controller('items')
export class ItemsController {
@Post()
@UsePipes(new ValidationPipe())
createItem(@Body() createItemDto: CreateItemDto): string {
console.log(createItemDto); // Validated data
return 'Item created';
}
}
In this example, the ValidationPipe
will validate the createItemDto
against the rules defined in the CreateItemDto
class. If any validation errors occur, NestJS will throw a BadRequestException
with a detailed error message.
Creating Custom Pipes
While NestJS provides a range of built-in pipes, you may need to create custom pipes to handle more specific validation or transformation requirements. To create a custom pipe, you need to implement the PipeTransform
interface from the @nestjs/common
package.
Example: Creating a Custom Transformation Pipe
Let's create a custom pipe that converts a string to uppercase:
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
@Injectable()
export class UppercasePipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata) {
if (typeof value !== 'string') {
throw new BadRequestException('Value must be a string');
}
return value.toUpperCase();
}
}
Now, you can use this custom pipe in your controller:
import { Controller, Get, Param, UsePipes } from '@nestjs/common';
import { UppercasePipe } from './uppercase.pipe';
@Controller('items')
export class ItemsController {
@Get(':name')
getItem(@Param('name', UppercasePipe) name: string): string {
return `Item Name: ${name}`;
}
}
In this example, the UppercasePipe
will convert the name
parameter to uppercase before it's passed to the getItem
method.
Example: Creating a Custom Validation Pipe
Let's create a custom pipe that validates if a string contains only alphabetic characters:
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
@Injectable()
export class AlphabeticOnlyPipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata) {
if (typeof value !== 'string') {
throw new BadRequestException('Value must be a string');
}
if (!/^[a-zA-Z]+$/.test(value)) {
throw new BadRequestException('Value must contain only alphabetic characters');
}
return value;
}
}
Usage is similar to the UppercasePipe. This ensures your input parameter only contains letters.
Applying Pipes Globally
You can apply pipes globally to your entire application by using the useGlobalPipes
method in your main.ts
file:
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();
Applying the ValidationPipe
globally is a common practice to ensure that all incoming requests are validated against your DTOs. Be mindful of the performance impact of global pipes and whether they are required across the entire application.
ArgumentMetadata
The ArgumentMetadata
interface provides information about the argument being processed by the pipe. It includes the following properties:
type
: The type of the argument (e.g., 'body', 'query', 'param', 'custom').metatype
: The type of the argument (e.g., String, Number, Boolean, CreateItemDto). This is typically the class constructor function. Can be undefined if the type cannot be determined.data
: A string containing the name of the argument (e.g., 'id', 'name'). For @Body() arguments, this is undefined unless specified explicitly.
You can use the ArgumentMetadata
to customize the behavior of your pipe based on the type and metadata of the argument. For example, you might want to apply different validation rules depending on whether the argument is a query parameter or a request body property.