Developers January 14, 2021

Best practice to fetch multiple resources by ID from REST API?

Justin @Harrjm

I'm working on a little CMS-like side project, I want to build it using best practices in case I release it to the public. Content in this CMS is stored in bundles and you can either fetch one or multiple bundles at a time by their ID.

What would be the best practice for fetching multiple bundles by IDs as RESTful as possible and support as many standards (frameworks) as possible?

  1. https://mysite.com/api/bundle/1,2,3
  2. https://mysite.com/api/bundle?id=1&id=2&id=3
  3. Other?

#1 follows better REST practices by using the path for resource IDs and reserving query strings for filtering and sorting. But #2 allows you to fetch in a more standard way using something like Axios:

axios.get('http://mysite.com/api/bundles', {
   params: {
      id: ['a', 'b',' c']
   }
})

What would you do? It'd be especially be helpful if you know a reference of someone doing this on another service.

  1. 5

    Hey Justin, I think you might be better off with a third approach:
    https://mysite.com/api/bundle?id=1,2,3.

    (This is also a popular answer on SO).

    1. 1

      I would also take this approach. Think of it like having a filter on your index resource, it just accepts multiple values.

  2. 3

    The truth is no matter how you look at this, it isn't RESTful, so if you are trying to build it RESTfully, you are out of luck. That doesn't mean you can't, but that you shouldn't.

    First the question is why are you trying to do this, it isn't a best practice and even bad. There are things like caching and security which are different between resources and make this just a really bad idea.

    There is a #3 however, but that requires understanding your resources. If a user frequently will fetch a group of resources together. You should instead expose this concept to users by:

    • POST /bundle-groups, data: { ids: ['bundle-1', 'bundle-2', 'bundle-3']
    • And then later GET /bundle-groups/BundleGroup which can return the list of bundles that were added to it earlier.
    1. 1

      Thanks for the detailed response! Yeah definitely get that it wouldn't be RESTful, I'm just trying to keep it as close as possible to avoid any issues like this one seems to have uncovered.

      Caching is a concern but I figured I could cache a JSON response from /bundle/1,2,3 the same as /bundle/1, and Cloudfront at least allows you to cache on query string if you choose, but I only looked at it on face value for now and haven't dug much into it. Similarly with fetching resources my query to the DB would essentially be "Find these bundle where this is the owner", so if they passed a resource that didn't belong to them then that bundle simply wouldn't be populated.

      That's a great idea! As you said the point of this was to keep bundles small and allow you to fetch multiple together. I had already contemplated the idea of bundle groups but the way you propose it of simply posting multiple bundle IDs to create the group and then later fetching it is really clean. I had hoped to rather allow creating this bundle groups on the fly but just requesting any bundles but if it causes this much headache it's not worth it.

      I'll also have to consider just allowing users to 'tag' bundles, and then fetch by tag instead of ID which would allow you to fetch multiple bundles by only GETting one tag.

  3. 1

    Companies I've worked for did #2. The back end MVC automatically converted that into an array.

    For those endpoints the return structure was always a list. no matter how many id= were there.
    { "list": [{...}, {...}, {...}] }

  4. 1

    I think you should go with #1 as the URLs are more concise and you keep query parameters reserved for optional filtering and sorting. Also, REST API designs should follow cross-platform best practices which specifically means that you shouldn't optimize for the way they're called in one specific HTTP client.

  5. 1

    You shouldn't do id=1&id=2 etc for this may cause some issues with overriding parameters. The most restful way would be to do /1 /2 /3, it makes the most sense from my perspective to either do it with promise.all or to call /bundles with a list of ids in the request body not the parameters..

  6. 1

    How about sending the array in a POST body? I feel like you'll have more control that way.

    1. 1

      I just don’t think it would make much sense to “get” your bundles via a POST. I would think that’d lead to a lot of confusion wouldn’t it?

      1. 1

        You’re right. Do you plan to have any kind of limit on the ids or is it something that’s not gonna be problematic down the road?

    1. 2

      GraphQL is in the pipeline but I’m much better at REST currently so while it’s still a personal project I’m building it in REST and then will layer add a GraphQL api once it’s all hammered out.

      1. 1

        Hasura auto generate your GraphQL API based on a PostgreSQL database.

        If you want to try it quickly I built Nhost.

        1. 1

          That's really cool never looked into Hasura but that sounds super helpful, thanks!

  7. 0

    In REST you can identify a specific resource or you can return a collection a level higher and filter it.

    Maybe checkout GraphQL. It is much better for allowing flexibility on how you retrieve data. It's a really different paradigm than REST, so I understand being hesitant. Since this is a personal project, there's no real reason you can't go down that path.

    1. 0

      Yeah GraphQL is seeming like the better choice and I'm learning it as we speak. It's just a little overkill because there is no relationships, no nesting, I don't want to be able to select parts of an object individuals or anything. I just want to store multiple JSON objects, on the same level, and fetch one or more of them. I may just go the route of adding tagging to objects so you can grab multiple by assigning them a tag and fetching all objects by tagname.

      1. 2

        Depending on how much of a rabbit hole you want to explore, I would do some digging on using documents as a resource and see if there are any approaches that seem a better fit than your initial inclinations.

        It doesn't sound like GraphQL is a good choice.

      2. 1

        If you don't need nesting or require partial response attributes then GraphQL is an overkill. It's also a little harder to consume on the frontend because you would need to enter query including all the params.

        1. 1

          Yeah and making it easy to consume on the frontend is the #1 goal of the project, which is why I was hoping simple logical REST-like paths would work but it seems to be causing more confusion than not. Tagging seems to be the best option I have but even that is a complication I wanted to avoid.

Recommended Posts