The Problem
- when using only Mongoose methods, application returns data from database exactly as Mongo provides it
- representation of data from Mongo is not always the same representation that we want to expose from API
Overview of Data Transfer Objects (DTOs)
- DTOs are simple objects used to transfer data between different parts of application
- useful for 3 main reasons:
- Validation: ensure data being transferred meets certain criteria
- Transformation: convert data from one format or structure to another
- Encapsulation: group related data together into single object
- common pattern in NestJS to use library called
class-transformer for transforming data between objects
Defining Basic DTOs
import { Exclude, Expose } from 'class-transformer';
@Exclude()
export class TodoDto {
@Expose({ name: '_id' })
id: string;
@Expose()
completed: boolean;
@Expose()
description: string;
@Expose()
title: string;
}
@Exclude()
export class CreateTodoDto {
@Expose()
description: string;
@Expose()
title: string;
}
@Exclude()
export class UpdateTodoDto {
@Expose()
completed?: boolean;
@Expose()
description?: string;
@Expose()
title?: string;
}
import { plainToClass } from 'class-transformer';
import { TodoDto } from './dtos/todo.dto';
import { TodoDocument } from './schemas/todo.schema';
export function transformTodoDto(todo: TodoDocument): TodoDto {
return plainToClass(TodoDto, todo.toJSON());
}
import { Body, Controller, Delete, Get, NotFoundException, Param, Post, Put, Query } from '@nestjs/common';
import { CreateTodoDto, TodoDto, UpdateTodoDto } from './dtos/todo.dto';
import { TodoService } from './todo.service';
import { transformTodoDto } from './utils';
@Controller('todos')
export class TodoController {
constructor(private readonly todoService: TodoService) {}
@Get()
async findAll(@Query('showIncomplete') showIncomplete: boolean): Promise<TodoDto[]> {
const todos = await this.todoService.findAll(showIncomplete);
return todos.map(transformTodoDto);
}
@Get(':id')
async findOne(@Param('id') id: string): Promise<TodoDto> {
const foundTodo = await this.todoService.findOne(id);
if (!foundTodo) {
throw new NotFoundException('Todo not found!');
}
return transformTodoDto(todo);
}
@Post()
async createOne(@Body() todo: CreateTodoDto): Promise<TodoDto> {
const newTodo = await this.todoService.createOne(todo);
return transformTodoDto(newTodo);
}
}