4

When designing REST API I generally start by identifying the resource of my system, to give an example let's say that we manage multiple blogs, each one with multiple posts, each one with multiple comments.

The standard REST API for a case like this would be to have some endpoints like these ones:

/blogs //all blogs

/blogs/:blog_id/posts //all post of blog :blog_id

/blogs/:blog_id/posts/:post_id/comments //all comments of post :post_id

This is a very simple and effective way to implement the basic REST functionality, but I think that what comes after it is way less clear.

1 - What is a proper way to expose the possibility for the client to ask for a subset of blogs?

A possibility would be:

/blogs?blog_id=:blog_id1&blog_id=:blog_id2

What do you think about it?

2 - What is the proper way to expose the possibility for the client to ask for a a blog with its posts all in one request?

A possibility would be:

/blogs?_with=posts

What do you think about it?

3 - If you like the approach at question number 2, what about sub-resources of sub-resources?

/blogs?_with=posts.comments

Is this acceptable? How many nested elements does it make sense to require in this way?

What about

/blogs?_with=posts.comments.authors.posts.comments

Is there some objective reason (design-wise, I'm not talking about things like performance) to consider this kind of endpoint bad designed.

I understand that this question is mostly subjective, but I think that there are some "best practice" that may be identified and implemented across a wide variety of systems.

7

3 Answers 3

2

Filtering

Query parameters are a good semantic fit for this, and plenty of APIs are implemented this way, see for example OpenStack API. Using query parameters where each filter maps a single value directly to a single attribute makes it easy to combine filters for either representing a logical intersection between two result sets (AND) or a logical union (OR), but not both at the same time. E.g. the following request can be interpreted two ways:

GET /blogs/:blog_id/posts?created_at=today&author=12

The request can either be saying: Give me all posts for the given blog that were created today and whose author ID is 12, or it can be saying: Give me all posts for the given blog that were created today or whose author ID is 12. Whichever is used can assumed to be convention within your particular API, I believe most people would understandably believe the ampersand character to prefer the AND approach but there's no reason it can't be a union of the two sets instead.

It may be necessary when querying for resources that you need more complex logic to determine which results are formed. In this case you need to be a little smarter in how you set up the query parameters for the filters and would probably need some form of query language to be implemented. The actual parameters in the URL would not map to attributes of your resources, but the values instead contain references to the attributes, along with other assorted things that languages have such as operators like '>', '<' and keywords like AND and OR. See for example JIRA JQL:

GET /blogs/:blog_id/posts?query=created_at IS today AND (author IS 1 OR author IS 2)

The approaches can be combined, so that you can have a one-to-one mapping for query parameters/attributes yet still support more robust filtering:

GET /blogs/:blog_id/posts?created_at=today&author=in:1,2

From a conceptual point of view I don't think there's much difference between using query parameters vs. appending additional strings onto the path. I could just as easily represent the first request like this:

GET /blogs/:blog_id/posts/created_at/today/author/12

I have a feeling though that supporting this approach would be a bit tougher to match routes against versus just using query parameters as well as determining whether a given section of the path is an 'attribute' or a 'value', but your mileage may vary. I mention this mostly because you may want some more fundamental filters to actually be represented on the path for easy recognition/ease of use:

GET /blogs/:blog_id/posts/new

That request could return the same results as this request:

GET /blogs/:blog_id/posts?created_at=today

Embedded Data

There are systems that do exactly what you are suggesting. See for instance league/fractal. They take it a bit farther, wherein you can pass parameters to the embedded data to filter it (which can be combined with the approaches from above). This means you can make requests such as:

GET /blogs?include=posts.comments:created(today)

This request will return all blogs, their posts, and only the comments created 'today' (versus just all comments on all posts).

The size of your set of resources could have a limiting factor on how responsive your API is, so if you have a top level resource with a chain of 40 related resources beneath it you may need to exercise caution in your requests to keep from overloading your resource server, or provide an upper limit to how deep in the relationship chain you can go.

There's also concerns on implications to pagination. If you're paginating the top-level resource you are requesting, do you also paginate the embedded resources? Supporting HATEOAS-style link relations can help your client obtain references to further dive into the paginated embedded data listing that it may be interested in and can mitigate the issue some, but is a factor for consideration.

1
  1. There are three options I've seen. The first is to do what you propose, which will in most cases result in getting a Long[] or somesuch on the server side. The second is not quite a query language - just allow a delimited list of ids as a string, then break it out on the server. The third would be the full-blown query language route, as proposed by @JeffLambert. If you don't need a query language, it's less mental load for everyone to pick one of the other options. If you want a human-hackable URL, then a comma-delimited list works well. Some frameworks will have an easy time making multiple query parameters with the same name for you.

  2. I've typically seen it done as include=[foo, bar, baz.blop], but there's no magic in naming the query parameter include.

  3. As for how many elements it makes sense to refer to, in general I'd agree with @RobertHarvey to be guided by the Law of Demeter. I would allow any number of includes to a depth of one e.g. include=[foo, bar, baz, blort, ...] but no include=[baz.blop]. Of course, that depends on the needs of your users. I could contrive of a performance-critical application where lives are on the line and you need nesting to depth 12 in one query. That should be the exception, not the rule. The web is more performant than most people give it credit for, and usually multiple queries are just fine.

In an ideal world, I'd say don't bother with the include parameter at all and just give links off of your base object to foo, bar, baz, etc. Make sure you have a real, measured performance issue with multiple queries before you start down the include path.

-2

REST doesn't care what spellings you use for your resource identifiers

What is a proper way to expose the possibility for the client to ask for a subset of blogs?

How would you do it with a website?

What is the proper way to expose the possibility for the client to ask for a a blog with its posts all in one request?

How would you do it with a website?

If you like the approach at question number 2, what about sub-resources of sub-resources?

How would you do it with a website?

Once you figure out how to do it with a website, take that same design, and think about how to make the web pages in that site machine readable.

I'm not really sure if the website is a good comparison

You may want to review, for example, Asbjørn Ulsberg's discussion of API Change Strategy

In many ways, a web API has more in common with a web site than to a statically linked software library.

2
  • 1
    I'm not really sure if the website is a good comparison. When designing the website pages, developers expose a fixed set of information. With some API they generally try to be more flexible, allowing the client some freedom in asking more or less data. Mar 23, 2018 at 8:52
  • Websites and Web APIS speak the same language (HTTP) and play with the same rules (WWW). The websites comparision is valid and useful since in both cases "navigation" matters. Narrowing down websites to mere static web content is too simplistic.
    – Laiv
    May 1, 2019 at 21:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.