Exploring REST and GraphQL with Python 3
In the past couple of years, we’ve seen companies begin to embrace GraphQL as an alternative to REST API’s due to their expressive power and correctness guarantees.
Here at Kalo, we’ve been exploring ways of upgrading some of our API’s and found that concrete examples for developers looking to create GraphQL servers Python were lacking, so we decided to build a small project as an experiment to compare the two paradigms and share the knowledge by writing about it.
The repo for the project described in this article can be found here and it can be installed from pypi at this link.
This article can also be viewed as a slideshow.
In the beginning... there was REST.
Really Genesis, shortly thereafter followed by SOAP
Let's say we had a simple data model such as the following:
In a RESTful server, the endpoints used to retrieve that data might look something like this:
What's wrong with this?
Well, let's say on the client-side, we wanted to retrieve a set of authors using a query like
We may get a structure like the following:
However, maybe we're unconcerned with the
books field and all that extra information seems to be slowing down our page load.
To mitigate this problem, maybe we add another filter to the authors endpoint i.e.
Great, so now we get something like the following:
What happens if we type
In general, how do we define and interpret the arguments passed as query params on the server side?
Furthermore, how to we enforce that contract with our clients and give them helpful feedback when mistakes are made?
Also, what happens if we have a similar query to the one above, where we ARE interested in the
book field for a given author, but only a subset of that data, such as
book.title excluding all other data in the
Seems hardly worth creating another filter. We'll probably just suck it up and retrieve all the data in the book field and ignore what isn't needed on the client side.
General considerations with REST
- How do we handle different data types and serialization between the client server for complex data types?
- How do we ensure that our rest api documenation is up-to-date? How do we ensure it's documented at all?
- How do we communicate to the client what data can be retrieved from the server?
Solutions have been proposed
What makes GraphQL different?
But really, GraphQL is first-and-foremost a declarative language specification for client-side data fetching.
And, despite the snark, the buzz is important. GraphQL is created and backed by Facebook, and there is a rapidly growing community and ecosystem of libraries that make GraphQL compelling over other standards like JSON-API, Falcor, or OData.
A GraphQL-compliant server will be able to tell what information can be exchanged with the client and how, in a way that is more expressive and provides more guarantees of correctness than REST.
Still, it's up to the backend engineer to correctly implement the spec, so it's not magic.
On the server-side, having the client describe the specific shape of data it wants can allow the server to make smarter decisions as to how to serve that data i.e. re-shaping SQL expressions or batching database queries.
On the client-side, it's great just to be able to introspect what's available and have a well-defined way of communicating with the server. Since the server will communicate what type of data can be sent/received, the client doesn't need to worry that the api documentation isn't up-to-date or doesn't exist.
We're going to implement a backend server that has a couple RESTful endpoints and one written in GraphQL to demonstrate our earlier points.
The framework we'll use to build our server will be aiohttp, which works on top of asyncio. This will allow us to write our
controllers (depending on how one interprets MVC) using co-routines. This means requests can be processed asynchronously without resorting to multi-tenant architecture or multi-threading. We can write asynchronous code and leverage the full power of asyncio's event loop. Cool.
These def the logic for the restful routes we'll create on our application i.e.
For a single resource:
Or based on url query parameters:
Let’s see how we might go about writing a handler function for those endpoints…
So before, we saw an example of how you might implement a REST api where each resource i.e.
GraphQL is conceptually very different. The idea is that instead mapping your URL's to your data and implenting queries and mutations through a combination of URL query params and HTTP verbs, your entire API is exposed via a single URL.
Requests to this url will have a GraphQL query either in the body of the HTTP request, or in the
query url parameter. This GraphQL query will tell the server what data to fetch or change.
The GraphQL server, in turn, exposes the data as a vertex-edge or node-link graph where the root node has a special name,
query which is left out of graphql queries as its' implicitly there, in the same way
https://google.com implicitly has the root DNS node contained
Each resource we create, such as an
author (objects in GraphQL parlance) will have an associated resolver function that exposes that object on the graph.
So concretely, what does this look like in-practice?
Well, on the client side hitting
http://localhost:8080/rest/authors?limit=3&no_books=true may return something like
If we wanted to get the same information using graphql, we'd start by writing our query.
In a scenario where we're using the excellent httpie client and we have the above query bound to the
query variable, we'd retreive the information as such:
http :8080/graphql "query=$query"
Writing GraphQL server code
Since we're using graphene, we'll first need to use the library to create a schema describing our data.
This schema will then tell the library how to implement the GraphQL-compliant endpoint view.
Describing the Graph
Once we've defined our schema, we need to expose that information by adding our objects as fields on the graph and writing resolver functions for those fields that describe how to fetch the actual data we want.
Remember that the root node of our graph is an object with the name
Please feel free to play around with the actual server we created to demonstrate what’s written.
pip3 install graphql-example