DEV Community

Cover image for Understanding DTOs (Data Transfer Objects) in NestJSπŸš€
Abhinav
Abhinav

Posted on

1

Understanding DTOs (Data Transfer Objects) in NestJSπŸš€

In modern web application development, ensuring that the data passed between the client and server is structured, validated, and secure is paramount. One of the tools that help achieve this is the DTO (Data Transfer Object) design pattern. In NestJS, DTOs are frequently used to define the structure of request and response bodies, while also integrating data validation to ensure that the data meets specific requirements.

In this blog post, we will explore what DTOs are, how to create and use them in NestJS, and how they help manage the flow of data in your application.


What is a DTO (Data Transfer Object)? πŸ€”

A Data Transfer Object (DTO) is a design pattern that is used to transfer data between different layers of an application. Typically, DTOs are used to encapsulate data that is sent over a network, whether it's in an API request or response. DTOs are especially helpful in:

  • Defining the structure of the data πŸ“Š
  • Performing data validation to ensure data integrity βœ…
  • Preventing issues like over-fetching or under-fetching of data πŸ”’

In NestJS, DTOs are usually classes with decorators that define the structure and validation rules for the data. They ensure that the incoming and outgoing data is well-formed, validated, and serialized properly.


Creating a Simple DTO in NestJS ✨

In NestJS, you create a DTO by defining a class. You can then use decorators provided by libraries like class-validator and class-transformer to define validation rules and transform the data.

Example 1: Simple DTO for User Registration πŸ‘€

Let’s say you are building a user registration API and need a DTO to validate the data the user sends.

import { IsString, IsEmail, IsInt, Min, Max } from 'class-validator';

export class CreateUserDto {
  @IsString()
  name: string;

  @IsEmail()
  email: string;

  @IsInt()
  @Min(18)
  @Max(120)
  age: number;
}
Enter fullscreen mode Exit fullscreen mode

In this DTO:

  • The @IsString() decorator ensures that the name property is a string. ✍️
  • The @IsEmail() decorator ensures that the email property is a valid email address πŸ“§.
  • The @IsInt(), @Min(18), and @Max(120) decorators validate that the age is an integer between 18 and 120 πŸ§‘β€πŸ€β€πŸ§‘.

Enabling Validation with the Validation Pipe πŸ’ͺ

NestJS integrates the class-validator library for validation and class-transformer for transforming data. To use validation, you need to enable the ValidationPipe globally in your app.

In the main.ts file, add the following code to activate the global validation pipe:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // Enable global validation
  app.useGlobalPipes(new ValidationPipe());

  await app.listen(3000);
}
bootstrap();
Enter fullscreen mode Exit fullscreen mode

This will ensure that every incoming request is validated according to the rules defined in your DTOs. 🎯


Using DTOs in Controllers πŸ§‘β€πŸ’»

Once the DTO is created and validation is enabled, you can use it in your controllers. For example, you might have a POST endpoint to register a new user, which would accept a CreateUserDto.

Example 2: Controller Using DTO πŸš€

import { Controller, Post, Body } from '@nestjs/common';
import { CreateUserDto } from './create-user.dto';
import { UsersService } from './users.service';

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Post()
  async create(@Body() createUserDto: CreateUserDto) {
    return this.usersService.create(createUserDto);
  }
}
Enter fullscreen mode Exit fullscreen mode

In the above example:

  • The @Body() decorator extracts the body of the incoming request and automatically maps it to the CreateUserDto. πŸ“₯
  • The ValidationPipe ensures that any invalid data will result in a 400 HTTP error response ⚠️.

Using DTOs for Responses πŸ”„

DTOs are not only useful for request validation but also for shaping the data that your application returns in response to the client. This helps prevent exposing unnecessary or sensitive data and ensures that the response format remains consistent.

Example 3: Response DTO for User Data πŸ“¨

export class UserResponseDto {
  id: number;
  name: string;
  email: string;
}
Enter fullscreen mode Exit fullscreen mode

In your controller, you can use this response DTO to return a user object:

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Post()
  async create(@Body() createUserDto: CreateUserDto) {
    const newUser = await this.usersService.create(createUserDto);

    // Return a response with the UserResponseDto
    return new UserResponseDto(newUser);
  }
}
Enter fullscreen mode Exit fullscreen mode

In this case, the UserResponseDto ensures that only the id, name, and email properties are included in the response, excluding any other internal data (like passwords). πŸ”


Partial DTOs for Updates πŸ”„

When dealing with updates (such as with a PATCH request), not all fields are required to be provided. NestJS allows you to define optional fields in your DTOs using the @IsOptional() decorator.

Example 4: DTO for Updating a User ✍️

import { IsString, IsEmail, IsOptional } from 'class-validator';

export class UpdateUserDto {
  @IsOptional()
  @IsString()
  name?: string;

  @IsOptional()
  @IsEmail()
  email?: string;
}
Enter fullscreen mode Exit fullscreen mode

In this UpdateUserDto:

  • The @IsOptional() decorator allows fields to be omitted from the request body. If the client does not provide a name or email, they won’t trigger validation errors. βœ…

Nesting DTOs πŸ”—

DTOs can be nested when you have complex data structures. For example, a user might have an address, which is itself a DTO.

Example 5: Nesting DTOs for User and Address 🏑

export class AddressDto {
  @IsString()
  street: string;

  @IsString()
  city: string;

  @IsString()
  postalCode: string;
}

export class CreateUserDto {
  @IsString()
  name: string;

  @IsEmail()
  email: string;

  @IsInt()
  age: number;

  @IsOptional()
  @Type(() => AddressDto)  // Use the Type decorator for nested objects
  address?: AddressDto;
}
Enter fullscreen mode Exit fullscreen mode

Here, the CreateUserDto includes an AddressDto. The @Type() decorator (from class-transformer) is used to ensure that the nested AddressDto is properly transformed and validated. πŸ› οΈ


Conclusion πŸŽ‰

DTOs in NestJS are a powerful tool to help ensure that the data flowing through your application is structured, validated, and secure. They provide several benefits:

  • Data Validation: Ensures incoming data meets the required format and constraints. βœ…
  • Consistency: Standardizes how data is structured, both for requests and responses. πŸ“
  • Security: Helps prevent issues like over-fetching of sensitive data. πŸ”

By using DTOs, you ensure that your NestJS applications are scalable, secure, and maintainable. Whether you're validating user input or shaping API responses, DTOs play an essential role in modern NestJS development. πŸš€

Do your career a big favor. Join DEV. (The website you're on right now)

It takes one minute, it's free, and is worth it for your career.

Get started

Community matters

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

πŸ‘‹ Kindness is contagious

Engage with a sea of insights in this enlightening article, highly esteemed within the encouraging DEV Community. Programmers of every skill level are invited to participate and enrich our shared knowledge.

A simple "thank you" can uplift someone's spirits. Express your appreciation in the comments section!

On DEV, sharing knowledge smooths our journey and strengthens our community bonds. Found this useful? A brief thank you to the author can mean a lot.

Okay