DTO explained in NestJS

Lawrence Liu
4 min readAug 14, 2023

What is DTO(Data Transfer Object)pattern and how to properly use it in NestJS

Overview

DTO (Data Transfer Object) is a design pattern that is commonly used in software development to transfer data between different layers of an application. The main idea behind the DTO pattern is to encapsulate data and provide a standardised way of transferring it between different parts of the application.

In practice, a DTO is a simple object that contains data and may have some validation logic. It defines part or all data of a domain object, but do not have any business login in it. It’s typically used to transfer data between the client and the server, or between different layers of the server-side application. The DTO object is usually created by the server-side code, populated with data from a database or other sources, and then sent to the client. The client-side code can then use the DTO object to display data to the user or to send it back to the server for processing.

Basic Usage

Here’s an example of how to use DTOs in a NestJS application:

  1. Define your DTO class:
export class CreateUserDto {
readonly name: string;
readonly email: string;
readonly password: string;
}

2. Use your DTO class in your controller:

import { Controller, Post, Body } from ‘@nestjs/common’;
import { CreateUserDto } from ‘./create-user.dto’;
@Controller('users')
export class UsersController {
@Post()
async create(@Body() createUserDto: CreateUserDto) {
// Your create user logic here
}
}

Proper Usage

DTO is designed to help us organise data from different domain objects or have only part of the data from a domain object, besides, it helps us validate data or encapsulation of the serialization’s logic.

Think about these scenarios:

  1. User registration: When a user registers for an account on a website, you might need to collect data from multiple domain objects, such as the user’s name and email address from the User object, and the user’s billing address from the BillingAddress object. By using a DTO, you can encapsulate this data into a single object and pass it around your application as needed.
  2. API responses: When building an API, you might want to return only a subset of the data from a domain object to reduce the amount of data that needs to be transferred over the network. For example, you might have a User object with many properties, but only want to return the user’s name and email address in the API response. By using a DTO, you can define a class that contains only the properties you want to return, and return instances of this class from your API endpoints.
  3. Data validation: When accepting data from a client, you might want to validate that the data is in the correct format before processing it. By using a DTO, you can define validation logic in the DTO class itself, which can help reduce duplication and make your code more maintainable.

Now here is an example of how to use DTO to have only part of the data:

Let’s say we have a User entity:

class User {
id: number;
name: string;
email: string;
password: string;
}

We have a service that can handle user operations:

class UserService {
// Fetch the user object from the database
getUserById(userId: number): User {
// Fetch user data from the database
// …
return user;
}
}

Now, if we want to create a user profile with only the name and email field, we can define a DTO:

// Define a DTO class to represent a subset of user data
class UserProfileDto {
name: string;
email: string;
}

We then define a controller to handle API requests, in this controller, we use the service to get user entity and we’re using a DTO (UserProfileDto) to represent a subset of data for a user’s profile:

@Controller('users')
class UsersController {
constructor(private userService: UserService) {}

// Define an API endpoint to return a subset of user data
@Get(':id/profile')
getUserProfile(@Param('id') userId: number): UserProfileDto {
const user = this.userService.getUserById(userId);

// Create a DTO object containing only the name and email properties
const userProfileDto = new UserProfileDto();
userProfileDto.name = user.name;
userProfileDto.email = user.email;

// Return the DTO object as the API response
return userProfileDto;
}
}

Another example is you can also use DTO to organise multiple domain object through one DTO:

Let’s say your client wants a user’s latest blog post, and you have another post entity:

class Post {
id: number;
title: string;
content: string;
userId: number;
}

You will need a service:

// Define a service to handle user and post operations
class UserService {
// Fetch the user object and their latest post from the database
getUserWithLatestPost(userId: number): { user: User, latestPost: Post } {
// Fetch user data from the database
// ...

// Fetch the latest post data for the user from the database
// ...

return { user, latestPost };
}
}

Now you can create a UserWithLatestPostDto:

class UserWithLatestPostDto {
name: string;
email: string;
latestPostTitle: string;
latestPostContent: string;
}

In the controller, it would be:

// Define a controller to handle API requests
@Controller('users')
class UsersController {
constructor(private userService: UserService) {}

// Define an API endpoint to return user data and their latest post
@Get(':id/with-latest-post')
getUserWithLatestPost(@Param('id') userId: number): UserWithLatestPostDto {
const { user, latestPost } = this.userService.getUserWithLatestPost(userId);

// Create a DTO object containing user data and their latest post
const userWithLatestPostDto = new UserWithLatestPostDto();
userWithLatestPostDto.name = user.name;
userWithLatestPostDto.email = user.email;
userWithLatestPostDto.latestPostTitle = latestPost.title;
userWithLatestPostDto.latestPostContent = latestPost.content;

// Return the DTO object as the API response
return userWithLatestPostDto;
}
}

In this example, DTO helps you reduce API calls, and only transfer the data in need for our client.

--

--