Configuring NestJS ValidationPipe for DTO Validation with class-validator and class-transformer
Learn how to install, configure, and test NestJS ValidationPipe with class-validator and class-transformer to validate and transform incoming request payloads into strongly-typed DTO instances.
03 Jan 2026, 19:32 UTC

Desired Outcome
Ensure that every incoming HTTP request payload is validated against a set of rules, stripped of unknown properties, and transformed into a strongly-typed DTO instance before reaching your controller logic.
Prerequisites
- A NestJS project created with the CLI (
nest new my-project) or an existing NestJS application. - Node.js 14 or later and npm installed.
- Write access to the project directory to modify
package.jsonand source files.
Installation
Run the following command in the project root to add the required packages:
npm i @nestjs/pipes class-validator class-transformerWhere to run: Terminal at the project root.
Permissions: Normal user rights; the command writes to node_modules and updates package.json.
Expected check: After installation, package.json should list @nestjs/pipes, class-validator, and class-transformer under dependencies.
Risk: Version conflicts with existing dependencies; review any npm warnings and consider npm install --legacy-peer-deps if needed.
Creating a DTO
Define a Data Transfer Object that declares validation rules using decorators from class-validator. The example below validates a user creation payload.
import { IsString, IsInt, Min, IsEmail } from 'class-validator';
export class CreateUserDto {
@IsString()
name: string;
@IsEmail()
email: string;
@IsInt()
@Min(0)
age: number;
}The DTO is a plain TypeScript class; after validation and transformation (transform: true) NestJS will instantiate it, making any methods you add available in the controller.
Global ValidationPipe Configuration
Apply the pipe once in main.ts so every route inherits the same validation behavior.
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}),
);
await app.listen(3000);
}
bootstrap();Where to run: No command needed; restart the application after saving main.ts.
Permissions: Write access to src/main.ts.
Expected check: The application starts without errors and the pipe is active for all endpoints.
Risk: If a DTO constructor depends on injected services, the pipe's instantiation will fail because it creates plain instances. Keep DTO constructors free of dependencies.
Per-Route ValidationPipe Usage
When different endpoints need distinct validation settings (for example, looser rules for a public endpoint), you can inject a pipe locally.
import { Controller, Post, Body, ValidationPipe } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
@Controller('users')
export class UsersController {
@Post()
create(@Body(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: false })) dto: CreateUserDto) {
return { message: 'User created', user: dto };
}
}Where to run: No special command; save the file and restart the app.
Permissions: Write access to the controller file.
Expected check: Requests to POST /users obey the local pipe settings; other routes continue to use the global pipe.
Risk: Over-using local pipes can scatter configuration; prefer the global pipe unless a clear exception exists.
Testing the Setup
Use curl or a tool like Postman to verify behavior against a running server (npm run start:dev).
Valid request
curl -X POST http://localhost:3000/users \
-H "Content-Type: application/json" \
-d '{"name":"Ada","email":"[contact removed]","age":30}'Expected response: HTTP 200/201 with a JSON body containing the success message and the DTO data.
Missing required field
curl -X POST http://localhost:3000/users \
-H "Content-Type: application/json" \
-d '{"name":"Ada","email":"[contact removed]"}'Expected response: HTTP 400 with a body containing a message array of constraint errors, such as an entry stating that age must be an integer not less than 0.
Extra property with whitelist enabled
curl -X POST http://localhost:3000/users \
-H "Content-Type: application/json" \
-d '{"name":"Ada","email":"[contact removed]","age":25,"role":"admin"}'Expected response: with whitelist: true alone, HTTP success and the role property stripped from the DTO; with forbidNonWhitelisted: true, HTTP 400 with an error about non-whitelisted properties.
Where to run: Terminal with network access to localhost:3000.
Permissions: None beyond normal user.
Expected check: Status codes and response bodies match the descriptions above.
Risk: If the application is not running you will get connection errors; ensure the server is started first.
Limitations and Practical Verification
- ValidationPipe adds runtime overhead; consider whether strict validation is required on every hot path.
- Circular dependencies between DTOs and providers can break metadata resolution; keep DTOs free of provider imports.
- When
transform: trueis used, avoid constructors that rely on injected services; the pipe creates plain instances. - Without
whitelist, unexpected properties pass through, which can cause data injection or schema drift.
To confirm validation is active without manual requests, write a unit test that calls the pipe's transform method directly with a plain object and asserts the result is an instance of your DTO, or use SuperTest in an e2e test to assert both success (200/201) and failure (400) scenarios.
Recovery Options
If you need to revert the changes:
- Remove the packages:
npm uninstall @nestjs/pipes class-validator class-transformer. - Delete or comment out the
app.useGlobalPipescall inmain.ts. - Delete any DTO classes you created for testing.
- Restart the application.
After uninstalling, verify that package.json no longer lists the removed dependencies and that the application starts without validation-related errors.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.