Stephen Mizell

Using GraphQL With REST APIs

March 20, 2019

People choose GraphQL because of all the details it lets them forget about. It’s tough to go back to modern REST approaches after working with the powerful patterns and well-built tools of the GraphQL ecosystem. But maybe we don’t have to choose between GraphQL and REST. Maybe we can get the benefits of both approaches.

To explore this, I’ve worked on a proof of concept. I called it Graphable JSON. It’s a JavaScript library that takes a GraphQL query and lazily fetches all the requested data from a REST API by following links. It allows API consumers to move beyond resources, URLs, HTTP methods, and status codes by specifying their required data in GraphQL’s query syntax. All of this happens in the client rather than sending the query to an API somewhere. This is an important distinction to remember.

An example of how it works

I feel it’s easiest to explain this approach by way of examples. We’ll use an order API for these examples, and we’ll write a GraphQL query to fetch data from it. The API is a REST API with no GraphQL endpoint, so the Graphable JSON library will have to figure out how to get the data on its own.

The API is made up of several different examples for representing order data. For each example, our GraphQL query will be the same. This will show that we can have different kinds of responses and representations of data and evolve the API without breaking the client. We’ll also see how client developers can specify the data they want without worrying about the details of the REST API.

Here’s our GraphQL query to fetch order data.

{
  order {
    order_number
    total
  }
}

No orders in the response

We’ll start with the first example which is an empty object with no order data, {}.

Since there are no orders, the result for the query will be:

{ "order": [] }

Graphable JSON will handle null the same way, meaning we’ll never have to worry about null and undefined values. The shape of the data will always be the same.

A single order in the response

The second example has a single order. The library will first look for a single value and yield that value if it exists.

{
  "comment": "A single embedded order object",
  "url": "https://graphablejsonapi.glitch.me/examples/example1",
  "order": {
    "url": "https://graphablejsonapi.glitch.me/orders/1000",
    "order_number": "1000",
    "total": 150,
    "unit": "USD"
  }
}

When we run our query, we get just the data we requested.

{
  "order": [
    {
      "order_number": ["1000"],
      "total": [150],
    }
  ]
}

Even though there was a single order, we see an array of order items in the result. There are a couple of things to mention about this before moving on.

First, the Graphable JSON library makes use of asynchronous generators. Without getting too far into those details, it means that it will return a function that can yield many values rather than returning a single value. This little piece of magic lets us treat zero values, single values, and—as we’ll see in the next sections—multiple values the same way.

Second, it may feel odd to have the singular word order referring to an array. It’s best to think of each property as describing a relationship between the given object and the value or values of the object. The object returned can have zero or many order items, though there is only one concept of an order relationship in this context. Treating relationships this way let’s us evolve the API and not break clients when we need to expand the number of related items.

The goal is to return the same shape of the data in the client even though the API may change how that data is represented. Separating Graphable JSON’s response from the representation of the API’s data is the key to making this idea work.

Multiple orders in the response

I’ve shortened the third example for the case of brevity. It shows multiple orders in a single response.

{
  "comment": "All order objects embedded",
  "url": "https://graphablejsonapi.glitch.me/examples/example2",
  "order": [
    {
      "url": "https://graphablejsonapi.glitch.me/orders/1000",
      "order_number": "1000",
      "total": 150,
      "unit": "USD"
    },
    {
      "url": "https://graphablejsonapi.glitch.me/orders/1001",
      "order_number": "1001",
      "total": 25,
      "unit": "USD"
    },
  ]
}

As we would expect, we get the same shape as above, only with more orders.

{
  "order": [
    {
      "order_number": ["1000"],
      "total": [150],
    },
    {
      "order_number": ["1001"],
      "total": [25],
    }
  ]
}

Linked orders

What happens when we want to give each order its own URL? How can we provide ways to cache all or parts of the data from the API? The way we do that is by adding links in the responses and letting the Graphable JSON client follow those links. Consider our “single order” example above. Instead of embedding the order, we can link it with an order_url property.

{
  "comment": "Links to each order rather than embedding",
  "url": "https://graphablejsonapi.glitch.me/examples/example3",
  "order_url": "https://graphablejsonapi.glitch.me/orders/1000"
}

Graphable JSON would return the same response as the “single order” example. Here’s how it works. The library first looks for order. If it’s not found, it looks for either order_url or orderUrl. If found, it requests the link. In both cases, the result of the query is the same. This works for multiple links as well.

{
  "comment": "Links to each order rather than embedding",
  "url": "https://graphablejsonapi.glitch.me/examples/example3",
  "order_url": [
    "https://graphablejsonapi.glitch.me/orders/1000",
    "https://graphablejsonapi.glitch.me/orders/1001",
    "https://graphablejsonapi.glitch.me/orders/1002",
    "https://graphablejsonapi.glitch.me/orders/1003"
  ]
}

The library will request each order link and provide the response as if it were included.

Collections of orders

Lastly, there are times where we’ll want to paginate data in the API. Most GraphQL approaches require the consumer to deal with pagination in the query. Graphable JSON does pagination by looking for collections of data and following links if they exist—it happens automatically.

In our example, if the order value contains an $item property, it will respond with each item and follow a next link if it exists. Here is a response with a link to an order, but this time it’s to an order collection.

{
  "comment": "Links to a paginated order collection",
  "url": "https://graphablejsonapi.glitch.me/examples/example4",
  "order_url": "https://graphablejsonapi.glitch.me/ordersCollection"
}

The linked order collection looks something liket his below.

{
  "$item": [
    {
      "url": "https://graphablejsonapi.glitch.me/orders/1000",
      "order_number": "1000",
      "total": 150,
      "unit": "USD"
    }
  ],
  "next_url": "https://graphablejsonapi.glitch.me/ordersCollection?page=2"
}

The library will return all of the items then move on to the next page until there were no longer any next links. Like all the examples above, this results in the same shape of the data, all using the same query. All of this is hidden from the client developers.

The benefits of this approach

Though Graphable JSON doesn’t solve the problem of overfetching and underfetching, it does provide other benefits that may outweigh that issue.

  1. API designers can think about relationships of data rather than strict structures of data.
  2. API developers can evolve the API based on technical needs without breaking client implementations.
  3. API developers can optimize responses based on client needs.
  4. API consumers don’t have to piece together resources to understand the interconnectedness of the API—the links show the way.
  5. Any data in the API can be given a URL, making it possible to refresh smaller batches of data later without requesting larger queries.
  6. Clients are able to cache data based on cache settings in the response. For the browser, this implementation comes for free.

Graphable JSON is just a proof of concept. But maybe it will spark ideas for others. There are many benefits to REST, and we lose some of these benefits as we move to things like GraphQL. Maybe there are ways to get the best of both worlds.

Let’s discuss

If this idea is interesting to you and you’d like to give some thoughts, you can find me on Twitter. I have also cross-posted this article to dev.to if you liked to write longer responses.