RESTful API design concerns

marcin piczkowski
Marcin Piczkowski
Published in
3 min readJul 26, 2019
Photo by Omer Rana on Unsplash

Originally published at dev.to

When working on real production system recently I asked myself a question:

Should REST API be constrained by the current architecture choices?

To illustrate the problem I invented an example.
Let’s imagine a system which is used for renting cars. The business is sort of Airbnb for “car rental”. Small companies which rent cars can register their spot in the system to access wider range of potential customers. The system allows them to manage their cars but not any other rental spot cars.

Let’s imagine we’re a startup and build the system. We start small and use relational database to store all cars in a single table. Each car is identified by unique ID in this table.
We want to export a RESTful API for our system.

Among others, we would need APIs to browse all cars in a spot and get single car details.

The API for listing all cars in a spot could look like:

GET /spots/{spotId}/cars

It would return a list of cars from which we could get IDs of the cars.

The API for getting a car by ID could look like:

GET /spots/{spotId}/cars/{carId}

or

GET /cars/{carId}

Since we want to be aligned with good practices of API design, we’ve decided to go with the longer path, because the cars are resources which cannot exist alone and always belong to a given spot. The path /spots/{spotId}/cars clearly explains the relationship.

However, the spotId in the path is redundant.
Since we have all the cars in single table and we know the car ID, because we got it from the /spots/{spotId}/cars endpoint, the only variable we really need is the carId.
Of course, in our relational database we will have relation from car to a spot and we could add the spotId in out query, but it's not crucial.

E.g. we could have a query like:

select c.* from cars c inner join spots s
on s.id = c.spot_id
where s.id = :spotId and c.id = :carId

but it would get the same result as:

select * from cars 
where id = :carId

So, should we use /spots/{spotId}/cars/{carId} or /cars/{carId} as the endpoint path?

I’ve been thinking about it and both options have pros & cons. As mentioned before, the longer one sounds more appropriate from the semantics of the API perspective, but the shorter one is easier to use and implement in the current state of the backend architecture.

If we think about the evolution of our service, then we can imagine that we may want to split the cars table into separate per each spot. This may happen if the volume of data grows, or if we want to distribute database and set several instances in locations nearby to each spot (for better performance and scaling). Each car would then be unique but within the single DB instance (or instances if we consider a cluster of instances in specific location for given spot). Then we could only distinguish a car by a pair of spotId and carId and the longer API path would make more sense.

Finally, I answered to myself:
API is not still. When the architecture evolves so does API.
What currently makes sense and is simple (/cars/{id}) may not be applicable anymore in future. In future, if I need to split car storage into separate table/database for each car rental spot, the new API may look like: /spots/{spotId}/cars/{carId}. On the other hand this might never happen and as Donald Knuth used to say “Premature optimisation is the root of all evil”.

What is your answer to the problem. If you have more thoughts please share with me and other readers in comments.

Thanks for reading! Feel free to hit the recommend button below if you found this piece helpful.

You can connect with me on Twitter or subscribe to my mailing list if you want to get occasional info about my recent work.

--

--