Implementing Global Request Validation in NestJS with ValidationPipe
Set up a ValidationPipe globally to enforce DTO constraints, strip unknown fields, transform payloads, and customize error responses.
20 Jul 2025, 19:47 UTC

Desired outcome
Apply a single ValidationPipe instance globally so that every incoming request body, query string, and route parameter is automatically validated against a DTO, stripped of undeclared properties, and transformed to the correct Typescript types. Invalid requests should return a 400 Bad Request with a predictable error shape, while valid requests reach the controller as instantiated DTO objects.
Prerequisites
- A NestJS project created with the CLI (
nest new my-app) or an existing application. - Node.js >= 14 and npm or yarn installed.
- Install the peer dependencies required for validation:
# Run in the project root
npm install class-validator class-transformer
# or with yarn
yarn add class-validator class-transformer
Verify that @nestjs/common version matches the ValidationPipe API you intend to use (the guide assumes v9+).
Focused procedure
1. Create a sample DTO
Define a Data Transfer Object that uses class‑validator decorators to describe the expected shape.
// src/users/dto/create-user.dto.ts
import { IsString, IsInt, Min, IsOptional } from 'class-validator';
import { Type } from 'class-transformer';
export class CreateUserDto {
@IsString()
name: string;
@IsInt()
@Min(0)
age: number;
@IsOptional()
@IsString()
email?: string;
}
2. Register the ValidationPipe globally
In the main bootstrap file, instantiate ValidationPipe with the options you need and attach it to the Nest application.
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/pipes';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(
new ValidationPipe({
// Remove properties that are not decorated in the DTO
whitelist: true,
// Reject requests that contain unknown properties
forbidNonWhitelisted: true,
// Automatically transform payloads to class instances
transform: true,
// Enable implicit conversion for query strings (e.g. "42" => 42)
enableImplicitConversion: true,
// Customize the error object format (optional)
exceptionFactory: (errors) => {
const messages = errors.map((err) => ({
property: err.property,
errors: Object.values(err.constraints || {}),
}));
return new BadRequestException(messages);
},
}),
);
await app.listen(3000);
}
bootstrap();
The pipe will now run for every controller method that accepts a DTO via @Body(), @Query(), or @Param().
3. Use the DTO in a controller
Inject the DTO as a typed parameter; Nest will pass the validated and transformed instance.
// src/users/users.controller.ts
import { Controller, Post, Body } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
@Controller('users')
export class UsersController {
@Post()
create(@Body() dto: CreateUserDto) {
// dto is guaranteed to be an instance of CreateUserDto
// with correct types and only whitelisted properties
return { message: 'User created', data: dto };
}
}
4. Verify the behavior
Perform the following checks manually or with an automated test suite.
- Whitelisting: Send a JSON body containing an extra field, e.g. {"name":"Alice","age":30,"role":"admin"}. With
whitelist:truetheroleproperty is removed; withforbidNonWhitelisted:truethe server responds 400 and includes a message about the unexpected property. - Transformation: Send a numeric query string like
?age=twenty. BecauseenableImplicitConversion:trueis set, the pipe will attempt to convert the string; a non‑numeric value will cause a validation error, while a numeric string becomes a realnumberinside the DTO. - Error shape: Trigger a validation failure (e.g. send a negative age). Confirm the response body matches the format defined in
exceptionFactory(an array of objects withpropertyanderrors). - Instance check: In the controller, log
dto instanceof CreateUserDto; it should printtruefor valid requests.
5. Override globally when needed
If a particular endpoint requires looser rules (e.g. allow extra fields), create a local ValidationPipe and apply it with @UsePipes or directly in the parameter decorator.
@Post('loose')
@UsePipes(new ValidationPipe({ whitelist: false, forbidNonWhitelisted: false }))
createLoose(@Body() dto: CreateUserDto) { ... }
Expected checks
- Valid requests reach the handler with a DTO instance and correct types.
- Requests with unknown properties are either stripped or rejected according to the chosen flags.
- Invalid values produce a 400 response containing the customized error array.
- Query string numbers are transformed to
numberwhenenableImplicitConversionis true.
Recovery options
If the global pipe causes unintended side effects:
- Remove the
app.useGlobalPipesline frommain.tsand restart the application. - Re‑apply validation selectively using
@UsePipes(new ValidationPipe())on the routes that still need it. - Adjust the pipe options (e.g. set
whitelist:false) and redeploy.
These steps revert the application to its pre‑validation state without affecting other code.
Limitations and practical notes
- The ValidationPipe only validates the deserialized payload; it does not protect against SQL injection, NoSQL injection, or malformed file uploads. Additional guards or libraries are required for those concerns.
- Implicit conversion can produce surprising results (e.g. the string "0" becomes the number
0, while "" becomes0as well). For strict control, prefer explicit@Type(() => Number)decorators on DTO properties. - Default values of ValidationPipe options may change between NestJS major versions. Consult the changelog for the installed
@nestjs/commonversion to confirm behavior. - If you use custom exception filters globally, ensure they do not swallow the
BadRequestExceptionthrown by the pipe’sexceptionFactory.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.