DTO Orchestration
A robust DTO structure to aid us with this endeavor.
We'll start with the DTOs for filtering. A product may be filtered by name
, price
or category
. Well, filtering by name
is something very common and that may be done with different entities. Due to this, let's create a dedicated DTO for it in querying -> dto -> name-filter.dto. With it, we may then create the DTO for the filter fields of an entity without the need to define the name
field everytime.
And that's what will be done now. Let's then create the file products -> dto -> querying -> filter-products.dto. Here, we should create the two remaining fields and extend the NameFilterDto
.
Excellent! We have the fields to filter by and their respective validations. Let's then proceed to create the DTOs for sorting.
With sorting, there will be a difference. When filtering by name
, for example, we may pass any text we want, as we are stating what should be in the name
of the product. However, when sorting, we should use predefined values, as we'll sort by the existing fields.
Well, sorting involves two steps: choosing the sort
field by which the sorting will happen, and the order
method. There are only two order methods: ascending and descending. Let's then represent them in the file querying -> dto -> order.dto.
As was said, here predefined values are expected. There is no sense in accepting any text in the order
field, as it should only have the values ASC or DESC. Due to this, this validation is not enough. Fortunately, there's an interesting way to solve this.
First, we can create a constant array with the allowed values.
After that, we can create a namesake type which is the union of these values.
This article better explains this syntax, if there is interest for a further read.
This approach of constant array + literal union can also be used in place of enums, if desired.
After that, we just need to adjust the DTO. Here, ASC is used by default if no order
is sent.
TypeScript can infer when the constant or the type is being used.
Now, the DTO to represent both the sort
and order
fields will be created in products -> dto -> querying -> sort-products.dto. If no sort
field is sent, products will be sorted by name
. Every sorting process uses the fields sort
and order
, meaning, respectively, the field to sort by and how this field will be ordered. Therefore, each entity will have its own sort
field with predefined values.
Finally, for just a bit more of type safety, we can enforce that the Sort
array must only contain values that are fields of the Product
entity, by adding at the end:
We now have a DTO for filtering and another one for sorting. As only a single DTO may be used, what should be done now is to create a new DTO that will be the combination of these two, together with the one for pagination. We can do it in the same folder, and name the file query-products.dto.
With all these DTOs in hand, we may then proceed to actually apply filtering and sorting.
Commit - Orchestrating dtos for filtering and sorting
Last updated