A (very) brief introduction to GraphQL
GraphQL describes itself as a "query language for your API". We live in a web-driven world where the term API often translates to Web-API. But despite that, it is worth noting, that GraphQL is not only capable of powering APIs through HTTP, but it is a query language comparable to SQL, that can power any kind of Application Interface. The general idea behind GraphQL is, that a client defines which data to receive from the API by passing a GraphQL document to it. I won't get into the details of GraphQL here. If you want more information on this you can find an introduction on our blog: An Introduction to GraphQL or have a look at the official tutorials and documentations.
Implementing a simple RESTful API for a blog
To implement our REST API we'll be using Elixir and Phoenix. I got to know GraphQL through Absinthe, the GraphQL Toolkit for Elixir, and I just love it. But there are of course implementations for many different programming languages including Ruby and PHP.
So following this little experiment shouldn't be a big problem if you know some web framework like Phoenix, Rails, Laravel or similar.
The general idea: GET /posts
A typical implementation of the index function for a REST API would look something like this:
Now we change this implementation, so that it sends a GraphQL document to our internal API implementation.
For that we define a private function graphql that takes a connection and a GraphQL document, which is nothing more than a string, runs it against a GraphQL schema via Absinthe and sends the result as a JSON response.
Now, for our GraphQL API to work, we need three things:
- A type for every object we want to be retrievable.
- A schema that defines the queries and mutations our API can resolve.
- A resolver for every query and mutation, that does the actual job.
So our RestQL implementation will have the following structure.
At first we need types for the posts and users (author) we want to receive.
Next we define the query posts in our schema.
The schema imports the types we implemented earlier and defines the query posts. This query will be resolved by the function PostResolve.all/2, which does the actual work of retrieving the posts and the corresponding author.
Now we can run our server and will be able to retrieve all posts by visiting GET /posts. The API will provide the title, the body and the author name forall posts. If we want it to provide the post's id too, we just need to add it to our GraphQL query document in our PostController, since we already defined it as a field on the post object for GraphQL.
Using manipulators - POST /posts
In the same way we can now define all other REST actions using GraphQL documents. I'll show this for the creation of new blog posts. First we need to define a create function on our PostController as we would do in a typical REST implementation.
As you can see we now use a mutation to create a new post. For that we put the post parameters, passed to the controller, into the document via GraphQL variables. This way the parameters get sanitized and we can even check for their type and presence with String!. Note that GraphQL provides a very extensive type system, that we could use here, if we wanted.
So all we need to do now, is to define this manipulation in our schema and implement the resolver:
We defined title and body to be non-null strings. By that we could have avoided checking for presence and type via document variable types. But keep in mind, that the errors, the client will receive, won't really make sense to him, since it will reference the GraphQL document, he doesn't know about. It would be easy to handle this error in our controller and provide a useful error message, though.
So know our RestQL API is ready to ship. But what are the take-aways from this experiment?
Why all the fuss?
Couldn't we just have implemented the same thing without all this fancy GraphQL knick-knack? Well, of course we could have and I'm aware, that using GraphQL to power a small RESTful API like this, may not make sense at all.
But although this was more of an experiment, I can think of some reasons to use this pattern in production.
1. Useful patterns
Using Absinthe to implement a GraphQL API is just smooth. I like seperating my API implementation into a schema, types and resolvers. Of course this isn't that different to a well-made API implementation using short controller functions, separated contexts and models, but with Absinthe you are forced to do so and may end up with more readable code.
2. Easy to change
Your client wants to have this or that information hidden, shown, transformed? Most changes to the API won't require much, you'll never have to remove fields from your GraphQL types once you put them there. Sometimes the change will be as small as changing the GraphQL document in the controllers.
3. Easy to create different APIs for different clients
You want to provide slightly different APIs for different clients i.e. send less data to mobile devices? With a GraphQL backend you can do that with only one API implementation while changing the controller facade that lays on top of that.
4. Use caching, set up RESTful API and GraphQL on different servers, get fancy
We created the GraphQL documents just by writing some Elixir strings. When building bigger APIs one would probably want to use a GraphQL client library for that (for Elixir there is Neuron). When those libraries mature, you might get caching strategies, to reduce the hits on your database. You could even think about splitting the GraphQL backend and the RESTful API to different apps and get fancy with distributed setups and what not.
Conclusion: When to use and when most definitely not to
Nice, now I get to refactor all my APIs!
Well, you don't. I listed some reasons, this pattern may make sense for some cases, but it won't make sense for most, where proven RESTful patterns just work.
On the other hand, if you are interested in GraphQL or even think about providing a GraphQL API over HTTP besides a RESTful API, this pattern will probably make sense. And with GraphQL client libraries becoming more mature, providing RESTful APIs with a GraphQL backend may become extremely useful.