Next.js Discord

Discord Forum

OOP extend entity with required and optional field - typesafe

Answered
B33fb0n3 posted this in #help-forum
Open in Discord
Hey, I am building a REST API in a OOP way

I have a Product entity. This entity contains currently only variables that are really needed for the Product. So stuff like: price, content, title, ... Relations like "owner" are not included to keep things clean.

Now when I fetch a list of products, that don't contain a "owner" relation, everything is fine and works. I create the objects and they get returned, serialized through zod and returned correctly.

But when I have a "get single product" route, the owner should return with it. Like I need to build a product object, that contains an owner (not owner: User | undefined like before, now it's: owner: User). I can easily extend the object, yes. And that works, yes. But that might not be the only relation that I have to another object. Maybe there are pictures in the future, maybe variants, ...

Best case would be, that I call setOwner(owner: User) and then the object itself knows that it contains an owner.

I am using typescript. How to deal with relations like that?

Builder (without returning Object): https://pastebin.com/3tNyjhrU
Answered by Asian black bear
You typically have a ProductDto for the /products endpoint and if your /products/:id endpoint returns more details you have a ProductDetailsDto unique to that endpoint. Technically you can inherit from ProductDto which works in some cases, but can cause some issues if changes to the parent shouldn't be reflected in the details. As such it's also common to not inherit and just be explicit about the DTOs transferred as a response of those endpoints.
View full answer

11 Replies

Asian black bear
You typically have a ProductDto for the /products endpoint and if your /products/:id endpoint returns more details you have a ProductDetailsDto unique to that endpoint. Technically you can inherit from ProductDto which works in some cases, but can cause some issues if changes to the parent shouldn't be reflected in the details. As such it's also common to not inherit and just be explicit about the DTOs transferred as a response of those endpoints.
Answer
So at the end instead of returning the product object (that is then serialized with zod), I create an explicit DTO object, that will consume in this case my product details (the basic ones) and an owner (user object)?
Asian black bear
Yes, you never expose your exact entity. You build the DTO from the entity manually or use a mapper for that. In case you need to enrich the returned object you combine all the relevant data into the DTO.
Yea, I thought zod would be the one, who controls the output. Like I can return from my handler:
return {
    id: "id_13456",
    title: "some title",
    description: "some wonderful desriptipn"
} 

And when zod is like this:
const result = z.object({
    id: z.string(),
})

Then the response of the API would be:
{
    id: "id_13456",
} 

Even if I returned the whole object. That also works for nested objects
Asian black bear
My original point still stands: don't try to reuse needlessly complex objects with nullable fields across multiple endpoints that return different responses. Make the DTOs unique to each endpoint if they are not strictly the same, sometimes it even makes sense to duplicate them to make changes more comfortable.
How you map is up to you, it can be a trivial or sophisticated as you need it to be.
hmm ok. Then I'll do it like you said. Thanks!
Asian black bear
One thing that may be of interest for really advanced enterprise projects if you work on those at any point in time: rather than using CRUD some elect to work with CQRS. In this architecture you model commands and queries very granular and specific to their capabilities or features and end up with unique DTOs for each of them.
@Asian black bear One thing that may be of interest for really advanced enterprise projects if you work on those at any point in time: rather than using CRUD some elect to work with CQRS. In this architecture you model commands and queries very granular and specific to their capabilities or features and end up with unique DTOs for each of them.
yea, the only thing that feels just wrong for me is to create soooo much DTOs. It looks just... ugly... and thats only for 4 objects in total... my folder will blow up, when I have more tables. (Ignore the "Request". They are plain json objects)
Asian black bear
That's why people prefer to consolidate them by feature and not drop them all in a single folder.
hm yea, ok