NestJS ValidationPipe with class-validator: global setup and limits
Register ValidationPipe globally with transform and whitelist to get automatic 400 responses for bad payloads, class instances in handlers, and stripping of unknown fields. Learn the mechanism, a minimal config, and common pitfalls.
19 Mar 2026, 05:57 UTC

The practical problem: stop bad payloads before they reach handlers
If you want request bodies to be checked automatically and converted to typed DTOs, register NestJS ValidationPipe globally with class-validator decorators. The useful takeaway is a single bootstrap change that gives you 400 responses for constraint violations, automatic stripping of unknown properties, and class instances instead of plain objects.
How ValidationPipe works with class-validator and class-transformer
ValidationPipe is a built-in pipe that intercepts incoming data. It relies on two libraries. class-transformer creates a class instance from plain JSON using reflection metadata. class-validator then runs decorator-based constraints against that instance. Validation only runs when Nest can resolve a metatype for a parameter, which means a class decorated with validators, not a plain TypeScript interface.
Global registration is done in main.ts during bootstrap. The pipe is applied before route handlers execute.
// main.ts
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({
transform: true,
whitelist: true,
forbidNonWhitelisted: false,
transformOptions: { enableImplicitConversion: false }
}));
await app.listen(3000);
}
bootstrap();
With transform: true, the incoming JSON is converted to an instance of the DTO class. With whitelist: true, properties that have no decorator are removed from the object before it reaches the controller. forbidNonWhitelisted: true changes the behavior from silent stripping to an explicit 400 error for unknown properties.
Worked DTO and controller
// create-user.dto.ts
import { IsString, IsNotEmpty, IsEmail } from 'class-validator';
export class CreateUserDto {
@IsString()
@IsNotEmpty()
name: string;
@IsEmail()
email: string;
}
// users.controller.ts
import { Body, Controller, Post } from '@nestjs/common';
import { CreateUserDto } from './create-user.dto';
@Controller('users')
export class UsersController {
@Post()
create(@Body() dto: CreateUserDto) {
// dto is a CreateUserDto instance when transform is enabled
return { received: dto };
}
}
The pipe checks the metatype of the @Body() parameter. Because CreateUserDto is a class with decorators, validation runs. If a constraint fails, the request is rejected with HTTP 400. If transform is enabled, you can verify the type inside the handler with instanceof CreateUserDto.
Typical limits
- Decorator driven only. Validation does not run for interfaces, plain objects, or classes without class-validator decorators. Missing decorators means no check.
- Reflection metadata must be available. class-transformer and class-validator must be installed and the DTO must be a class. TypeScript compiler options that strip metadata will break the pipe.
- Version sensitivity. Option names and defaults differ across NestJS, class-validator, and class-transformer major versions. Confirm options against the installed versions.
- Performance. Reflection and validation add runtime cost. The overhead is modest for small payloads but grows with large bodies, deeply nested DTOs, and custom async validators.
- Whitelist side effects. whitelist: true removes undecorated properties silently. Code that later expects those properties will see undefined even though validation passed.
Common mistakes and how to check them
Forgetting transform. Without transform: true the handler receives a plain object. Type checks and nested validation will not behave as expected. Check by logging dto instanceof CreateUserDto inside the handler.
Using interfaces for DTOs. An interface disappears at runtime, so Nest cannot resolve a metatype and validation is skipped. Use a class.
Assuming whitelist protects the API contract. With whitelist: true unknown fields are stripped. If strict rejection is required, set forbidNonWhitelisted: true so unknown fields produce a 400 instead of silent removal.
Async custom validators returning non-Promise values. A custom validator must return a boolean or a Promise that resolves to boolean. Synchronous truthy returns can cause the pipe to finish before validation completes.
Practical verification steps without claiming test results:
- Bootstrap with the global pipe shown above and send a payload missing a decorated property to a route expecting CreateUserDto. The pipe is designed to reject with HTTP 400.
- Send a payload with an extra field not present in the DTO. With whitelist: true the field should be absent from the controller DTO. With forbidNonWhitelisted: true the request should be rejected.
- Log the parameter type in the controller to confirm transform produced a class instance rather than a plain object.
- Temporarily remove a decorator from a property and resend invalid data for that property to see validation no longer rejects, demonstrating decorator-driven behavior.
Keep DTOs small, explicit, and class-based, and treat ValidationPipe configuration as part of your API contract.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.