DTOs, Validation & Pipes
Validate and transform incoming data in NestJS with DTO classes, class-validator decorators, the global ValidationPipe and built-in pipes like ParseIntPipe.
On this page 12 sections
Introduction
Never trust input from the outside world. A client might send a price of "free", an email of 12345, or extra fields like isAdmin: true. In NestJS, the clean way to stop bad data at the door is with pipes — and the most important pipe of all is ValidationPipe, powered by DTO classes.
- What pipes are and the two jobs they do.
- How to use built-in pipes like ParseIntPipe.
- How to describe validation rules on DTOs with class-validator.
- How to enable ValidationPipe globally with whitelist and transform.
- How to reuse DTOs with PartialType and write your own pipe.
What is a Pipe?
A pipe is a class that runs just before a controller method, receiving the arguments that method is about to get. A pipe does one of two jobs:
- Transformation — convert input into the desired form (for example, the string "42" into the number 42).
- Validation — check the input and throw an exception if it is invalid, so the controller method never runs.
Built-in Pipes
| Pipe | What It Does |
|---|---|
| ValidationPipe | Validates an object against a DTO's class-validator rules. |
| ParseIntPipe / ParseFloatPipe | Converts a string to a number, or throws 400 Bad Request. |
| ParseBoolPipe | Converts "true"/"false" to a boolean. |
| ParseUUIDPipe | Checks that a string is a valid UUID. |
| ParseEnumPipe | Checks that a value belongs to an enum. |
| ParseArrayPipe | Parses and validates arrays (e.g. comma-separated query values). |
| DefaultValuePipe | Supplies a default when a param or query value is missing. |
@Get(':id')findOne(@Param('id', ParseIntPipe) id: number) { return this.productsService.findOne(id);}
@Get()findAll( @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number,) { return this.productsService.findAll(page);}Click Run to see what this code prints.
DTOs with class-validator
For request bodies, you describe the rules directly on the DTO class using decorators from the class-validator library. Install it along with class-transformer:
npm install class-validator class-transformer// src/users/dto/create-user.dto.tsimport { IsEmail, IsInt, IsOptional, IsString, Max, Min, MinLength } from 'class-validator';
export class CreateUserDto { @IsString() @MinLength(2) name: string;
@IsEmail() email: string;
@IsString() @MinLength(8) password: string;
@IsOptional() @IsInt() @Min(13) @Max(120) age?: number;}The Global ValidationPipe
Turn on validation for every endpoint at once in main.ts:
// src/main.tsimport { ValidationPipe } from '@nestjs/common';import { NestFactory } from '@nestjs/core';import { AppModule } from './app.module.js';
async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes( new ValidationPipe({ whitelist: true, // strip properties without decorators forbidNonWhitelisted: true, // ...or reject the request entirely transform: true, // turn plain JSON into DTO class instances }), ); await app.listen(process.env.PORT ?? 3000);}await bootstrap();Now if a client POSTs { "name": "A", "email": "not-an-email", "password": "123" } to /users, the controller never runs. Instead Nest responds automatically:
Click Run to see what this code prints.
| Option | Effect |
|---|---|
| whitelist: true | Silently removes any property not declared (with a decorator) on the DTO — blocks fields like isAdmin. |
| forbidNonWhitelisted: true | Throws 400 instead of silently stripping unknown properties. |
| transform: true | Converts the body into an actual DTO instance and converts primitive params to their declared types. |
Update DTOs with PartialType
An update usually accepts the same fields as create, but all optional. Rather than duplicating every rule, derive it with PartialType from @nestjs/mapped-types (installed by nest g resource):
// src/users/dto/update-user.dto.tsimport { PartialType } from '@nestjs/mapped-types';import { CreateUserDto } from './create-user.dto.js';
export class UpdateUserDto extends PartialType(CreateUserDto) {}The same package also provides PickType, OmitType and IntersectionType for building DTOs from other DTOs. (If you use Swagger, import these from @nestjs/swagger instead so the documentation stays accurate.)
Writing a Custom Pipe
A custom pipe implements PipeTransform and its transform() method:
import { BadRequestException, Injectable, PipeTransform } from '@nestjs/common';
@Injectable()export class SlugPipe implements PipeTransform<string, string> { transform(value: string): string { const slug = value.trim().toLowerCase().replace(/\s+/g, '-'); if (!/^[a-z0-9-]+$/.test(slug)) { throw new BadRequestException('Invalid slug'); } return slug; }}
// usage@Get(':slug')findBySlug(@Param('slug', SlugPipe) slug: string) { return this.postsService.findBySlug(slug);}Common Mistakes
- Using a TypeScript interface for a DTO — interfaces vanish at runtime, so nothing gets validated.
- Adding decorators but never registering ValidationPipe, so the rules are silently ignored.
- Forgetting whitelist: true, allowing clients to sneak extra fields (like role or isAdmin) into your database.
- Forgetting to install class-validator and class-transformer — ValidationPipe needs both.
Best Practices
- Register ValidationPipe globally with whitelist, forbidNonWhitelisted and transform.
- Create a separate DTO for each operation (CreateXDto, UpdateXDto) and derive with PartialType.
- Use ParseIntPipe/ParseUUIDPipe on every id parameter.
- Keep validation in DTOs and pipes, not scattered through services with manual if checks.
Frequently Asked Questions
Add @ValidateNested() and @Type(() => AddressDto) (from class-transformer) to the property, so the nested object is converted into its DTO class and validated too.
Yes — every class-validator decorator accepts an options object, e.g. @IsEmail({}, { message: 'Please enter a valid email' }).
Yes. Pipes can be applied to @Param(), @Query() and @Body() arguments, to a single route, to a whole controller or globally.
Key Takeaways
- Pipes transform or validate controller arguments before the handler runs.
- Built-in pipes like ParseIntPipe handle common conversions and return 400 on bad input.
- DTO classes plus class-validator decorators declare validation rules.
- A global ValidationPipe with whitelist and transform protects every endpoint.
- PartialType derives update DTOs without duplicating rules.
Summary
Your API now rejects invalid data automatically, with clear error messages. Next, you will look at middleware — code that runs even earlier in the request, before routing to a specific handler.