Home > Software engineering >  REST - Strongly typed models
REST - Strongly typed models

Time:07-21

I'm thinking about using a new model structure for my new api.

Current state

I have GET, POST, PUT and DELETE endpoints. Just forget about DELETE because it only takes a query parameter and has no model.

Those three endpoints all use the same model:

Car Model 

id: string | undefined;
manufacturer: string;
date: Date;

Problem

When requesting a single car via the GET endpoint I theoretically could get a car without a id. So when I want to pass the ID to the DELETE endpoint I always need to check that the ID is not undefined.

Possible solution

Providing different models for each HTTP method.

1. Car Model for POST request

id: string | undefined;
manufacturer: string;
date: Date;
2. Car Model for [PUT request, GET response, ...]

id: string;
manufacturer: string;
date: Date;

I also want to provide PATCH request I would also need this model

3. Car Model for PATCH request

id: string | undefined;
manufacturer: string | undefined;
date: Date | undefined;

Questions

  1. In total I would need three different models. But is that really worth going for and considered as a 'Good Practice'?
  2. If that is a 'Good Practice' how should I name the models?

CodePudding user response:

You've only got one model, called Car. It looks like this.

interface Car {
  id: string;
  manufacturer: string;
  date: Date;
}

Calling the other ones models is missing the point. At no point are those partial things stored in a database or in any sort of long-term storage. They're transient.

As you've already noticed, for PUT requests and GET responses, you're always receiving or returning (respectively) a fully defined object, so Car suffices for that.

Your proposed PATCH type is simply Partial<Car>. No need for a new type; TypeScript provides this type for you. This can also be used if you want to have a query interface, where a GET request gives you a partially-defined car and finds all matches.

For your proposed POST, you want an optional id but all other fields required. That's a bit more complex but should look something like

Omit<Car, "id"> & { id?: string }

which reads "remove the id field from the Car object and replace it with this optional one". Omit is another utility type that TypeScript provides us with.

You can give these things names. In fact, I encourage you to. I'm always an advocate of naming more things, as it provides more documentation in the code itself as to why we're doing what we're doing.

type PatchCar = Partial<Car>;
type PostCar = Omit<Car, "id"> & { id?: string };

PatchCar screams "Car but for PATCH" in its name, while Partial<Car> doesn't really say why we've just made all of the fields optional.

The important thing is that there's one representation of the Car model: namely the interface called Car. Everything else is just an offshoot of that. If we add fields to Car, we only have to update it in one place, not several. Remember, don't repeat yourself.

CodePudding user response:

In my experience, it's almost always a bad practice to have multiple variants of the same model structure in your code. (see DRY principle.)

TypeScript utility types can possibly help you out.

  • Related