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.