NestJS: DTO with Automapper
Welcome back!
The API is complete and tested. But something is wrong: a good API must decouple the entities coming from the database and those returned from API as results. In fact, the goal of an API is to serve a resource and this one does not necessarily need all the fields of our entity object but sometimes just a subset to perfectly match the needs of the UI or in terms of performance (we are thinking in particular of foreign keys allowing us to bring back data from another table as nested objects). This concept is solved by using DTO pattern. This one is strongly linked with CQRS architecture.
A data transfer object (DTO) is a design pattern used in object-oriented software architectures. Its purpose is to simplify data transfers between subsystems of a software application. Data transfer objects are often used in conjunction with data access objects.
We join the concept of interface with data contracts. In short, an awesome program! But we will stop there to discover Automapper.
Automapper allows us to map one object to another and integrates really powerful features, for example creating calculated on the fly properties or select some properties during the mapping…
Installation
Open a terminal at the root of the solution and let's launch these commands:
> npm i @automapper/core
> npm i @automapper/classes
> npm i @automapper/nestjs
> npm i @automapper/types
We need to update module imports to add Automapper configuration:
AutomapperModule.forRoot({
strategyInitializer: classes(),
}),
For the project we used the classes mapping. Automapper manages other type of mapping, you can find all these types on documentation.
Before creating our DTO we need to define targeted objects:
- One DTO to represent the reading of an item,
- One DTO to represent the creation of an item,
- One DTO to represent the update of an item.
In "shared/src", let's create a new folder "DTO" and create the targeted objects:
Notice the use of @Automap() decorator to declare properties to use with the mapping.
We need to update the entity object to add the decorator on each properties.
Automapper allows us to create a profile file to create mapping between classes. In "Shared/src" let's create a new folder "profile" and create a profile file:
Inside, we create 3 mappings for each DTO. Notice the direction of each mapping. For reading we go from the entity to the DTO because we start from the database and go outside. For writing and updating, we go from the outside to the database, we start from the DTO to finish to the entity. For the create operation we have ignored the update of the ID with this function:
forMember((dest) => dest.id, ignore())
We just have to use the profile as a provider of our library.
providers: [InventoryService, InventoryItemProfile],
exports: [InventoryService],
The application layer will no longer manipulate the entities but only DTO. Decoupling is effective!
How use the automatic mapping?
Back to shared "InventoryService" service and let's inject the Automapper mapper:
We have to use some functions from mapper to map results and update the definition of our functions!
this.classMapper.map(item, InventoryItemCreateDTO, InventoryItem);
We map one object of "InventoryItemCreateDTO" type to one object of "InventoryItem" type(you can use also the "mapAsync" function is you have to use an async process)
this.classMapper.mapArrayAsync(await
this.inventoryItemRepository.find(), InventoryItem,
InventoryItemReadDTO);
With "mapArrayAsync" We map an array of "InventoryItem" type to an "InventoryItemReadDTO" array type. Simple and efficient!
It remains a last step: update the controllers functions definition to return DTO instead of "InventoryItem" entity.
And of course update the tests in each layer!
Mission complete!
Our decoupling is effective and it's time to document our APIs! We can think about the next part: Ultimate API documentation with Swagger.