GraphQL and REST

Introduction

There is a new trend in open source that I'm not sure I like very much: big companies announce that they are going to open source something, but the release is nowhere in sight yet. Announcing something invites feedback, especially if it's announced as open source. When the software in question is available already as closed source for people to play with I don't really mind as feedback is possible, though it makes me wonder what the point is of holding back on a release.

It's a bit more difficult to give feedback on something when the thing announced is still heavily in development with no release in sight. How can one give feedback? But since you announced you'd open source it, I guess we should not be shy and give you feedback anyway.

Facebook has been doing this kind of announcement a lot recently: they announced React Native before they released it. They also announced Relay and GraphQL, but both have not yet been released. They've given us some information in a few talks and blog posts, and the rest is speculation. If you want to learn more about Relay and GraphQL I highly recommend you read these slides by Laney Kuenzel.

From the information we do have, Relay looks very interesting indeed. I think I'd like to have the option use Relay in the future. That means I need to implement GraphQL somehow, or at least something close enough to it, as it's the infrastructure that makes Relay possible.

React is a rethink of how to construct a client-side web application UI. React challenges MVC and bi-directional data binding. GraphQL is a rethink of the way client-side web applications talk to their backend. GraphQL challenges REST.

REST

So what was REST again? Rich frontend applications these days typically talk to a HTTP backend. These backends follow some basic REST patterns: resources are on URLs and you can interact with them using HTTP verbs. Some resources represent single items:

/users/faassen

If you issue a GET request to it you get its representation, typically in JSON. You can also issue a PUT request to it to overwrite its representation, and DELETE to delete it.

In a HTTP API we typically also have a way to access collections:

/users

You can issue GET to this too, possibly with some HTTP query parameters to do a filter, to get a representation of the users known to the application. You can also issue POST to this to add a new user.

Real proper REST APIs, also known as Hypermedia APIs, go beyond this: they have hyperlinks between resources. I wrote a web framework named Morepath which aims to make it easier to create complex hypermedia APIs, so you can say I'm pretty heavily invested in REST.

Challenging REST

GraphQL challenges REST. The core idea is that the code that best knows what data is needed for a UI is not on the server but on the client. The UI component knows best what data it wants.

REST delivers all the data a UI might need about a resource and it's up to the client to go look for the bits it actually wants to show. If that data is not in a resource it already has, the client needs to go off to the server and request some more data from another URL. With GraphQL the UI gets exactly the data it needs instead, in a shape handy for the UI.

As we can see here, a GraphQL query looks like this:

 {
   user(id: 400) {
     id,
     name,
     isViewerFriend,
     profilePicture(size: 50)  {
       uri,
       width,
       height
     }
   }
}

You get back this:

{
  "user" : {
    "id": 4000,
    "name": "Some name",
    "isViewerFriend": true,
    "profilePicture": {
      "uri": "http://example.com/pic.jpg",
      "width": 50,
      "height": 50
    }
  }
}

This says you want an object of type user with id 4000. You are interested in its id, name and isViewerFriend fields.

You also want another object it is connected to: the profilePicture. You want the uri, width and height fields of this. While there is no public GraphQL specification out there yet, I think that size: 50 means to restrict the subquery for a profile picture to only those of size 50. I'm not sure what happens if no profilePicture of this size is available, though.

To talk to the backend, there is only a single HTTP end-point that receives all these queries, or alternatively you use a non-HTTP mechanism like web sockets. Very unRESTful indeed!

REST and shaping data

Since I'm invested in REST, I've been wondering about whether we can bring some of these ideas to REST APIs. Perhaps we can even map GraphQL to a REST API in a reasonably efficient way. But even if we don't implement all of GraphQL, we might gain enough to make our REST APIs more useful to front-end developers.

As an exercise, let's try to express the query above as a REST query for a hypothetical REST API. First we take this bit:

user(id: 4000) {

We can express this using a path:

/users/4000

The client could construct this path by using a URI template (/users/{id}) provided by the server, or by following a link provided by the server, or by doing the least RESTful thing of them all: hardcode the URL construction in client code.

How do we express with HTTP what fields a user wants? REST of course does have a mechanism that can be used to shape data: HTTP query parameters. So this bit:

id,
name,
isViewerFriend,

could become these query parameters:

?field=id&field=name&field=isViewerFriend

And the query would then look like this:

/users/4000?field=id&field=name&field=isViewerFriend

That is pretty straightforward. It needs server buy-in, but it wouldn't be very difficult to implement in the basic case. The sub-query is more tricky. We need to think of some way to represent it in query parameters. We could do something like this (multi-line for clarity):

?field=profilePicture&
  filter:profilePicture.size=50&
  field=profilePicture.uri&
  field=profilePicture.width&
  field=profilePicture.height

The whole query now looks like this:

/users/4000?
  field=id&
  field=name&
  field=isViewerFriend&
  field=profilePicture&
  filter:profilePicture.size=50&
  field=profilePicture.uri&
  field=profilePicture.width&
  field=profilePicture.height

The result of this query would look the same as in the GraphQL example. It's important to notice that this REST API is not fully normalized -- the profilePicure data is not behind a separate URL that the client then needs to go to. Instead, the object is embedded in the result for the sake of convenience and performance.

I'd be tempted to make the server send back some JSON-LD to help here: each object (the user object and the profileData subobject) can have an @id for its canonical URL and a @type as a type designator. A client-side cache could exploit this @id information to store information about the objects it already knows about. Client-side code could also use this information to deal with normalized APIs transparently: it can automatically fetch the sub-object for you if it's not embedded, at the cost of performance.

Does this REST API have the same features as the GraphQL example? I have no idea. It probably depends especially on how queries of related objects work. GraphQL does supply caching benefits, which you wouldn't have without more work. On the other hand you might be able to exploit HTTP-level caching mechanisms with this REST-based approach. Then again, this has more HTTP overhead, which GraphQL can avoid.

Let's briefly get back to the idea to automatically map GraphQL to a REST API. What is needed is a way to look up a URI template for a GraphQL type. So, for the user type we could connect it to a URI template /users/{id}. The server could supply this map of GraphQL type to URI template to the server, so the server can make the translation of the GraphQL to the REST API.

Further speculation

What about queries for multiple objects? We could use some kind of collection URL with a filter:

/user?filter:startsWith=a

It is normal in REST to shape collection data this way already, after all. Unfortunately I have no clear idea what a query for a collection of objects looks like in GraphQL.

I've only vaguely started thinking about mutations. If you can access the objects's URL in a standard way such as with an @id field, you can then get a handle on the object and send it POST, PUT and DELETE requests.

Conclusion

All this is wild speculation, as we don't really know enough about GraphQL yet to fully understand its capabilities. It's quite possible that I'm throwing away some important property of GraphQL away by mapping it to a REST API. Scalability, for instance. Then again, usually my scalability use cases aren't the same as Facebook's, so I might not care as long as I get Relay's benefits to client-side code development.

It's also possible that it's actually easier to implement a single GraphQL-based endpoint than to write or adapt a REST API to support these patterns. Who knows.

Another question I don't have the answer to is what properties a system should have to make Relay work at all. Does it need to be exactly GraphQL, or would a subset of its features work? Which properties of GraphQL are essential?

Thank you, GraphQL and Relay people, for challenging the status quo! Though it makes me slightly uncomfortable, I greatly appreciate it. Hopefully my feedback wasn't too dumb; luckily you can't blame me too much for that as I can legitimately claim ignorance! I'm looking forward to learning more.

Comments

Comments powered by Disqus