A DTO (Data Transfer Object) defines the shape of data moving in or out of your API. This lesson explains why NestJS favors class-based DTOs and how they integrate with validation.
What Is a DTO?
A DTO is a plain class describing the expected shape of data for a specific operation, most commonly a request body. Unlike a TypeScript interface, a class exists at runtime, which is exactly what ValidationPipe needs to check incoming data against validation decorators.
Each class-validator decorator adds a runtime validation rule that ValidationPipe checks automatically before the handler ever runs.
Why a Class, Not an Interface
interface CreateCatDto { name: string; age: number; } // erased at runtime
class CreateCatDto { name: string; age: number; } // exists at runtime
TypeScript interfaces are compile-time only and disappear entirely from the compiled JavaScript.
Classes are preserved at runtime, letting ValidationPipe inspect their decorators.
DTOs are typically named by intent: CreateCatDto, UpdateCatDto, FindCatsQueryDto.
A single resource commonly has several DTOs, one per operation, rather than one shared shape.
class-validator Decorator Cheatsheet
The most common validation decorators used inside DTOs.
Decorator
Validates
@IsString()
Value is a string
@IsInt() / @IsNumber()
Value is an integer / number
@IsBoolean()
Value is a boolean
@IsEmail()
Value is a valid email address
@IsOptional()
Field may be omitted
@Min() / @Max()
Numeric lower/upper bound
@IsEnum(SomeEnum)
Value matches one of an enum's values
@ValidateNested()
Validates a nested DTO object
Deriving DTOs With Mapped Types
The @nestjs/mapped-types (or @nestjs/swagger) package provides helpers like PartialType, PickType, and OmitType to derive new DTOs from an existing one, avoiding duplicated field definitions.
import { PartialType, OmitType } from '@nestjs/mapped-types';
export class UpdateCatDto extends PartialType(CreateCatDto) {}
export class PublicCatDto extends OmitType(CreateCatDto, ['internalNotes'] as const) {}
Validating Nested Objects and Arrays
When a DTO contains a nested object or array of objects, @ValidateNested() combined with @Type() from class-transformer ensures the nested class's own validation rules are actually enforced.
import { Type } from 'class-transformer';
import { ValidateNested, IsArray } from 'class-validator';
export class CreateOrderDto {
@IsArray()
@ValidateNested({ each: true })
@Type(() => OrderItemDto)
items: OrderItemDto[];
}
Common Mistakes
Using a TypeScript interface for a DTO, then wondering why ValidationPipe doesn't validate anything.
Reusing one DTO across create, update, and response serialization when their needs actually differ.
Forgetting @ValidateNested() and @Type() on nested objects, silently skipping their validation.
Putting business logic inside a DTO class instead of keeping it a plain data shape.
Key Takeaways
DTOs are classes (not interfaces) so their validation decorators exist at runtime.
class-validator decorators declare validation rules directly on DTO properties.
Mapped types (PartialType, OmitType, PickType) derive related DTOs without duplication.
Nested objects need @ValidateNested() plus @Type() for their validation to actually run.
Pro Tip
Create a dedicated DTO for every distinct shape of data crossing your API's boundary, even if two DTOs look nearly identical today. Sharing one DTO between unrelated operations is one of the most common sources of "why is this field required here but not there" bugs later.
You now understand DTOs and how they enable validation. Next, dive deeper into Validation itself and how ValidationPipe enforces these rules.