NestJS DTOs

A DTO (Data Transfer Object) is a class that defines the shape of data moving between layers of your application. In NestJS, DTOs describe what data a client must send in a request body. They act as a formal contract between the client and the API.

Why DTOs Exist

Without a DTO, a controller method receives a generic object of unknown shape. You have no idea what fields it contains, whether they have the right types, or whether required fields are present. Your code must guess, check, and defend against every possibility.

A DTO removes this uncertainty. You define exactly what fields are expected, what types they should be, and which ones are required. TypeScript enforces the shape at compile time. Validation libraries enforce it at runtime.

Think of a DTO as a customs declaration form at an airport. Every item entering the country must be declared on the form. Items not on the form get flagged. The customs officer (NestJS validation) checks each declaration and rejects anything that does not comply.

Creating a DTO

A DTO is a plain TypeScript class. Create a dto folder inside your feature folder and name the file descriptively:

src/
└── users/
      └── dto/
            ├── create-user.dto.ts
            └── update-user.dto.ts
// create-user.dto.ts
export class CreateUserDto {
  name: string;
  email: string;
  age: number;
  role?: string;   // optional field
}

Using a DTO in a Controller

@Post()
create(@Body() createUserDto: CreateUserDto) {
  return this.usersService.create(createUserDto);
}

NestJS reads the body and passes it typed as CreateUserDto. Your IDE gives you autocomplete on createUserDto.name, createUserDto.email, and every other declared field. TypeScript warns you if you access a field that does not exist on the DTO.

DTO Without Validation — What Happens

Typing alone does not stop a client from sending garbage data. TypeScript types exist only at compile time. At runtime, if the client sends a request without the required fields, the DTO simply has undefined values — no error is thrown unless you add validation.

Request body sent: { "role": "admin" }
  → name is undefined
  → email is undefined
  → age is undefined

Without validation: the controller still runs
With validation (ValidationPipe + class-validator): 400 Bad Request

Validation decorators from the class-validator package and NestJS's ValidationPipe complete the DTO setup. Those are covered in the next two topics.

The UpdateUserDto and PartialType

For PATCH endpoints, all fields are optional — the client may update one, some, or all fields. Instead of duplicating the DTO with every field marked ?, NestJS provides the PartialType utility:

// update-user.dto.ts
import { PartialType } from '@nestjs/mapped-types';
import { CreateUserDto } from './create-user.dto';

export class UpdateUserDto extends PartialType(CreateUserDto) {}

PartialType copies all fields from CreateUserDto and marks them optional automatically. It also copies all validation decorators, so partial updates still validate the fields that are present.

Other Mapped Type Utilities

Utility         | What It Does
----------------|--------------------------------------------------
PartialType()   | All fields become optional
PickType()      | Keeps only the specified fields
OmitType()      | Removes specified fields
IntersectionType() | Combines two DTOs into one
// A DTO with only name and email from CreateUserDto
export class LoginDto extends PickType(CreateUserDto, ['email'] as const) {}

// A DTO without the 'role' field
export class PublicUserDto extends OmitType(CreateUserDto, ['role'] as const) {}

DTOs vs Entities

A DTO and a database entity often look similar but serve different purposes:

DTO                            | Entity
-------------------------------|----------------------------------
Defines API input/output shape | Defines database table structure
Used in controller layer       | Used in database layer
No database column decorators  | Has @Column, @PrimaryKey, etc.
May exclude sensitive fields   | Has all fields the DB stores

Keep DTOs and entities separate. Exposing your database entity directly through the API leaks internal structure and makes it hard to change your database schema without breaking your API contract.

Response DTOs

DTOs also describe the data your API sends back. A response DTO might exclude sensitive fields like passwords or internal IDs that clients should not see:

// user-response.dto.ts
export class UserResponseDto {
  id: number;
  name: string;
  email: string;
  // No password field
  // No internalId field
}

NestJS's ClassSerializerInterceptor can automatically transform service responses into response DTOs, stripping excluded fields before the data reaches the client.

DTOs sit at the boundary between the outside world and your application. Every piece of data that enters or leaves your API passes through a DTO. They define expectations clearly, enable automatic validation, and protect both your application logic and your database structure from the outside.

Leave a Comment

Your email address will not be published. Required fields are marked *