Bring the hype for hypermedia controls

 • Jonas Svalin

In a previous post I described how REST APIs do not become truly RESTful until they start to leverage hypermedia controls which allow them to be explored and traversed. In the article I used the example of a blog to describe how resources can relate to other resources by using HAL+JSON, a proposed specification for JSON APIs that allows for full hypermedia controls.

The resource in question is a comment for a blog article, which contains the actual comment but more importantly references both the original article and the author of the comment through links. If you want to learn more about why this is so beneficial then I highly suggest reading the full article linked above.

{
  "_links": {
    "self": {
      "href": "https://article-service.io/articles/123/comments/456"
    },
    "article": {
      "href": "https://article-service.io/articles/123"
    },
    "author": {
      "href": "https://user-service.io/users/789"
    }
  },
  "comment": "This was a crazy article"
}

Constructing links

An obvious question you may have when looking at this is "how do you construct the links in a consistent manner?". To answer this question lets have a look at how API routes are constructed in bidi, a popular routing library for Clojure. Bidi supports bi-directional URI routing which is critical for our use case.

If you are serving REST resources, you should be providing links to other resources, and without full support for forming URIs from handlers your code will become coupled with your routing. In short, hard-coded URIs will eventually break.

Here's an example of a set of routes defined according to bidi:

(def routes
  [""
   [["/" :index]
    ["/articles" :articles]
    [["/articles/" :article-id]
     [["" :article]
      [["/comments/" :comment-id] :comment]]]]])

As we can see the routes are defined as a pure data structure, nested vectors consisting of patterns and keywords (which typically correspond to some type of HTTP handler defined in a separate data structure). The benefit of defining our routes like this is that we can easily inspect and perform various other transformations on it. Instead of matching a route to a keyword we can match a keyword to a route, allowing us to construct the links in the comment resource that we looked at above.

Let us look at an example. The self-link of the comment looks like this:

https://article-service.io/articles/123/comments/456

There are four components required to construct this link:

  • the bidi routes;
  • the keyword matching the route;
  • the base URL for the service; and
  • the article and comment IDs.

Assuming that you have an incoming request for this resource, all of these components are available to you. The bidi routes are defined within your service, the keyword maps directly to the resource, the base URL is part of the incoming request and the IDs will depend on the particular request. They may be provided in the incoming request already, or they will be part of the persisted resource which is being loaded.

Hype

Hype is an open-source library we created to abstract away all the details for generating links from these four components.

(require '[hype.core :as hype])
(require '[ring.mock.request :as ring-mock])

(def request (ring-mock/request "GET" "https://localhost:8080/"))

(hype/absolute-url-for request routes :comment
  {:path-params {:article-id 123
                 :comment-id 456}})
; https://article-service.io/articles/123/comments/456

Hype also supports more sophisticated use cases that often come up when designing APIs, such as generating templated links which allow the client to fill in details themselves.

(hype/absolute-url-for request routes :article
  {:path-template-params       {:article-id :articleID}
   :path-template-param-key-fn clojure.core/identity})
; https://article-service.io/articles/{articleID}

(hype/absolute-url-for request routes :articles
  {:query-template-params [:page :per-page]})
; https://article-service.io/articles{?page,perPage}

Conclusion

RESTful APIs are a staple of the internet but unfortunately many of them suffer from poor adherence to the fundamental principle of offering Hypermedia Controls. I believe that a common cause for this is that the barrier of entry is perceived as too high and simple questions such as the one we asked ourselves in the beginning of this article seem too daunting for the expected reward. With tools such as Hype, hopefully we can lower the barrier of entry and make Hypermedia Controls more accessible. If you want to try out Hype yourself I suggest heading over to the official documentation.