In the article Building Web Services the REST Way, the author states:
Design to reveal data gradually. Don't reveal everything in a single response document. Provide hyperlinks to obtain more details.
Why is this considered a good practice?
UPDATE: I realize that this practice is also known as HATEOAS.
2
I think the main advantage you gain when following this principle is the API’s browsability. If different resources are interconnected in this way, you only need to provide a couple of entry points (well known URLs that provide clients a way to start exploring your API) and the clients are virtually free from the responsibility of building URLs. You can just provide them with links.
Another important point is that with each link, you can provide some semantically significant description that describes the nature of the link. This is a good way to describe relationships between resources.
Consider the following examples:
A non-granular approach
GET /users/1
Response:
{
"id": 1,
"name" : "Tom"
"addresses" : [
{
"id" : "1",
"email" : "[email protected]",
"street" : "Sesame Street",
"city" : "London",
"country" : "United Kingdom"
}
],
"posts" : [
{
"id" : "1",
"title" : "HATEOAS Examples",
"content" : "Yada yada yada",
"created" : "2014-01-01"
},
{
"id" : "2",
"title" : "Foo bar baz",
"content" : "Lorem ipsum dolor",
"created" : "2014-01-05"
},
// many many more
]
}
- How do I limit the number of posts I retrieve?
- How do I limit the number of addresses bound to my profile that I retrieve?
- How do I delete an address related to my profile?
- How do I update a post?
- How do I get the posts from a specific day?
- Can you cache this entire response? When to invalidate the cache?
You cannot immediately tell how to do any of these things. Trying to implement them using a non-granular API like this, you’ll and up with numerous parameters that are not really specific to the resource you’re fetching and the user of your API will have to spend a lot of time browsing the documentation just to render an appropriate interface for the end user to interact with an API like this.
A hierarchy of granular, interconnected resources
Now consider the sub-resources as separate, discoverable entities. The data is revealed gradually, as the consumer performs further requests.
GET /users/1
Response:
{
"id": 1,
"name" : "Tom",
"self" : "/users/1",
"links" : [
{ "rel" : "addresses", "url" : "/users/1/addresses" },
{ "rel" : "posts", "url" : "/posts?user=1" },
{ "rel" : "all.users", "url" : "/users" }
]
}
And the separate resources:
All addresses
GET /users/1/addresses
Response:
{
"title" : "Tom's addresses",
"self" : "/users/1/addresses",
"links" : [
{ "rel" : "user", "/users/1" }
{ "rel" : "address", "/users/1/addresses/1"}
]
}
A single address
GET /users/1/addresses/1
Response:
{
"id" : "1",
"email" : "[email protected]",
"street" : "Sesame Street",
"city" : "London",
"country" : "United Kingdom",
"self" : "/users/1/addresses/1"
"links" : {
{ "rel" : "all.addresses", "/users/1/addresses" },
{ "rel" : "update.form", "/users/1/addresses/1/update-form" },
}
}
All posts
GET /posts?user=1
Response:
{
"title" : "Tom's posts",
"self" : "/posts?user=1",
"links" : [
{ "rel" : "user", "/users/1" },
{ "rel" : "post", "url" : "/posts/1" },
{ "rel" : "post", "url" : "/posts/2" },
// many more
]
}
Example single post
GET /posts/1
Response
{
"title" : "HATEOAS Examples",
"content" : "Yada yada yada",
"created" : "2014-01-01",
"self" : "/posts/1",
"links" : [
{ "rel" : "user", "url" : "/users/1" },
{ "rel" : "user.all.posts", "url" : "/posts?user=1" }
{ "rel" : "edit.form", "url" : "posts/1/edit-form" }
]
}
Again, the same questions:
- How do I limit the number of posts I retrieve? – Now you can have an application-wide parameters to handle pagination, say,
limit
andoffset
. You can provide these already embedded into various links. Even if you don’t, such a convention is very easy to adopt by API consumers. - How do I limit the number of addresses bound to my profile that I retrieve? – Same as above, no need for additional parameters to remember.
- How do I delete an address related to my profile? – Obtain the
rel
link to the address and issue aDELETE
request, again, this can be an application-wide convention. You can just follow links and delete whatever you can. Another way would be to provide additional metadata, you could easily provide a link with additional information saying that aDELETE
request is allowed for the resource linked… or maybe make this a property of a resource and not a link itself. Ideally, you could serve such information in response toOPTIONS
requests. Either way, it’s easy to understand by API consumers. - How do I update a post? – You can provide a link to a form, this can be a piece of HTML you prepared or even a JSON/XML response with information on how to render a form like this (this can be used by non-browser clients)
- Can you cache this entire response? When to invalidate the cache? You can cache every single one of these responses. An update of a small, lightweight resource, like changing a post title does not require you to recreate a huge response. You also need to invalidate caches when a resource linked to something is created or deleted (to update the links).
What I like the most about this approach is that it’s easy to understand and using the API feels like browsing a website. This allows you to drive the application state using just the responses. The consumer just needs to follow the links you provide to move around.
It’s much harder for a client of such an API to break when you change something. Even if you decide to move some resources around and change their links, the clients can use their relationships, which are generally much more inert. Particularly if you use well-thought-out relations. I didn’t go this far in the examples above but in a real API, you should ideally take a look around and see what known link relations you could use. IANA keeps a registry of those. Reusing standardized links will make it easier for others to understand your API (especially if they know other APIs using them) and it will save you the work of documenting the relations (you can just point to the right RFC or other applicable document)
Potential Pitfalls
Of course none of this comes for free. Creating an API like this is not easy and it may cost you a lot of effort.
APIs designed this way tend to be more difficult to build and understand. Scalability and flexibility are very important in public APIs, for which many clients can be written by unrelated parties. This is where you can gain the most by properly using hypermedia. However, if your API is only to be consumed within your company or even in a single intranet solution, you might prefer to save yourself the effort and opt for an easy to implement (or even generate out of a service description document) design that relies on human-readable documentation and conventions rather than rich hypermedia. There’s nothing wrong with betraying the principles of REST if your use case really calls for it. Just don’t say your API is RESTful when you do so.
Another downside of this approach is the sheer amount of calls using an API like this requires. The impact of this is reduced by the API’s cache-ability and scalability. If this isn’t enough, you can consider providing the user with a way to reach deeper into the hierarchy (like, provide a parameter instructing the server to embed some of the linked resources into the single response server-side)
Some resources you might find interesting:
- Richardson’s Maturity Model
- The Netflix REST API using rel links in a similar manner
- An article describing the usage of a REST API
- An Apigee article on HATEOAS
- A stackoverflow answer to a similar question about HATEOAS
- Roy Fielding’s very own blog post on the importance of hypertext in REST
I can also wholeheartedly recommend RESTful Web APIs by Amundsen, Richardson and Ruby. Hypermedia and its proper use in RESTful APIs is the key subject of the book.
1