Nest: Best practices to avoid repetition

Created on 14 Nov 2017  路  9Comments  路  Source: nestjs/nest

I've started a small project using nest and I'm loving it :) Kudos to the team!

I was wondering if there's any documented design pattern or suggested architecture for nest to minimize repetition when dealing with model interfaces, entities and DTOs.

This is, considering the maintainability of the metadata associated to TypeORM and class-validator, for example when setting nullability, type and length constraints to an entity column and to the associated DTO field.
And considering it's best to have a single source of truth for this.

Suggestions from more experienced users are more than welcome as well.

Thank you and keep rocking!

discussion 馃敟 question 馃檶

Most helpful comment

We actually have been working quite a lot on this problem internally.

For now, we have tried 2 ways :

First, we have created a tool allowing us to stripe our entities (TypeORM) of all non-server decorators (ie. @Column()) and imports. This allows us to generate a "copy" of our entity, usable on front-end websites (angular, react, etc.) without the need to import NestJS. With this, out DTOs and entities are just one and the same. The entities are stored in a separate package (repository or package in a yarn workspace) and can be imported via import @project/server-dtos or import @project/client-dtos

However the big downside of this technique is that sometimes you don't want DTOs to be 100% similar to your database entities. That's especially the case with a complex database, with a lot of JOINs and deep structure.

So right now we are trying another solution, more classic: each database entity (stored in the server package) has a DTO (class) counterpart, created in a separate package (dtos) and imported on both the server and client. All the class validations are done on those DTOs (using class-validator) and each entity has 2 methods : FromDTO(args: dto) : entity and ToDTO(args: entity): dto to easily convert a received DTO into its entity counterpart, server-side.

The downside of this technique is that DTO can't store all the JOINs relations an Entity has (one-one, one-many, etc.). For now we are still struggling to find the perfect balance for the information stored in the DTOs.

Hope this helps

All 9 comments

Hi @fgallardograzio,
It's not documented anywhere yet. The docs (https://github.com/nestjs/docs.nestjs.com) are OS now, maybe someone will try to write something interesting 馃檪

@fgallardograzio I've got few. As a research, I created a POC plugin for eslint, which has few rules. Check it out - https://github.com/unlight/eslint-plugin-nestjs

I know it doesn't directly deal with the "repetition" you're talking about (between TypeORM and class-validator decorators/metadata), but still:
I created this PR to allow easily sharing class-validator metadatas between DTOs.
I'd love to see a new feature allowing us to easily do class validation using already-available constraints of TypeORM (e.g., if the property is decorated with @Column({type: 'varchar', length: 100}), then we should/could consider that it is also decorated with @IsString() @MaxLength(100)).

I assume I could read TypeORM decorators options to create ValidationMetadatas (using the MetadataStorage of class-validator, as I did in this PR), but first I want to see if the team will accept my first PR.

That's very useful, @unlight, Thanks!

And @VinceOPS, those are great ideas! I'll keep an eye on that PR.
Regarding doing class validation based on TypeORM constraints, I'm concerned about introducing hidden behaviour. The kind of "framework magic" that leaves new devs not familiar with the project or the inner workings of the framework ultimately confused about how one thing affects the other.
If that's the case I'd think of this as perhaps a new decorator, "above" class validation and orm specifications. That should be in charge of defining as transparently as possible how those two work together. What do you think?

Thanks!

I'm also struggling to avoid repetition in my DTOs, has anyone found any example repository where this is nicely handled?

Previously I used one model for that. I used class as DTO and as model interface. But I had some another validation logic.

But in terms of "Data Transfer Object" that object only have one responsibility is to pass data from point A to point B (in our case it's from controller to service). Also we have class-validator decorators in our model for validation purposes (it's looks like Java Bean Validation API). In terms of SRP our DTO and our Entity has different purposes, but looks like same. I thinking about - it's good or bad to have one file as DTO and as an Entity? I don't think it should be in one file, but sometimes I also want to optimize it, and use only one file for both purposes.

We actually have been working quite a lot on this problem internally.

For now, we have tried 2 ways :

First, we have created a tool allowing us to stripe our entities (TypeORM) of all non-server decorators (ie. @Column()) and imports. This allows us to generate a "copy" of our entity, usable on front-end websites (angular, react, etc.) without the need to import NestJS. With this, out DTOs and entities are just one and the same. The entities are stored in a separate package (repository or package in a yarn workspace) and can be imported via import @project/server-dtos or import @project/client-dtos

However the big downside of this technique is that sometimes you don't want DTOs to be 100% similar to your database entities. That's especially the case with a complex database, with a lot of JOINs and deep structure.

So right now we are trying another solution, more classic: each database entity (stored in the server package) has a DTO (class) counterpart, created in a separate package (dtos) and imported on both the server and client. All the class validations are done on those DTOs (using class-validator) and each entity has 2 methods : FromDTO(args: dto) : entity and ToDTO(args: entity): dto to easily convert a received DTO into its entity counterpart, server-side.

The downside of this technique is that DTO can't store all the JOINs relations an Entity has (one-one, one-many, etc.). For now we are still struggling to find the perfect balance for the information stored in the DTOs.

Hope this helps

Why should DTOs be mapped 1:1 with Entities?

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

Was this page helpful?
0 / 5 - 0 ratings