Best Practices


Designing a REST API isn’t easy. Anyone who claims differently either hasn’t designed one, hasn’t designed one for a moderately complex system, or is a rare genius who has never found anything to be challenging in their lives. One can set out determined to adhere to REST design principles where a client user can intuitively find the endpoints they need, where all “objects” in the system are represented as resources that can be acted upon with one of four HTTP operations (POST, GET, PUT, DELETE) and users can do whatever they’re trying to achieve with as few calls as possible. But challenges will appear. Immediately.

A couple years ago at Jama, we set out to build a new REST API to eventually replace all our SOAP and DWR interfaces. Two key design challenges were:

  • Chatty vs. Chunky API Design
  • Modeling Resources vs. Business Processes

The second challenge is difficult, and it’s one I could talk about all day (and perhaps I will in a later post). But, today I’m going to focus on the Chatty vs. Chunky problem.

Chatty vs. Chunky APIs


It wasn’t long after people started writing against our initial iteration of the API, that the conversation about “chattiness” came up. API developers have been having conversations about this concept all over the web, but the essence of it is this: Users need to get the data they are after in as few calls as possible. If they have to make too many smaller calls to get all the data they seek, then the API is too “chatty” for their needs. On the other hand, if API calls are too large, and return more data than is needed, the API can be considered too “chunky”. The two ends of this spectrum are also referred to as Fine Grained vs Coarse Grained APIs.The user call would look something like this:

Let’s give an example to illustrate this. Say I have an API call to retrieve user comments on a blog. Comments can be made on a blog post, so, if they are, they should have a page property to indicate the page they appear on. They should also have an author field to indicate who made the comment.

The user call would look something like this:

And the JSON data in the response might look something like this:

This example is overly simplified, but you can see in the comment, the author properties have the values 23 and 1 which you can assume are the authors’ unique user IDs. Similarly, the blog page these comments are on is being referred to by its page ID of 27. The second comment is also in reply to some other person’s comment, so that inReplyTo property has a value of 796 to reference another comment ID.

This payload is very short and simple, but it presents some problems if the API user is interested in knowing more about the author than just the author’s user ID (you can be sure that they at least want to know the author’s name!)

First, if you’re the user of this API, you’re not given any indication of how to retrieve the complete user information associated with user ID 1, nor the complete information for what content is on page ID 27. This is a problem with “discoverability.”

But even if that discoverability problem is solved, you would still need to make a separate API call to retrieve that user. Further, if you are retrieving a collection of hundreds of comments, you would potentially need to make a user call for each comment you retrieve. You would need to make hundreds calls to the API to get the information you’re looking for. Hence the term “chatty”.

But let’s look at the other end of the spectrum. The API could attach the complete information of all object properties to the response and you’d get something like this:

As the API user, you now have all the information you need. The full author information is available, you know the full details of the blog page that the comment was posted on, and you can even see the entire other comment that this comment was in reply to directly in the inReplyTo value.

But you have a myriad of new problems.

The first is the sheer size of the payload returned. This is a fairly simplified example. User and page objects would likely have many more properties than these examples show. If you are retrieving hundreds of comments and you’re getting a full object for every property on each comment, this is going to be a lot of data. As the client user, you may not have bandwidth concerns about getting this much data across the wire, but it certainly could take the server more time to assemble all that data, and long-running transactions are much harder on a server’s CPU & memory. Especially when the server is dealing with multiple concurrent requests.

It should also be noted, if it turns out that 99 out of 100 retrieved comments were all authored by the same user and all the comments are posted on the same page, then most of the user and page objects in your results are going to be redundant. The time the server spent assembling user and page data was mostly wasted.

Data inconsistencies are also bound to come up. In this case, what if there aren’t any restrictions on how many embedded replies you can have in your comments section? If someone replies to the first comment, then someone replies to that reply, then someone replies to that reply… you get the point. How should that data be represented? You could choose to go one level deep but API users may be confused about the point at which the API decides to cut off the addition of further data, and how they should write a client to consume it.

These are the problems with a chunky API.

The API could also try to find some kind of compromise and attach only partial data. While the full author’s info may be retrievable from some user call, the comment payload may only contain the author’s user ID, first name, and last name.

But, there are problems with this approach as well (this all sounds so negative!). Inconsistent partial objects make it harder to intuitively work with the API. The same inconsistencies described with the inReplyTo example above apply here as well. Also, the API may not provide the data you are looking for in the first place. If you just want the author’s name and avatar, but the avatar isn’t provided, you’ll still need to make a separate call to get the full user object just so you have that data.

So, what’s the best approach then?

The Solution

When making design decisions and facing a spectrum like this where both ends of the spectrum provide their own challenges, we can only strive to find balance and be practical. We need to find a solution to the Chatty API design problems while avoiding going down the Chunky API route. We want to remain simple, clean, and RESTful. We also want to make our API flexible enough to allow users to meet their own needs in the chatty to chunky continuum.

While the “partial data” example above attempts to find a compromise between Chatty and Chunky, I’ve already pointed out some issues with a compromised approach. Instead of compromising, what if we instead adhere to everything we like about the chatty API model, but give users the extra facilities to add data to their response?

Let’s look at another approach. With this approach, the calling user makes a request for comments, but specifies they would like the author field included as well:

The response would look like this:

This is a lot to ingest at once, but the payoff is worth it. You can see here that the comments payload is now listed under data. Also, there are now separate properties in the response called links and linked.

The data section is exactly the same as the one given under the Chatty API example above. But, with the helpful links and linked properties, the lack of associated data is much less of an issue.

The links section takes care of the “discoverability” problem I mentioned above. For any property that simply displays an ID (like authorpage and inReplyTo) the links section will describe how you can plug that ID into a separate API call to retrieve the information you’re seeking.

The linked section is where we really begin to solve the problems associated with Chatty APIs. In the request, you have asked to include any user objects that are referenced in any of the comment author fields. The resulting response now gives a data store of user objects in the linked section for any user IDs specified under author. This removes the need to make any further calls to the users endpoint to get all the information you’re seeking. But it also solves the redundancy problems since a user will only appear once per user ID. In other words, you could have 99 comments where the author value is the same, but you’d only have one inclusion of that author’s data in the linked section. This also (potentially) takes less time for the server to assemble than a full attachment of all author data to each individual comment since we’re only loading and assembling the author data once for the data store.

This solution offers the simplicity of the chatty API at its base. It’s only giving you the basic information you’re requesting. But, it additionally gives you discoverability and the flexibility to ask for further data in the same request so we solve the main problems associated with chatty APIs. We’ve managed to address all of these previously mentioned problems:

  • Discoverability
  • Not enough data i.e. the need for repeated API calls or chattiness
  • Large payloads associated with chunky APIs
  • Server processing time associated with chunky APIs
  • Redundancy

Here at Jama, while designing our REST API, we’ve set out to find balance in our API design with an emphasis on ease-of-use, practicality and flexibility for our users. This chatty vs chunky tradeoff is just one aspect of the challenges we face to build an API that works for us and our users. We’ve come up with a REST JSON response data structure very similar to the one in the example above. It allows us to be uncompromising in resources being clean and lean, while still allowing our users to retrieve the data they seek. It’s loosely based on an initial version of the JSON API specification and we feel it elegantly satisfies our and our users’ needs.

Comments? Questions? I’d love to hear your feedback!