Almost 12 years ago, Roy Fielding introduced Representational State Transfer (REST) in his dissertation on Architectural Styles and the Design of Network-based Software Architectures. Since then, APIs adopting the REST architectural style (so-called "RESTful" APIs) have gradually increased in popularity. Nonetheless, a key constraint that Fielding proposed has yet to be adopted as a mainstream feature of RESTful APIs. This feature is known as Hypermedia as the Engine of Application State, or HATEOAS. APIs that enforce this constraint are referred to as HATEOAS-compliant APIs. More generally, APIs that adopt the characteristics of HATEOAS are called "Hypermedia APIs."

In this article we will examine the HATEOAS constraint through an example. We will design a traditional RESTful API, then augment the design so that it's HATEOAS-compliant. In the process, we will explain some of the key benefits of incorporating Hypermedia into the design of your RESTful API, as well as lay the groundwork for investigating Hypermedia API specifications (such as Siren, HAL and Collection+JSON) in later articles.

An Example API

The RESTful API we're designing in this article is for a public bookmarking service. Users can view a list of publicly available bookmarks. They can also filter and order this list based on certain criteria. For example, I can view the most popular bookmarks for a given user, or a list of bookmarks tagged as "video" or "meme." To fetch the actual URL of a bookmark, a user will need to send an additional request specifying the bookmark of interest. This way, requests for specific bookmarks can be tracked as "views"; this metadata can subsequently be used to calculate popularity.

In addition to bookmarks, our API provides basic user management functionality. This allows for bookmarks to be owned by users. A very primitive authentication scheme is used to allow destructive operations (deletes) on both users and bookmarks. Upon creation, a unique authentication token is generated and returned by the API. For bookmark deletion, this token must match the user who created the bookmark; for user deletion, the token must match the user being deleted.

Our API's Endpoints

Traditionally, we think of APIs in terms of endpoints. We can think of endpoints as a way to group the functionality we are providing through our API. In our case, it makes the most sense to expose two endpoints--one for bookmarks, and another for users. To formalize the requirements laid out in the prior section, we describe a series of operations for each endpoint below:

/bookmarks

Name HTTP Method URL Purpose index GET /bookmarks List all bookmarks show GET /bookmarks/:id Show bookmark details (includes URL) create POST /bookmarks Create a new bookmark destroy DELETE /bookmarks/:id Destroy an existing bookmark

/users

Name HTTP Method URL Purpose show GET /users/:id Show user details create POST /users Create a new user destroy DELETE /users/:id Destroy an existing user

API Design Considerations

Now, as diligent API designers, we should think about practical use cases that combine multiple API calls. This helps us confirm that the API is useful to the developer of the client application. Moreover, it gives us an idea of the most requested operations. (This is important to know in case our API gets popular.)

Say a user wants to browse a list of recent bookmarks, then pick one and create his or her own bookmark using the same URL. The client app will first render a list of bookmarks to the user, allowing the user to pick one he or she would like to bookmark. When the user does so, the client application creates the bookmark (using the API) and renders the API response. In Ruby, this could look something like:

# Show list of bookmarks to the user def list_bookmarks bookmarks = http_request('get', '/bookmarks', 'order=recent') render(bookmarks) end # Create a bookmark for a user def create_bookmark(user_input) url = user_input['bookmark_url'] headline = user_input['bookmark_headline'] user = user_input['user_name'] result = http_request('post', '/bookmarks', "url=#{url},headline=#{headline},username=#{user}") render(result) end # render [data] to user on a web (or mobile app) page (AKA "view") def render(data); end # send an http [method] request to [url], passing [parameters] def http_request(method, url, parameters); end

NOTE: For simplicity, the render and http_request methods are left as stubs; the comments explain their assumed functionality.

In this use case, you may have noticed a shortcoming of our implementation. Specifically, we are not authenticating the user. As a result, any user could create bookmarks on behalf of another. Although a token-based authentication scheme was described earlier, let's assume that this was not considered a critical part of our MVP, so we launched the API without the authentication functionality.

Implementing the authentication requirement post-API launch would result in what's known as a "breaking change". As you'd expect, this occurs quite often with APIs, and requires coordination with API consumers (that is, developers who are implementing the API). For example, Facebook adopts a 90-day breaking change policy. On the consumer end, significant rework throughout the application stack is not uncommon. If workflows are affected, a complete rethink of the entire user experience may be necessary.

In the above example, the create_bookmark method would break, a new method for authenticating the user would be required, and an unauthenticated user of the client application will need to be restricted from certain functionality. Granted, the authentication oversight in our case would have been hard to miss. However, real-world APIs often provide richer functionality, so more subtle oversights can and do occur in practice.

The Hypermedia API "Value-Add"

A Hypermedia API is a means to address the breaking change issue. Instead of providing independent operations grouped by endpoint, a Hypermedia API is exposed as a Finite-State Machine (FSM). Each response type can be considered a state, while each operation is a transition.

In this way, the API consumer (and, by extension, the implemented client application) relies on the API to provide the control logic (that is, the workflow). As a result, any API changes that have an effect on the workflow would not break the client application (although the client application's behavior would change). This is arguably one of the key benefits of implementing a HATEOAS-compliant API.

Our API's Finite-State Machine

Here's how the state machine of our HATEOAS-compliant API might look:

To keep things simple and thus easy to follow, we've left out the destroy user / bookmark functionality.

States

Name Description Initial State Initial state of the client application* User Created State reached immediately after the user has been successfully** created Bookmark List Shown A list (index) of bookmarks is being shown Bookmark Shown A single bookmark is shown, with all available details Bookmark Created A user has successfully** created a bookmark

* Technically, this state is not part of our API. We have included it here to complete the FSM diagram.

** For simplicity, we have omitted failure states. However, it's conceivable for these to be defined with their own transitions. For example, a 503 HTTP response type (service unavailable) could provide / allow for a "retry" transition.

Transitions

Name Description user create Create a new user bookmark index View a list of bookmarks bookmark index next Get the next set (or "page") of bookmarks bookmark index previous Get the previous set (or "page") of bookmarks bookmark show Show a bookmark's details bookmark create Create a new bookmark

Key API Design Characteristics

As you can see in the above diagram, a key feature of Hypermedia APIs is to limit the possible transitions for each state. In particular, users must pick a bookmark from a list to view its details (the bookmark show transition is only possible from the Bookmark List Shown state). Similarly, no functionality is available until a user is created. (The User Created state must have been visited prior to any of the other states.)

Thus, our Hypermedia API is the engine of application state; in other words, it is a HATEOAS-compliant API.

Conclusion

We hope that this article has inspired you to further discuss, investigate and experiment with Hypermedia APIs. In future installments we will continue to develop our Public Bookmarks API. In particular, we will investigate how some of the emerging Hypermedia API standard specifications (such as Siren, HAL, and Collection+JSON) can be applied to our API.

By Paul Sobocinski. Paul is a full stack engineer who's been developing in web applications for the past ten years. He can be found on Twitter and Google+.