Wasim
Wasim

Reputation: 5113

REST API design best practice for multiple collection in single call

I have 2 resources /users and /products and I can retrieve the users products with /users/{id}/products. This is simple enough and I would return a JSON response like this:

{
   "items": {
      ...
   }
}

Now what if I wanted to get the user and their products at the same time? I understand I could use ?expand=products on the /users/{id} call and return data like this:

{
   "item": {
      ...
      "products": {
          ...
      }
   }
}

But is this best practice? Or is it better to return something like this:

{
   "user": {
      "item": {
         ...
      }
   }
   "products": {
      "items": {
         ...
      }
   }
}

So my questions are:

  1. What's the best practice to return multiple data collections in a single call?
  2. I know it's better to reduce the number of API calls you're making, but if my application requires a lot of information at a certain point in time, e.g. user, products, currencies etc.. Is it better practice to use their respective api endpoints and make multiple calls or create an endpoint which returns all the data in one call?

Upvotes: 4

Views: 3156

Answers (3)

Russ Jackson
Russ Jackson

Reputation: 2112

I think the answer to your question might be in slides 75 thru 77 of the slideshare associated with the video you referenced (http://www.slideshare.net/stormpath/elegant-rest-design-webinar).

I believe another good (similar) approach in situations like this is to define a higher level composite resource (such as 'account', 'userProducts', etc.) that encompasses the two sub-resources that you need, sort of like a container for them. For example:

{
    "account" : {
       "id" : "123",
       "user": {
          "items": {
             ...
          }
       }
       "products": {
          "items": {
             ...
          }
       }
    }
}

They do something similar as you see in the slideshare whereby they create the 'container' resource, but instead of embedding the collection data they provide the links to the individual collections.

But I'm not sure multiple collections is the best fit for the example you provide. In your case I would think it might be appropriate to return the entire list of products from /users/{id}/products and simply flag the ones that are actually owned by that user (with the non-owned ones interpreted as being available to that user), such as:

{
    "user" : {
       "id" : "123",
       "products": [{
          "id" : "A456",
          "owns" : true,
          "details": {
             ...
          }
       }]
    }
}

You may even want to control whether or not 'non-owned' items appear in the results, such as:

/users/{id}/products?ownership=all

In other words, "give me the list of products available to the given user and flag the ones they actually own."

or

/users/{id}/products?ownership=owned

Likewise, "give me only the products the given user actually owns."

In the resource oriented architectural style for REST, query params are appropriate for selection (aka searching; which rows), pagination (how many), sorting (which order) and projection (which columns).

The 'ownership' query param fits into the selection/search category.

Upvotes: 0

dbrumann
dbrumann

Reputation: 17166

A usual approach for including other data is something like this (your 2nd example):

{
    "users: [
        {
            ...
            "products": [
                {
                    ...
                }
            ]
        }
    ]
}

If you usually wrap your results in an "items"-container, I would only do this for users, but have seen both being wrapped in a container. I don't think there's a real consensus, but I would lean to the first. As a side note because you mention it in your question I wouldn't switch between "item"/"items" as this would defeat the purpose of having a generic container for your data. Rather always use items and if you only have one item return it as an array.

Whether you should include items depends. Since it's not feasible to have pagination for the included items I tend to not allow include when it's likely there will be a lot of items returned. For your example (products owned by user) I would likely not use it if it means products (e.g. in a catalog) from a vendor, but it would be ok for me if it's some kind of shopping cart.

Upvotes: 1

Adam Hopkinson
Adam Hopkinson

Reputation: 28793

It sounds to me like /users/{id}/products and /users/{id}?expand=products are the same thing, so I'd stick with the first (non-query string) to keep your urls consistent. Then return data in the format:

{
    username: "Dave",
    userid: 123,
    products: [
        {
            productname: "amazing product",
            productid: "ab123"
        }
    ]
}

A users' products are then clearly sub-data of the user

For question 2, should I use many endpoints or just one, you make the call depending on how you're building that resource (if it's well indexed/cached, it might be 'cheaper'), whether it's a security risk, how many of each item you would actually make use of etc.

Upvotes: 3

Related Questions