Introduction
When browsing the web we typically navigate from one site to another by following Hyperlinks. Those links make the web for humans discoverable.
Hypermedia APIs provide the same discoverability for services. According to Roy Fielding Hypermedia is an essential part of a REST API and the Richardson REST Maturity Model describes Hypermedia as the final step to the glory of REST. So, Hypermedia seems to be quite an important thing for REST. However, in my experience, Hypermedia is used very rarely. This is a bit sad because Hypermedia provides some nice features to APIs.
Resource representations with links
Let's start with a simple example without Hypermedia. Assume we want to create a resource that provides information about an order. It might look like this:
GET /orders/123
{ "buyer_id": 456, "order_date": "2020-15-08T09:30:00", "total_price": 4.99, "payment_date": null, "status": "open", "items": [ { "product_id" : 789, "quantity": 1, "price": 4.99 } ] }
Note that the fields buyer_id and product_id are references to other resources. If the client wants to get more information about the buyer, it has to construct a new request URI like this:
String buyerUrl = "/customer/" + order.getBuyerId();
Here the client has to know the exact URI format of related resources. This is similar to surfing the web without using hyperlinks. Instead of clicking on links we have to manually update the browser request line for every sub page we want to visit.
To add Hypermedia support to our order representation, we have to replace IDs with links to related resources.
For example:
{ "buyer_url": "/customers/456", "order_date": "2020-15-08T09:30:00", "total_price": 4.99, "payment_date": null, "status": "open", "items": [ { "product_url" : "/products/789", "quantity": 5, "price": 4.99 } ] }
We now created links between related resources. A client does no longer have to care about IDs and URI construction. To get buyer information the client just has to send a GET request to the value of buyer_url.
Hypermedia response formats typically group links together in a separate JSON object. It is also a good idea to use a JSON object to represent a link. This gives us the option to add more information to links later.
If we apply this to our order representation it might look like this:
{ "order_date": "2020-15-08T09:30:00", "total_price": 4.99, "payment_date": null, "status": "open", "items": [ { "quantity": 5, "price": 4.99, "links" : [ { "rel": "product", "href": "/products/789" } ] } ], "links" : [ { "rel": "buyer", "href": "/customers/456" } ] }
With the rel field we describe the type of the resource relation while href contains the actual link (more on this later).
State Transitions (HATEOAS)
So far we only used links to indicate relations to other resources. Links can also be used to indicate possible actions on a resource. For example, orders can be paid and cancelled. We can use links to point to these operations:
{ "order_date": "2020-15-08T09:30:00", "total_price": 4.99, "status": "open", "payment_date": null, "items": [ ... ], "links" : [ { "rel": "buyer", "href": "/customers/456" }, { "rel": "payment", "href": "/orders/123/payment" }, { "rel": "cancellation", "href": "/orders/123/cancellation" } ] }
In order to cancel an order we can now simply send a PUT request to the cancellation link. After cancelling the order, the resource representation might look like this:
{ "order_date": "2020-15-08T09:30:00", "total_price": 4.99, "status": "cancelled", "payment_date": null, "items": [ ... ], "links" : [ { "rel": "buyer", "href": "/customers/456" }, ] }
Note that the order status has changed and the links for cancellation and payment are gone. Of course a cancelled order cannot be cancelled again and paying for a cancelled order makes no sense. So links do not just point to actions, they also tell us which actions are possible in the current resource status.
This is called Hypermedia as the Engine of Application State (HATEOAS). HATEOAS can transform a REST API into a state machine over HTTP.
More on Links
We used quite a few links so far. So, it's a good point to look into a few details.
The link attributes rel and href come from the attributes of the <a> tag that is used in HTML to represent links. A common set of link relations (like first, next, previous, etc.) has been standardized by IANA. You can find those relations on the IANA website. It is a good idea to take a look at this list before you come up with your own new rel type.
It is also a good practice to include a link to the current resource, named self. For example:
GET /orders/123
{ ... "links" : [ { "rel": "self", "href": "/orders/123" }, ... ] }
Links might not always point to exact resources. It is also possible to create links that contain placeholders or optional parameters. For example, the order list might contain a search-by-status link that contains a status request parameter:
GET /orders
{ ... "links" : [ { "rel": "self", "href": "/orders" }, { "rel": "search-by-status", "href": "/orders{?status}" }, ... ] }
Clients can use that link to filter the order list by a specific order status. For example, this might be a valid request:
GET /orders?status=open
These templates are called URI Templates (defined in RFC 6570). The RFC is a good source for more information.
Links are also an important part of your API documentation. Instead of documenting exact resource URIs you should document possible link relations for your resources. The client needs to know what a specific links does and how it should be used (HTTP method, request body if required, etc.)
The API entry point
If clients do not know any resource URIs they need some entry point for an initial request. This initial entry point then provides links to accessible resources. An API entry point for our example API might look like this:
GET /
{ "version": "1.2.3", "description": "Example API to manage orders", "links": [ { "rel": "orders", "href": "/orders" }, { "rel": "customers", "href": "/customers"}, { "rel": "customer-by-id", "href": "/customer/{id}"}, { "rel": "customer-by-email", "href": "/customer{?email}"}, ... ] }
With URI templates we can make sure clients do not need to browse through large collections in order to find a needed resource.
Hypermedia response formats
So far we just added links elements to our JSON representation. However, it can be a good idea to look at some common Hypermedia response formats before building a Hypermedia REST API. Unfortunately there is no single standard format. Instead, we can choose from a lot of different formats.
Here are some examples:
- HAL (Hypertext Application Language)
- JSON LD (JSON for Linking Data)
- Collection+JSON
- Siren
- JSON Hyper Schema
I would recommend looking at HAL first. HAL is quite simple and one of the formats that is widely supported by libraries. Besides standard REST clients you can use tools like HAL explorer to interact with APIs that use HAL.
Why is this useful and what are the downsides?
Introducing Hypermedia to REST APIs comes with a lot of benefits. It reduces coupling between the server and clients. Servers are able to refactor and evolve their URI structure without breaking clients. Clients no longer need to construct request URIs.
It also reduces the logic required on the client. Let's recap the previous example with the order that can be cancelled or paid. However, this time without links:
{ "order_date": "2020-15-08T09:30:00", "total_price": 4.99, "status": "open", "payment_date": null, "items": [ ... ], }
How does the client decide if it is possible to cancel or pay this order? Maybe an order can be cancelled as long as it is in open state? And it is possible to pay an order as long as it is in open state and payment_date is null?
This logic is already present on the server and can be communicated with HATEOAS. So instead of duplicating logic the client has just to check if a specific link is present. For example: If the cancellation link is present, it is possible to cancel the order and therefore the Cancel order button should be shown in the user interface.
The same approach works great for communicating allowed operations. The server already contains the logic to decide what a user is allowed to do based on his permissions/roles. So, if a user has no permission to cancel an order, don't add a cancellation link.
Those points are all great, but what are the downsides?
Adding links for resource relations and state transitions can be a significant effort on the server side. You have to construct links, list possible state transitions and check if the client has the permissions use them. This effort is only useful if clients actually make use of the Hypermedia elements provided by the API and do not use hardcoded URIs.
Using Hypermedia can also significantly increase the response size.
Summary
Hypermedia REST APIs use links to point to related resources and to possible resource state transitions. This makes REST APIs discoverable and reduces coupling between clients and the server. Clients can interact with links provided by the server instead of constructing URIs on their own. It also reduces logic duplication on client side.
However, implementing Hypermedia can be a significant effort on the server side.
Many different Hypermedia response formats are available, a simple and popular one is HAL.
Leave a reply