How to Use DTO in NestJS: Simple Guide with Examples
In NestJS, a
DTO (Data Transfer Object) is a simple class that defines the shape of data sent over the network or between layers. You create a DTO class with properties and use it in controllers and services to validate and type-check incoming data, often combined with class-validator decorators.Syntax
A DTO in NestJS is a plain TypeScript class that defines the expected data structure. You decorate its properties with validation decorators from class-validator to enforce rules. Then, you use this DTO as a type in controller methods to automatically validate incoming requests.
- class: Defines the DTO structure.
- Properties: Define fields expected in data.
- Decorators: Add validation rules like
@IsString(),@IsNotEmpty(). - Use in controller: Accept DTO as method parameter with
@Body().
typescript
import { IsString, IsNotEmpty } from 'class-validator'; export class CreateItemDto { @IsString() @IsNotEmpty() name: string; @IsString() description?: string; }
Example
This example shows a simple NestJS controller using a DTO to validate incoming POST data. If the data does not meet the rules, NestJS automatically returns a validation error.
typescript
import { Controller, Post, Body } from '@nestjs/common'; import { IsString, IsNotEmpty } from 'class-validator'; import { ValidationPipe } from '@nestjs/common'; export class CreateUserDto { @IsString() @IsNotEmpty() username: string; @IsString() @IsNotEmpty() password: string; } @Controller('users') export class UsersController { @Post() createUser(@Body(new ValidationPipe()) createUserDto: CreateUserDto) { return { message: `User ${createUserDto.username} created successfully!`, }; } }
Output
POST /users with body {"username":"john","password":"1234"}
Response: {"message":"User john created successfully!"}
POST /users with body {"username":"","password":"1234"}
Response: 400 Bad Request - Validation failed for username
Common Pitfalls
Common mistakes when using DTOs in NestJS include:
- Not enabling validation pipe globally or locally, so validation decorators have no effect.
- Using interfaces instead of classes for DTOs, which do not work with decorators.
- Forgetting to import and apply
ValidationPipein controllers or main app. - Not marking optional properties correctly, causing validation errors.
typescript
/* Wrong: Using interface (no decorators work) */ interface UpdateUserDto { username: string; } /* Right: Use class with decorators */ import { IsString, IsOptional } from 'class-validator'; export class UpdateUserDto { @IsString() @IsOptional() username?: string; }
Quick Reference
| Concept | Description | Example |
|---|---|---|
| DTO Class | Defines data shape with validation | class CreateDto { @IsString() name: string; } |
| Validation Decorators | Add rules to DTO properties | @IsNotEmpty(), @IsEmail(), @IsOptional() |
| ValidationPipe | Enables automatic validation | @Body(new ValidationPipe()) dto: CreateDto |
| Optional Properties | Use ? and @IsOptional() | description?: string; @IsOptional() |
| Global Validation | Apply ValidationPipe app-wide | app.useGlobalPipes(new ValidationPipe()); |
Key Takeaways
Create DTOs as classes with validation decorators to define and check data shape.
Use ValidationPipe in controllers or globally to enable automatic validation.
Never use interfaces for DTOs because decorators won't work on them.
Mark optional fields with ? and @IsOptional() to avoid validation errors.
DTOs help keep your NestJS app's data clean and predictable.