In a previous post we looked at many-to-many relations. This time we will see how to model one-to-many relations in a RESTful API.
An important question here is, if both sides of the relation can exist on their own (similar to typical many-to-many relations) or if the many-side is tightly coupled to the one-side. In the following we will examine both cases with different examples.
Tightly coupled relations
It is quite common for one-to-many relations that the many-side is tightly coupled to the one-side.
For example, consider a relation between articles and comments. An article can have many comments while a comment always belongs to exactly one article. Comments cannot move from one article to another article and the deletion of an article also deletes attached comments.
In such a scenario it is often a good idea to express this type of relation via the resource URI. In this example we can model comments as a sub-resource of articles. For example: /articles/<article-id>/comments. We can then use standard CRUD operations on this sub-resource to create, read, update and delete comments:
Getting all comments of an article 123:
GET /articles/123/comments
Creating a new comment for article 123:
POST /articles/123/comments Content-Type: application/json { "message": "Foo", ... }
Updating comment 456:
PUT /articles/123/comments/456 Content-Type: application/json { "message": "Bar", ... }
Deleting comment 456:
DELETE /articles/123/comments/456
Here the relation is only expressed by the resource URI. We do not need specific operations to attach or detach a comment to / from an article.
Both sides of the relation can exist on their own
Now let's look at a different example: A relationship between a player and a sports team. A team consists of many players and a player can only play for one team at a time. However, the player can change teams or be without a team for some time.
In this situation we use an approach similar to many-to-many relations. We use two separate resources for players and teams: For example /players and /teams. Both resources can be managed on their own (for example via common CRUD operations).
Next we create a sub-resource for the relation, for example /teams/<team-id>/players. This sub-resource is only used to manage the relation between both resources. We can now use GET, PUT and DELETE operations to retrieve, create and delete relations.
Getting players assigned to team 123:
GET /teams/123/players
Assigning player 42 to team 123:
PUT /teams/123/players/42
Unassigning player 42 from team 123:
DELETE /teams/123/players/42
It is part of the servers logic, to make sure a player is only assigned to a single team. Assume player 42 is currently assigned to team 122. Now, when a PUT /teams/123/players/42 request is issued, the server has first to unassign player 42 from team 122 before he is assigned to team 123. So, this request also modifies the /teams/122/players resource, which should be remembered if a cache is present.
Note we do not need a request body for any of these requests because the sub-resource is only used to manage the relation which can be fully determined by the request URI.
We can also model this from the player-side of the relation. Again, we use a new sub-resource: /players/<player-id>/team.
Getting the current team of player 42:
GET /player/42/team
Assigning player 42 to team 123:
PUT /player/42/team/123
Unassigning player 42 from the current team:
DELETE /player/42/team
Note: For the DELETE request no team id is required (a player can only be in one team).
Summary
We looked into two different approaches of modelling one-to-many relations with a REST API.
If both parts of the relation are tightly coupled we can often express the many-part as a sub-resource of the one-part and use simple CRUD operations. The relation is only expressed via the URI and no special assignment operation is needed.
However, if both sides of the relation can exist on their own, we use two separate resources and add sub-resources to manage the relation.
Comments
Hector - Friday, 7 August, 2020
crystal clear. thanks.
tivalii - Tuesday, 15 September, 2020
Thanks for this post.
And I have a question. What if you I want an extra data to be added, like date when player was joined to the team. How to handle this API routes/requests
mscharhag - Tuesday, 15 September, 2020
Hi tivalii,
in this situation it is often a good idea to create a resource with a separate name. For example "player + join date" can be be a "member" (e.g. /teams/123/members).
Additional information can be passed in the request body of the PUT request. However, in this example the join date can be set by the server automatically and no request body would be needed.
Stefan - Tuesday, 14 November, 2023
Very usefull article ;p
Leave a reply