LearnAI ToolsCareerPractice BuildsPlayDevelopmentContact
Lesson 622 min read

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 You Will Learn
  • 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

PipeWhat It Does
ValidationPipeValidates an object against a DTO's class-validator rules.
ParseIntPipe / ParseFloatPipeConverts a string to a number, or throws 400 Bad Request.
ParseBoolPipeConverts "true"/"false" to a boolean.
ParseUUIDPipeChecks that a string is a valid UUID.
ParseEnumPipeChecks that a value belongs to an enum.
ParseArrayPipeParses and validates arrays (e.g. comma-separated query values).
DefaultValuePipeSupplies 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);
}
GET /products/abc

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.ts
import { 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.ts
import { 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:

400 Bad Request

Click Run to see what this code prints.

OptionEffect
whitelist: trueSilently removes any property not declared (with a decorator) on the DTO — blocks fields like isAdmin.
forbidNonWhitelisted: trueThrows 400 instead of silently stripping unknown properties.
transform: trueConverts 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.ts
import { 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

Avoid These 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.

Next Lesson →

Middleware