Proper way to map Entities to DTOs in TypeScript
$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 DTO
s. The DTO
s 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 DTO
s.
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
:
- Beside simply mapping properties
id
andname
from one class to the other, I'm also "calculating"absolutePath
, which is not contained in the originalEntity
, but only inDTO
. Is this mapper class the correct place for that?
Furthermore, for that reason I need to inject theFolderService
to the mapper, which seems not quite correct to me... - Because of the Image
Entities
' relations toFolder
andTag
, I need to call their mapper classes from theImageEntityToDtoMapper
. Is this the correct way to do it? - Because I need to inject other
Entities
' mappers, I have a circular dependency betweenImageEntityToDtoMapper
andTagEntityToDtoMapper
. 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 usingforwardRef
, 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
New contributor
$endgroup$
add a comment |
$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 DTO
s. The DTO
s 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 DTO
s.
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
:
- Beside simply mapping properties
id
andname
from one class to the other, I'm also "calculating"absolutePath
, which is not contained in the originalEntity
, but only inDTO
. Is this mapper class the correct place for that?
Furthermore, for that reason I need to inject theFolderService
to the mapper, which seems not quite correct to me... - Because of the Image
Entities
' relations toFolder
andTag
, I need to call their mapper classes from theImageEntityToDtoMapper
. Is this the correct way to do it? - Because I need to inject other
Entities
' mappers, I have a circular dependency betweenImageEntityToDtoMapper
andTagEntityToDtoMapper
. 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 usingforwardRef
, 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
New contributor
$endgroup$
add a comment |
$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 DTO
s. The DTO
s 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 DTO
s.
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
:
- Beside simply mapping properties
id
andname
from one class to the other, I'm also "calculating"absolutePath
, which is not contained in the originalEntity
, but only inDTO
. Is this mapper class the correct place for that?
Furthermore, for that reason I need to inject theFolderService
to the mapper, which seems not quite correct to me... - Because of the Image
Entities
' relations toFolder
andTag
, I need to call their mapper classes from theImageEntityToDtoMapper
. Is this the correct way to do it? - Because I need to inject other
Entities
' mappers, I have a circular dependency betweenImageEntityToDtoMapper
andTagEntityToDtoMapper
. 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 usingforwardRef
, 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
New contributor
$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 DTO
s. The DTO
s 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 DTO
s.
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
:
- Beside simply mapping properties
id
andname
from one class to the other, I'm also "calculating"absolutePath
, which is not contained in the originalEntity
, but only inDTO
. Is this mapper class the correct place for that?
Furthermore, for that reason I need to inject theFolderService
to the mapper, which seems not quite correct to me... - Because of the Image
Entities
' relations toFolder
andTag
, I need to call their mapper classes from theImageEntityToDtoMapper
. Is this the correct way to do it? - Because I need to inject other
Entities
' mappers, I have a circular dependency betweenImageEntityToDtoMapper
andTagEntityToDtoMapper
. 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 usingforwardRef
, 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
typescript dto orm
New contributor
New contributor
New contributor
asked 7 hours ago
pschildpschild
1012
1012
New contributor
New contributor
add a comment |
add a comment |
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.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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.
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.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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