DTOs
- NestJS
- Node.js
- Validation
Data Transfer Objects define the exact shape of data crossing an API boundary, giving each request and response an explicit, validated contract.
Prerequisites
- Controllers and request validation (see Related Topics)
What you'll understand
- What a DTO is and why it defines an API boundary
- How DTOs make request and response shapes explicit
- Why DTOs and validation belong together
Explanation
A DTO (Data Transfer Object) is a simple object that defines the exact shape of data moving across a boundary, most often the body of an API request or the payload of a response. Instead of a controller accepting whatever JSON arrives, it accepts a specific DTO: these fields, of these types, and nothing else. The DTO is the contract.
Making that contract explicit pays off in several ways. It documents precisely what an endpoint expects and returns, it gives you a typed object to work with instead of an untyped blob, and it creates a natural place to attach validation rules (see Related Topics). A CreateUserDto that declares email as a valid email and password as a minimum length turns vague expectations into enforceable ones.
A key discipline is separating your DTOs from your internal data models. The shape you expose over the API is a deliberate choice, not a mirror of your database rows. Keeping an input DTO, an output DTO, and the internal entity distinct lets each change independently and stops internal fields, like a password hash, from leaking into a response by accident.
Examples
This TypeScript example defines an input DTO with validation decorators, so the shape and its rules live in one place:
import { IsEmail, MinLength } from 'class-validator';
export class CreateUserDto {
@IsEmail()
email!: string;
@MinLength(8)
password!: string;
}The controller then accepts the DTO rather than a raw body, and receives a typed, validated object (see Related Topics):
@Post()
create(@Body() input: CreateUserDto) {
return this.users.create(input);
}Common mistakes
- Accepting a raw, untyped request body instead of a defined DTO.
- Reusing a database entity as the API contract, so internal fields leak out.
- Sharing one DTO for input and output when they should differ.
- Defining the shape but attaching no validation, leaving the contract unenforced.
Best practices
- Define a DTO for each request and response shape at your API boundary.
- Keep DTOs separate from internal entities and database models.
- Attach validation rules to input DTOs so the contract is enforced (see Related Topics).
- Expose only the fields a client should see in response DTOs.
Further reading
- NestJS, Request payloads (DTOs) — https://docs.nestjs.com/controllers#request-payloads
- NestJS, Validation — https://docs.nestjs.com/techniques/validation