---
title: Writing a Hypermedia API client in Ruby
teaser: 'How Ruby and Hypermedia can simplify the way we write API clients.

  '
tags: hypermedia,api,rest,ruby
author: Ismael Celis
published_on: 2017-05-12
---

<figure>
  <img
    src="https://images.thoughtbot.com/blog-vellum-image-uploads/35dv2D31TBegi6xDwwZZ_hypermedia-client-console.gif"
    alt="Ruby Hypermedia Client in terminal session"
  />
  <figcaption>
    A Ruby Hypermedia client interacting with a REST API in a terminal session.
  </figcaption>
</figure>

Much ink and pixels have been spent discussing the virtues and flaws of
[Hypermedia](http://www.steveklabnik.com/hypermedia-presentation/#1) for API
design. Like with
[REST](https://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm), the sheer
amount of theory and jargon around the subject can make it hard to understand
the potential benefits for you as an API developer, the cost of implementation
and the consequences it would have on the way you build, and interact with REST
APIs.

In this article I'll show the outlines of what a Hypermedia API looks like, and
how a generic Ruby client for such APIs can be designed following these
patterns.

Our definition of Hypermedia will be this: Hypermedia for API design means
adding links to your API responses.

### Links

By _link_, I mean a link as you already know it from HTML documents:

```html
<a href="http://server.com/foobar">Foo bar</a>

<link rel="stylesheet" href="/styles.css" />
```

So at the very least, a link is an address or pointer to a separate resource
somewhere in the network.

### Links encode actions

But, in our definition, this will also constitute a link:

```html
<form action="/orders/place" method="PUT">
  <input type="text" name="discount_code" value="" />
  <select name="shipping">
    <option value="1">Free shipping</option>
    <option value="2">Expensive shipping</option>
  </select>
</form>
```

So not _just_ an address, but also the minimum information needed to instruct
_the client_ on how it should interact with the referenced resources. In the
example above, a HTML form tells the client &mdash;the browser&mdash; that it
must send a _PUT_ request, and also defines a schema for the data expected by
the server.

In other words, links or forms encapsulate _actions_ that the client may take
over resources, or ways in which it can change the state of a resource. These,
in a nutshell, are the ideas behind [<acronym title="Hypermedia As The Engine Of
Application State">HATEOAS</acronym>](https://spring.io/understanding/HATEOAS),
and they have been derived from the way the World Wide Web works as we know it
and interact with every day.

### An example

So how does any of this apply to REST APIs?

Let's imagine a simple shopping cart API that shows us information about an
_order_.

`GET /orders/123`

```json
{
  "updated_at": "2017-04-10T18:30Z",
  "id": 123,
  "status": "open",
  "total": 1000
}
```

Now let's say that you can place an open order by issuing a `PUT` request
against a resource, something like

`PUT /orders/123/completion`

The server updates the state of the order and returns a new representation:

```json
{
  "updated_at": "2017-04-10T20:00Z",
  "id": 123,
  "status": "placed",
  "total": 1000
}
```

Nothing new here. This is how most of us build REST/JSON APIs.

How do you know that you can place an open order? You'd check the relevant
documentation, which would include information on the URL to use, the required
request method, available parameters, etc.

If you wanted to use this API in Ruby, you'd grab any number of available HTTP
client libraries and end up with something like this:

```ruby
# get an order
order = Client.get("/orders/123")

# place the order
order = Client.put("/orders/123/completion")
```

After a while, manually concatenating URL paths gets tedious and error-prone, so
as an API client author you'll probably come up with something slightly more
domain-specific:

```ruby
order = Client.get_order(123)

order = Client.place_order(123)
```

This is what many Ruby API client libraries look like.

## APIs and links

Now let's re-implement the same example using a Hypermedia approach. State
transitions &mdash;actions&mdash; can be encoded as links in the API responses
themselves.

`GET /orders/123`

```json
{
  "_links": {
    "place_order": {
      "method": "put",
      "href": "https://api.com/orders/123/completion"
    }
  },
  "updated_at": "2017-04-10T18:30Z",
  "id": 123,
  "status": "open",
  "total": 1000
}
```

I'm using an extension of the [HAL](http://stateless.co/hal_specification.html)
specification to encode a "place_order" link, including its request method and
target URL.

For one, this makes the order resource a little bit more informative to humans:
not only does it tell you about the current state of an order, but _what you can
do with it_.

## The API client

As an API client implementor, you can leverage this simple convention and write
a generic client that learns what actions are available from the API itself.
This is particularly easy to do in Ruby.

Let's start from the top. First thing is to implement a class that wraps any API
resource and exposes its attributes and links. I'm calling it _Entity_.

```ruby
class Entity
  def initialize(data, http_client)
    @data = data
    @links = @data.fetch("_links", {})
    @client = http_client
  end

  # ...
end
```

`data` is the JSON data (a Hash) for the resource itself. `http_client` for now
is anything that supports the basic HTTP operations (`get`, `post`, `put`,
`delete`, etc). It can be an instance of
[Faraday](https://github.com/lostisland/faraday) or anything, really.

We then use the `method_missing` and
[`respond_to_missing?`](https://thoughtbot.com/blog/always-define-respond-to-missing-when-overriding)
combo to delegate data access to any properties available in the resource data.

```ruby
class Entity
  # ...

  def method_missing(method_name, *args, &block)
    if @data.key?(method_name)
      @data[method_name]
    else
      super
    end
  end

  def respond_to_missing?(method_name, include_private = false)
    @data.key?(method_name)
  end
end
```

With this, we can access regular properties.

```ruby
order = Entity.new(json_data, http_client)

order.id # 123
order.status # "open"
```

What about links? We can delegate those to a `Link` class that'll handle making
the relevant HTTP request and returning the response.

```ruby
class Entity
  # ...

  def method_missing(method_name, *args, &block)
    if @data.key?(method_name) # if it's a regular property...
      @data[method_name]
    elsif @links.key?(method_name) # if it's a link...
      Link.new(@links[method_name], @client).run(*args)
    else
      super
    end
  end

  def respond_to_missing?(method_name, include_private = false)
    @data.key?(method_name) || @links.key?(method_name)
  end
end
```

We instantiate a `Link` passing the link data and the HTTP client instance, and
then we run it with any optional arguments.

This allows us to run those links like any regular method call. For example
placing an order:

```ruby
order = Entity.new(json_data, http_client)

# this will issue a PUT request under the hood
placed_order = order.place_order
```

## The Link class

The link wrapper class delegates the actual HTTP request handling to the
underlying HTTP client.

```ruby
link = Link.new({
  "href": "https://api.com/orders/123/completion",
  "method": "put"
}, http_client)
```

We initialize it with the link data and the HTTP client.

```ruby
class Link
  attr_reader :request_method, :href

  def initialize(attrs, http_client)
    @request_method = attrs.fetch("method", :get).to_sym
    @href = attrs.fetch("href")
    @http_client = http_client
  end
  # ...
end
```

The `run` method uses the HTTP client to issue the appropriate HTTP request and
wraps the response in a new `Entity`.

```ruby
class Link
  # ...

  # we'll assume that request bodies need to be JSON-encoded
  def run(payload = {})
    response = case request_method
      when :get
        @http_client.get(href, payload)
      when :put
        @http_client.put(href, JSON.dump(payload))
      when :delete
        # etc
    end

    # error handling on failed responses...

    # wrap response body in new Entity
    Entity.new(response.body, @http_client)
  end
end
```

Because a `Link` sends optional parameters to the relevant endpoint, we can pass
any data expected by the API. For example:

```ruby
placed_order = order.place_order(
  discount_code: "ABC"
)
```

## Links encode capabilities

The presence or absence of links in a resource is meaningful information. For
example, an "open" order may include a `place_order` link, but an order that has
already been placed shouldn't let the client place it again.

We can add some syntax sugar to our `Entity` class to better interrogate
resources for their supported links.

```ruby
class Entity
  # ...
  def can?(link_name)
    @links.key? link_name.to_s
  end
end
```

So client code can decide what to do based not so much on the data in a
resource, but on its _capabilities_.

```ruby
if order.can?(:place_order)
  # PUT https://api.com/orders/123/place
  order = order.place_order
end

if order.can?(:start_order_fulfillment)
  # POST https://api.com/orders/123/fulfillments
  fulfillment = order.start_order_fulfillment(...)
end
```

This pattern has the side-effect of encouraging you to keep the business logic
on the server side. The client app is driven by the API's capabilities,
presented dynamically to the client along with every resource.

For example, instead of hardcoding conditional logic to decide whether of not to
show a button to place an order (based on the current state of the order,
possibly), a client application can blindly render the relevant UI elements if
the expected links are present in the current resource.

```erb
<% if order.can?(:place_order) %>
  <button type="submit">Place this order</button>
<% end %>
```

A client written in this way can be kept pretty generic. New endpoints available
in the API don't need client implementors to release new versions of the client
library. As long as the API presents a new link, the client can _follow_ it. The
release cycles of API and client are thus disentangled.

## The API root

The API root is the entry point into a Hypermedia-enabled API, and the only URL
a client needs to know about. Much as a website's homepage, the root resource
will most likely include a list of links to the main operations supported by
your API.

`GET /`

```json
{
  "_links": {
    "orders": {
      "href": "https://...",
    },
    "create_order": {
      "href": "https://...",
      "method": "post"
    }
  }
}
```

We only need to wrap up our `Entity` and `Link` classes into a client that we
can initialize with a root URL.

```ruby
class ApiClient
  def initialize(root_url, http_client)
    @root_url, @http_client = root_url, http_client
  end

  def root(url = @root_url)
    response = @http_client.get(url)
    Entity.new(response.body, @http_client)
  end
end
```

Once on the root resource, a client can just follow available links _by name_.
The actual URLs they point to may or may not be on the same host. As long as the
responses conform to the same conventions, the client &mdash;and the users of
your SDKs&mdash; don't need to care.

```ruby
api = ApiClient.new("https://api.com", SomeHttpClient)
root = api.root

# create order
order = root.create_order(line_items: [...])

# add items
order.add_line_item(id: "iphone", quantity: 2)

# place it
order = order.place_order
```

## Workflows

As an API designer, I've found that this approach encourages me to not only
think of individual endpoints but of entire _workflows_ through the service. How
do you start and place an order? How do you upload an image? By adding the right
links in the right places I can help make it easier for my customers to
accomplish particular tasks.

These workflows can form the core of the API's documentation, too, by
documenting the sequence of links to be run for each use case instead of just
singular endpoints.

## Pagination

A nice workflow to implement in this way is paginating over resources that
represent lists of things.

`GET /orders`

```javascript
{
  "_links": {
    "next": {
      "href": "https://api.com/orders?page=2"
     }
   },
   "total_items": 323,
   "items": [
      {"id": 123, "total": 100},
      {"id": 234, "total": 50},
      // ... etc
   ]
}
```

In this convention, a list resource will have an `items` array of orders. If
there are more pages available, the resource can include a `next` link.

We can extend `Entity` to implement the `Enumerable` interface for list
resources.

```ruby
module EnumerableEntity
  include Enumerable

  def each(&block)
    self.items.each &block
  end
end

class Entity
  def initialize(data, client)
    # ...
    self.extend EnumerableEntity if data.key?("items")
  end
end
```

The client can now ask the entity whether it can be paginated.

```ruby
page = root.orders

page.each do |order|
  puts order.status
end

page = page.next if page.can?(:next)
```

We can take this one step further and implement an Enumerator that will [consume
the entire data set in a memory-efficient
way](https://thoughtbot.com/blog/modeling-a-paginated-api-as-a-lazy-stream).

```ruby
module EnumerableEntity
  # ...
  def to_enum
    # start with the first page
    page = self

    Enumerator.new do |yielder|
      loop do
        # iterate over items in the first page
        page.each{|item| yielder.yield item }
        # stop iteration if we've reached the last page
        raise StopIteration unless page.can?(:next)
        # navigate to the next page and iterate again
        page = page.next
      end
    end
  end
end
```

We can now treat potentially thousands of orders in a paginated API as a simple
array.

```ruby
all_orders = root.orders(sort: "total.desc").to_enum

all_orders.each{|o| ...}

all_orders.map(&:total)

all_orders.find_all{|o| o.total > 200 }
```

## Non-hypermedia APIs

Even if your API doesn't include links, a generic Ruby client like the one
described here can be used to describe endpoints in your service with little
extra work.

```ruby
class ApiClient
  # ...

  def from_hash(data)
    Entity.new(data, @http_client)
  end
end
```

```ruby
api = ApiClient.new(nil, SomeHttpClient)
orders_service = api.from_hash({
  "_links": {
    "orders": {"href": "...", "method": "get"},
    "create_order": {"href": "...", "method": "post"}
  }
})

order = orders_service.create_order(...)
order = order.place_order
# etc
```

New releases of such a client could consist of just a YAML file with the entire
API definition.

## Reference

* [HAL](http://stateless.co/hal_specification.html) a simple specification for
  Hypermedia API responses.
* [BooticClient](https://github.com/bootic/bootic_client.rb) a Ruby client I
  wrote for a specific Hypermedia API, usable for any API that follows the
  conventions in this post.
* [HyperClient](https://github.com/codegram/hyperclient) another Hypermedia Ruby
  client. I haven't used it much but it looks pretty mature.
* [Presentation at the London Ruby User Group](https://skillsmatter.com/skillscasts/10029-practical-hypermedia-apis-in-ruby)
  (video).
