Lesson 2
Transforming Data Store Objects to DTOs
Introduction

Welcome to this lesson on transforming data store objects to Data Transfer Objects (DTOs) in NestJS. So far, we've covered integrating MongoDB into your NestJS app, from installing and configuring MongoDB and Mongoose, to defining schemas for ToDo items and implementing the necessary service and controller layers for CRUD operations. In this lesson, we're going to focus on how to use DTOs with database objects, a crucial part of managing data flow efficiently in your application. We'll also introduce class-transformer, the library we'll use for transforming data, and explore how to implement these transformations in your NestJS application.

The Problem

Right now, our application is returning the data from the database exactly as Mongo gives it to us. That is a problem because the representation that Mongo stores data in is not always (often not, in fact!) the same representation that we want to expose from our API. For example, a Todo from our API currently looks like this:

JSON
1{ 2 "title": "Learn NestJS", 3 "description": "Follow the course to learn NestJS", 4 "completed": false, 5 "_id": "66faff8a9dd50cd4e36391e2", 6 "__v": 0 7}

That looks mostly correct except for the following issues:

  • We don't want _id. Instead, we want id
  • What is __v? That is an implementation detail of Mongo that we don't want to expose to the consumers of the API

We'd like the JSON to look more like this:

JSON
1{ 2 "id": "66faff8a9dd50cd4e36391e2", 3 "title": "Learn NestJS", 4 "description": "Follow the course to learn NestJS", 5 "completed": false 6}
Overview of Data Transfer Objects (DTOs)

Data Transfer Objects (DTOs) are simple objects used to transfer data between different parts of an application. DTOs are especially useful in web applications for three main reasons:

  1. Validation: They ensure the data being transferred meets certain criteria.
  2. Transformation: They convert data from one format or structure to another.
  3. Encapsulation: They group related data together into a single object.

You used DTOs in the previous courses and we'll use them again. But how do we best return a DTO when whate we have is a Database Object?

Introduction to `class-transformer`

It is a common pattern in NestJS to use a library called class-transformer for transforming data between objects. This library provides decorators and utility functions to transform plain JavaScript objects into class objects.

Setting Up the Environment
Installation of Required Libraries

First, let's make sure you have the necessary libraries installed. If you're working on your local machine, you can install them using npm:

Bash
1npm install class-transformer

In the CodeSignal environment, these libraries are pre-installed, so you won't need to worry about installation here.

Creating DTO Classes
Defining Basic DTOs

Let's modify our three DTO classes for our ToDo application: TodoDto, CreateTodoDto, and UpdateTodoDto.

TypeScript
1import { Expose, Exclude } from 'class-transformer'; 2 3// Exclude fields unless explicitly stated below 4@Exclude() 5export class TodoDto { 6 7 // Convert the Mongo _id to the DTO id field 8 @Expose({name: '_id'}) 9 id: string; 10 11 @Expose() 12 title: string; 13 14 @Expose() 15 description: string; 16 17 @Expose() 18 completed: boolean; 19} 20 21@Exclude() 22export class CreateTodoDto { 23 @Expose() 24 title: string; 25 26 @Expose() 27 description: string; 28} 29 30@Exclude() 31export class UpdateTodoDto { 32 @Expose() 33 title?: string; 34 35 @Expose() 36 description?: string; 37 38 @Expose() 39 completed?: boolean; 40}
Using `@Exclude` and `@Expose` Decorators
  • @Exclude(): This decorator ensures that all class properties are excluded from serialization unless explicitly exposed.
  • @Expose(): This decorator makes specific properties available during serialization.
Example Code Overview

In the example above:

  • TodoDto transforms the MongoDB _id to a more friendly id and exposes relevant properties like title, description, and completed.
  • TodoDto will not expose the __v field
  • CreateTodoDto and UpdateTodoDto handle new and update requests, respectively. UpdateTodoDto supports optional properties due to the ? symbol.
Implementing Data Transformation in Controller
Transform Function

First, we define a utility function, transformTodoDto, to transform our document to a DTO.

TypeScript
1import { plainToClass } from 'class-transformer'; 2import { TodoDocument } from './schemas/todo.schema'; 3import { TodoDto } from './dtos/todo.dto'; 4 5function transformTodoDto(todo: TodoDocument): TodoDto { 6 return plainToClass(TodoDto, todo.toJSON()); 7}
Controller Methods

Next, let's update our controller methods to use this utility function for transforming data.

TypeScript
1import { Controller, Get, Query, Param, Post, Body, Put, Delete, NotFoundException } from '@nestjs/common'; 2import { TodoService } from './todo.service'; 3import { TodoDto, CreateTodoDto, UpdateTodoDto } from './dtos/todo.dto'; 4 5@Controller('todos') 6export class TodoController { 7 constructor(private readonly todosService: TodoService) {} 8 9 @Get() 10 async findAll(@Query('showIncomplete') showIncomplete: boolean): Promise<TodoDto[]> { 11 const todos = await this.todosService.findAll(showIncomplete); 12 return todos.map(transformTodoDto); 13 } 14 15 @Get(':id') 16 async findOne(@Param('id') id: string): Promise<TodoDto> { 17 const todo = await this.todosService.findOne(id); 18 19 if (!todo) { 20 throw new NotFoundException('Todo not found'); 21 } 22 23 return transformTodoDto(todo); 24 } 25 26 @Post() 27 async create(@Body() todo: CreateTodoDto): Promise<TodoDto> { 28 const newTodo = await this.todosService.createTodo(todo); 29 return transformTodoDto(newTodo); 30 } 31 32 // etc 33}
Summary and Preparation for Practice

In this lesson, we explored the concept of Data Transfer Objects (DTOs) and how to implement them using the class-transformer library in a NestJS application. We discussed the creation of DTO classes (TodoDto, CreateTodoDto, UpdateTodoDto) and how to use decorators such as @Exclude and @Expose to manage data serialization. We then saw how to apply these transformations within the controller methods and walked through practical examples for creating, updating, and fetching todos.

You've completed a significant part of your NestJS learning journey! Now it's time to put theory into practice with hands-on exercises that reinforce these concepts.

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.