Proper way to map Entities to DTOs in TypeScript












0












$begingroup$


In a recent project my backend architecture consists of





  • Repositories (talking to database)


  • Services (handling business logic)


  • Controllers (handling incoming REST calls and providing according responses to the client)


I introduced DTO classes for representing the data that is returned by the controllers when a client calls a REST route. Because the services work with Entities, I need to somehow map those to my DTOs. The DTOs contain most of the properties of the according Entity, but can define additional properties as well.



First of all, I have the following Entities:



Image Entity



@Entity()
export class Image {
id: number;
name: string;

@ManyToOne(...)
parentFolder: Folder;

@ManyToMany(...)
tags: Tag;
}


Tag Entity



@Entity()
export class Tag {
id: number;
label: string;

@ManyToMany(...)
images: Image;
}


As you can see, there are also some relations (ManyToOne, ManyToMany), so my Entities contain references to other Entities. Same for according DTOs.



Next, you can see my shortened ImageController. In the method findOne, a service is asked to look for an Entity with given id in the database.



Image Controller



@Controller('image')
export class ImageController {
constructor(
private readonly imageService: ImageService,
private readonly imageEntityToDtoMapper: ImageEntityToDtoMapper
) { }

@Get(':id')
async findOne(@Param('id') id): Promise<ImageDto> {
const image: Image = await this.imageService.findOne(id);
return this.imageEntityToDtoMapper.map(image);
}
}


In order to let the controller return a DTO, the result is mapped by an ImageEntityToDtoMapper.



ImageEntityToDtoMapper



@Injectable()
export class ImageEntityToDtoMapper {
constructor(
private readonly folderService: FolderService,
private readonly folderEntityToDtoMapper: FolderEntityToDtoMapper,
@Inject(forwardRef(() => TagEntityToDtoMapper))
private readonly tagEntityToDtoMapper: TagEntityToDtoMapper
) { }

async map(entity: Image): Promise<ImageDto> {
if (entity) {
const parentFolderPath = await this.folderService.buildPathByFolderId(entity.parentFolder.id);
const absolutePath = `${parentFolderPath}${path.sep}${entity.name}`;

const dto = new ImageDto();
dto.id = entity.id;
dto.name = entity.name;
dto.absolutePath = absolutePath;
dto.parentFolder = await this.folderEntityToDtoMapper.map(entity.parentFolder);
dto.tags = await this.tagEntityToDtoMapper.mapAll(entity.tags);
return dto;
}
}
}


And here are my questions, all regarding ImageEntityToDtoMapper:




  1. Beside simply mapping properties id and name from one class to the other, I'm also "calculating" absolutePath, which is not contained in the original Entity, but only in DTO. Is this mapper class the correct place for that?

    Furthermore, for that reason I need to inject the FolderService to the mapper, which seems not quite correct to me...

  2. Because of the ImageEntities' relations to Folder and Tag, I need to call their mapper classes from the ImageEntityToDtoMapper. Is this the correct way to do it?

  3. Because I need to inject other Entities' mappers, I have a circular dependency between ImageEntityToDtoMapper and TagEntityToDtoMapper. And in my whole project, I have more cases where multiple mappers depend on each other.

    Although the library (NestJS) I'm using offers a way to handle this by using forwardRef, I would like to avoid those circular dependencies. Could you think of any possibility?


Thank you in advance - I really appreciate any feedback!










share|improve this question







New contributor




pschild is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.







$endgroup$

















    0












    $begingroup$


    In a recent project my backend architecture consists of





    • Repositories (talking to database)


    • Services (handling business logic)


    • Controllers (handling incoming REST calls and providing according responses to the client)


    I introduced DTO classes for representing the data that is returned by the controllers when a client calls a REST route. Because the services work with Entities, I need to somehow map those to my DTOs. The DTOs contain most of the properties of the according Entity, but can define additional properties as well.



    First of all, I have the following Entities:



    Image Entity



    @Entity()
    export class Image {
    id: number;
    name: string;

    @ManyToOne(...)
    parentFolder: Folder;

    @ManyToMany(...)
    tags: Tag;
    }


    Tag Entity



    @Entity()
    export class Tag {
    id: number;
    label: string;

    @ManyToMany(...)
    images: Image;
    }


    As you can see, there are also some relations (ManyToOne, ManyToMany), so my Entities contain references to other Entities. Same for according DTOs.



    Next, you can see my shortened ImageController. In the method findOne, a service is asked to look for an Entity with given id in the database.



    Image Controller



    @Controller('image')
    export class ImageController {
    constructor(
    private readonly imageService: ImageService,
    private readonly imageEntityToDtoMapper: ImageEntityToDtoMapper
    ) { }

    @Get(':id')
    async findOne(@Param('id') id): Promise<ImageDto> {
    const image: Image = await this.imageService.findOne(id);
    return this.imageEntityToDtoMapper.map(image);
    }
    }


    In order to let the controller return a DTO, the result is mapped by an ImageEntityToDtoMapper.



    ImageEntityToDtoMapper



    @Injectable()
    export class ImageEntityToDtoMapper {
    constructor(
    private readonly folderService: FolderService,
    private readonly folderEntityToDtoMapper: FolderEntityToDtoMapper,
    @Inject(forwardRef(() => TagEntityToDtoMapper))
    private readonly tagEntityToDtoMapper: TagEntityToDtoMapper
    ) { }

    async map(entity: Image): Promise<ImageDto> {
    if (entity) {
    const parentFolderPath = await this.folderService.buildPathByFolderId(entity.parentFolder.id);
    const absolutePath = `${parentFolderPath}${path.sep}${entity.name}`;

    const dto = new ImageDto();
    dto.id = entity.id;
    dto.name = entity.name;
    dto.absolutePath = absolutePath;
    dto.parentFolder = await this.folderEntityToDtoMapper.map(entity.parentFolder);
    dto.tags = await this.tagEntityToDtoMapper.mapAll(entity.tags);
    return dto;
    }
    }
    }


    And here are my questions, all regarding ImageEntityToDtoMapper:




    1. Beside simply mapping properties id and name from one class to the other, I'm also "calculating" absolutePath, which is not contained in the original Entity, but only in DTO. Is this mapper class the correct place for that?

      Furthermore, for that reason I need to inject the FolderService to the mapper, which seems not quite correct to me...

    2. Because of the ImageEntities' relations to Folder and Tag, I need to call their mapper classes from the ImageEntityToDtoMapper. Is this the correct way to do it?

    3. Because I need to inject other Entities' mappers, I have a circular dependency between ImageEntityToDtoMapper and TagEntityToDtoMapper. And in my whole project, I have more cases where multiple mappers depend on each other.

      Although the library (NestJS) I'm using offers a way to handle this by using forwardRef, I would like to avoid those circular dependencies. Could you think of any possibility?


    Thank you in advance - I really appreciate any feedback!










    share|improve this question







    New contributor




    pschild is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
    Check out our Code of Conduct.







    $endgroup$















      0












      0








      0





      $begingroup$


      In a recent project my backend architecture consists of





      • Repositories (talking to database)


      • Services (handling business logic)


      • Controllers (handling incoming REST calls and providing according responses to the client)


      I introduced DTO classes for representing the data that is returned by the controllers when a client calls a REST route. Because the services work with Entities, I need to somehow map those to my DTOs. The DTOs contain most of the properties of the according Entity, but can define additional properties as well.



      First of all, I have the following Entities:



      Image Entity



      @Entity()
      export class Image {
      id: number;
      name: string;

      @ManyToOne(...)
      parentFolder: Folder;

      @ManyToMany(...)
      tags: Tag;
      }


      Tag Entity



      @Entity()
      export class Tag {
      id: number;
      label: string;

      @ManyToMany(...)
      images: Image;
      }


      As you can see, there are also some relations (ManyToOne, ManyToMany), so my Entities contain references to other Entities. Same for according DTOs.



      Next, you can see my shortened ImageController. In the method findOne, a service is asked to look for an Entity with given id in the database.



      Image Controller



      @Controller('image')
      export class ImageController {
      constructor(
      private readonly imageService: ImageService,
      private readonly imageEntityToDtoMapper: ImageEntityToDtoMapper
      ) { }

      @Get(':id')
      async findOne(@Param('id') id): Promise<ImageDto> {
      const image: Image = await this.imageService.findOne(id);
      return this.imageEntityToDtoMapper.map(image);
      }
      }


      In order to let the controller return a DTO, the result is mapped by an ImageEntityToDtoMapper.



      ImageEntityToDtoMapper



      @Injectable()
      export class ImageEntityToDtoMapper {
      constructor(
      private readonly folderService: FolderService,
      private readonly folderEntityToDtoMapper: FolderEntityToDtoMapper,
      @Inject(forwardRef(() => TagEntityToDtoMapper))
      private readonly tagEntityToDtoMapper: TagEntityToDtoMapper
      ) { }

      async map(entity: Image): Promise<ImageDto> {
      if (entity) {
      const parentFolderPath = await this.folderService.buildPathByFolderId(entity.parentFolder.id);
      const absolutePath = `${parentFolderPath}${path.sep}${entity.name}`;

      const dto = new ImageDto();
      dto.id = entity.id;
      dto.name = entity.name;
      dto.absolutePath = absolutePath;
      dto.parentFolder = await this.folderEntityToDtoMapper.map(entity.parentFolder);
      dto.tags = await this.tagEntityToDtoMapper.mapAll(entity.tags);
      return dto;
      }
      }
      }


      And here are my questions, all regarding ImageEntityToDtoMapper:




      1. Beside simply mapping properties id and name from one class to the other, I'm also "calculating" absolutePath, which is not contained in the original Entity, but only in DTO. Is this mapper class the correct place for that?

        Furthermore, for that reason I need to inject the FolderService to the mapper, which seems not quite correct to me...

      2. Because of the ImageEntities' relations to Folder and Tag, I need to call their mapper classes from the ImageEntityToDtoMapper. Is this the correct way to do it?

      3. Because I need to inject other Entities' mappers, I have a circular dependency between ImageEntityToDtoMapper and TagEntityToDtoMapper. And in my whole project, I have more cases where multiple mappers depend on each other.

        Although the library (NestJS) I'm using offers a way to handle this by using forwardRef, I would like to avoid those circular dependencies. Could you think of any possibility?


      Thank you in advance - I really appreciate any feedback!










      share|improve this question







      New contributor




      pschild is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.







      $endgroup$




      In a recent project my backend architecture consists of





      • Repositories (talking to database)


      • Services (handling business logic)


      • Controllers (handling incoming REST calls and providing according responses to the client)


      I introduced DTO classes for representing the data that is returned by the controllers when a client calls a REST route. Because the services work with Entities, I need to somehow map those to my DTOs. The DTOs contain most of the properties of the according Entity, but can define additional properties as well.



      First of all, I have the following Entities:



      Image Entity



      @Entity()
      export class Image {
      id: number;
      name: string;

      @ManyToOne(...)
      parentFolder: Folder;

      @ManyToMany(...)
      tags: Tag;
      }


      Tag Entity



      @Entity()
      export class Tag {
      id: number;
      label: string;

      @ManyToMany(...)
      images: Image;
      }


      As you can see, there are also some relations (ManyToOne, ManyToMany), so my Entities contain references to other Entities. Same for according DTOs.



      Next, you can see my shortened ImageController. In the method findOne, a service is asked to look for an Entity with given id in the database.



      Image Controller



      @Controller('image')
      export class ImageController {
      constructor(
      private readonly imageService: ImageService,
      private readonly imageEntityToDtoMapper: ImageEntityToDtoMapper
      ) { }

      @Get(':id')
      async findOne(@Param('id') id): Promise<ImageDto> {
      const image: Image = await this.imageService.findOne(id);
      return this.imageEntityToDtoMapper.map(image);
      }
      }


      In order to let the controller return a DTO, the result is mapped by an ImageEntityToDtoMapper.



      ImageEntityToDtoMapper



      @Injectable()
      export class ImageEntityToDtoMapper {
      constructor(
      private readonly folderService: FolderService,
      private readonly folderEntityToDtoMapper: FolderEntityToDtoMapper,
      @Inject(forwardRef(() => TagEntityToDtoMapper))
      private readonly tagEntityToDtoMapper: TagEntityToDtoMapper
      ) { }

      async map(entity: Image): Promise<ImageDto> {
      if (entity) {
      const parentFolderPath = await this.folderService.buildPathByFolderId(entity.parentFolder.id);
      const absolutePath = `${parentFolderPath}${path.sep}${entity.name}`;

      const dto = new ImageDto();
      dto.id = entity.id;
      dto.name = entity.name;
      dto.absolutePath = absolutePath;
      dto.parentFolder = await this.folderEntityToDtoMapper.map(entity.parentFolder);
      dto.tags = await this.tagEntityToDtoMapper.mapAll(entity.tags);
      return dto;
      }
      }
      }


      And here are my questions, all regarding ImageEntityToDtoMapper:




      1. Beside simply mapping properties id and name from one class to the other, I'm also "calculating" absolutePath, which is not contained in the original Entity, but only in DTO. Is this mapper class the correct place for that?

        Furthermore, for that reason I need to inject the FolderService to the mapper, which seems not quite correct to me...

      2. Because of the ImageEntities' relations to Folder and Tag, I need to call their mapper classes from the ImageEntityToDtoMapper. Is this the correct way to do it?

      3. Because I need to inject other Entities' mappers, I have a circular dependency between ImageEntityToDtoMapper and TagEntityToDtoMapper. And in my whole project, I have more cases where multiple mappers depend on each other.

        Although the library (NestJS) I'm using offers a way to handle this by using forwardRef, I would like to avoid those circular dependencies. Could you think of any possibility?


      Thank you in advance - I really appreciate any feedback!







      typescript dto orm






      share|improve this question







      New contributor




      pschild is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.











      share|improve this question







      New contributor




      pschild is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.









      share|improve this question




      share|improve this question






      New contributor




      pschild is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.









      asked 7 hours ago









      pschildpschild

      1012




      1012




      New contributor




      pschild is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.





      New contributor





      pschild is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.






      pschild is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.






















          0






          active

          oldest

          votes











          Your Answer





          StackExchange.ifUsing("editor", function () {
          return StackExchange.using("mathjaxEditing", function () {
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          });
          });
          }, "mathjax-editing");

          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "196"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: false,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });






          pschild is a new contributor. Be nice, and check out our Code of Conduct.










          draft saved

          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f213410%2fproper-way-to-map-entities-to-dtos-in-typescript%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown

























          0






          active

          oldest

          votes








          0






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes








          pschild is a new contributor. Be nice, and check out our Code of Conduct.










          draft saved

          draft discarded


















          pschild is a new contributor. Be nice, and check out our Code of Conduct.













          pschild is a new contributor. Be nice, and check out our Code of Conduct.












          pschild is a new contributor. Be nice, and check out our Code of Conduct.
















          Thanks for contributing an answer to Code Review Stack Exchange!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          Use MathJax to format equations. MathJax reference.


          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f213410%2fproper-way-to-map-entities-to-dtos-in-typescript%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          How to reconfigure Docker Trusted Registry 2.x.x to use CEPH FS mount instead of NFS and other traditional...

          is 'sed' thread safe

          How to make a Squid Proxy server?