Pipes in NestJS v10 are powerful tools for preprocessing request data before controller methods are executed. They can be used for data transformation, validation, and cleanup.
Introduction to Pipes
Pipes are a core concept in NestJS, enabling developers to transform and validate incoming parameters before they reach controller methods. Pipes can be applied globally across the application or locally to specific controllers or methods.
Using Pipes
Creating a Pipe
To create a pipe, you need to implement the PipeTransform interface or, optionally, the TransformPipeOptions interface.
// src/pipes/validation.pipe.ts
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';
@Injectable()
export class ValidationPipe implements PipeTransform<any> {
async transform(value: any, metadata: ArgumentMetadata): Promise<any> {
const { metatype } = metadata;
if (!metatype || !this.toValidate(metatype)) {
return value;
}
const object = plainToClass(metatype, value);
const errors = await validate(object);
if (errors.length > 0) {
throw new BadRequestException('Validation failed');
}
return value;
}
private toValidate(metatype: Function): boolean {
const types = [String, Boolean, Number, Array, Object];
return !types.includes(metatype);
}
}Validation with Joi
Joi is a powerful schema description language and data validator that can be used with NestJS pipes.
// src/pipes/joi-validation.pipe.ts
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import * as Joi from '@hapi/joi';
@Injectable()
export class JoiValidationPipe implements PipeTransform<any> {
constructor(private schema: Joi.Schema) {}
transform(value: any, metadata: ArgumentMetadata): any {
const { error } = this.schema.validate(value);
if (error) {
throw new BadRequestException('Validation failed');
}
return value;
}
}Applying Pipes
Pipes can be applied at the method level or globally across the application.
// src/controllers/users.controller.ts
import { Controller, Post, Body, UsePipes, ValidationPipe } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
@Controller('users')
export class UsersController {
@Post()
@UsePipes(new ValidationPipe())
create(@Body() createUserDto: CreateUserDto) {
// ...
}
}Global Pipes
You can set a global pipe to avoid declaring pipes for each controller or method.
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from './pipes/validation.pipe';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();Local Pipes
Local pipes can be applied to specific controllers or methods.
// src/controllers/users.controller.ts
import { Controller, Post, Body, UsePipes, ValidationPipe } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
@Controller('users')
export class UsersController {
@Post()
@UsePipes(new ValidationPipe())
create(@Body() createUserDto: CreateUserDto) {
// ...
}
}Custom Error Messages
You can customize error messages to provide more user-friendly feedback when validation fails.
// src/pipes/validation.pipe.ts
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';
@Injectable()
export class ValidationPipe implements PipeTransform<any> {
async transform(value: any, metadata: ArgumentMetadata): Promise<any> {
const { metatype } = metadata;
if (!metatype || !this.toValidate(metatype)) {
return value;
}
const object = plainToClass(metatype, value);
const errors = await validate(object);
if (errors.length > 0) {
const messages = errors.map(err => Object.values(err.constraints).join(', ')).join(', ');
throw new BadRequestException(messages);
}
return value;
}
private toValidate(metatype: Function): boolean {
const types = [String, Boolean, Number, Array, Object];
return !types.includes(metatype);
}
}Using with DTOs
Pipes are commonly used with DTOs (Data Transfer Objects) to ensure incoming data conforms to the expected structure.
// src/dto/create-user.dto.ts
import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
export class CreateUserDto {
@IsString()
@IsNotEmpty()
name: string;
@IsEmail()
email: string;
}Joi Validation
If you prefer using Joi for validation, you can create a Joi validation pipe and apply it to controller methods.
// src/pipes/joi-validation.pipe.ts
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import * as Joi from '@hapi/joi';
@Injectable()
export class JoiValidationPipe implements PipeTransform<any> {
constructor(private schema: Joi.Schema) {}
transform(value: any, metadata: ArgumentMetadata): any {
const { error } = this.schema.validate(value);
if (error) {
throw new BadRequestException('Validation failed');
}
return value;
}
}
// src/controllers/users.controller.ts
import { Controller, Post, Body, UsePipes, ValidationPipe } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { JoiValidationPipe } from './pipes/joi-validation.pipe';
const createUserSchema = Joi.object({
name: Joi.string().required(),
email: Joi.string().email().required(),
});
@Controller('users')
export class UsersController {
@Post()
@UsePipes(new JoiValidationPipe(createUserSchema))
create(@Body() createUserDto: CreateUserDto) {
// ...
}
}



