mscharhag, Programming and Stuff;

A blog about programming and software development topics, mostly focused on Java technologies including Java EE, Spring and Grails.

Monday, 15 June, 2020

REST: Managing Many-To-Many relations

Introduction

Managing relations between multiple resources can be an essential part of an RESTful API. In this post we will see how many-to-many relationships can be managed with a REST API.

We use a simple user / group relation as an example. Let's assume users and groups are two separate resources (e.g. /users and /groups) and we want to provide a way to manage the relationship described by the following points:

  • A user can be added to multiple groups
  • A group can contain multiple users
  • Users can only be added once to a group

 

 

Many-to-Many relations can be divided into two different types:

  • Relations without additional information besides the actual relation
  • Relations that contain additional data. In our example this can be something like a group member status (e.g. a user is a moderator in one group and a simple member in another group)

In this post we will only look at the first type of relation. Relations with additional data will be covered in a future post.

Of course there is no single correct solution to this problem. The next section describes the approach I made the best experience with. After that, we will have a look at some alternative solutions.

Modeling sub-resources and GET operations

First we introduce two sub resources:

  • /users/<user-id>/groups represents the groups assigned to the user with id <user-id>
  • /groups/<group-id>/users represents the users assigned to the group with id <group-id>

Using the GET verb we can now request both collections.

Getting users assigned to a specific group:

GET /groups/<group-id>/users

Getting groups assigned to a specific user:

GET /users/<user-id>/groups

Adding and Removing users

Now we need a way to add a user to a group. We do this using the PUT verb.

Adding a user to a group:

PUT /groups/<group-id>/users/<user-id>

No request body is needed for this operation.

For example, this adds user 32 to group 21:

PUT /groups/21/users/32

Note, here we need to ask the question if adding a user to a group is idempotent. In our example this operation is idempotent: A user can only be added once to a group. Therefore, we use the PUT verb. If the assignment operation is not idempotent (e.g. a user can be added multiple times to a group) we have to use POST instead of PUT.

You can read more on idempotency and the difference between POST and PUT in my other posts.

As an alternative we can also model this operation from the /users perspective if we want.

Adding a group to a user:

PUT /users/<user-id>/groups/<group-id>

To remove a user from a group we use the DELETE verb.

Removing a user from a group:

DELETE /groups/<group-id>/users/<user-id>

For example, this removes user 32 from group 21:

DELETE /groups/21/users/32

or vice versa, from the /users side:

Removing a group from a user:

DELETE /users/<user-id>/groups/<group-id>

Note, while we perform PUT and DELETE operations on /groups/<group-id>/users/<user-id> there is no need to implement GET for this URI. GET /groups/21/users/32 would simply return the same result as GET /users/32 (as long as the user is part of the given group)

Alternative solutions

Introducing a separate /group-members resource

Another approach is to create a completely separate resource that manages the relation between users and groups.

Adding a user to a group might look like this:

POST /group-members
{
    groupId: 31,
    userId: 23
}

To get the users assigned to a given group, we can use a similar request as in our previous solution:

GET /groups/<group-id>/members

However, this time it returns a list of group-member resources.

This approach creates a bit more complexity (we add a completely new resource that might have its own identifier). However, it is especially useful if we want to add some additional information to the relation (e.g. the join-date of a user). We will have a closer look at this in a future post, when look at relations with additional data.

Managing relations as part of normal resource updates

Another approach is to use the standard update operation to manage relations. For example:

PUT /users/23
{
    "name" : "John",
    "groups" : [
        { "id" : "42" },
        { "id" : "43" }
    ]
}

While this can work fine in certain situations, I cannot recommend this approach.

Resources and relations are often changed independent from each other. Merging both operations together can cause various problems. For example, from the security perspective both operations might need different permissions. A client might be allowed to a add a user to a group but might not have permissions to update the user itself.

With a lot of relations this approach can also be very troublesome for performance. So it is typically better to provide separate operations for updating resources and relations.

Comments

  • Momo - Tuesday, 22 September, 2020

    Hi
    the article was so good but how can I make requests when I have a join table in my relation. User-->UserGroup<--Group and in the join table I just have composite key by userid and groupid?
    I can get user with it's groups but how can I set a user to a specific group via the join table in my restapi?

Leave a reply