GraphQL query language by examples
Django REST APIs developer perspective
GraphQL is the new hot thing right now, bursting into the market as an alternative to REST architecture, solving lots of its problems but also bringing its own. It’s already been adopted by a host of big names, including Facebook, GitHub, PayPal and the New York Times.
At 10Clouds, we decided it’s high time that we tested this new query language, so we set ourselves a simple task: to prepare a proof of concept app aimed at exploring the joys and sorrows of implementing GraphQL API in Django. We wanted to share our findings with you.
This article is for you if:
- You want to find out the basics of GraphQL
- You want to understand its benefits and challenges
- You’re wondering how it compares to REST
Why me?
- I work as a Python developer at 10Clouds, and am working on Django backends for Web Services, mainly with REST APIs using Django Rest Framework
- We always aim to be at the cutting edge of the latest dev developments and to test them on practical examples
- I take part mostly in startup and new projects, with constantly changing and developing APIs, requiring fast and easy to maintain solutions, like GraphQL
Before diving into the details of the aforementioned project and how GraphQL works with Django, I wanted to shed some light on what exactly GraphQL is, to show its differences from REST and present a brief comparison of the two.
GraphQL and REST
GraphQL is a data query language developed by Facebook in 2012 for internal use and open sourced in 2015. It strives to be the new standard in modern development, focusing on productivity and minimizing data transfer by providing queries where the client decides exactly what he gets.
Using simple yet powerful query language, rather than resting (pun intended) on uncountable endpoints, GraphQL allows focusing on domain and providing data as clear and extensible structure without need to optimize each endpoint separately for specific needs, as happens to be the case with REST APIs.
Let’s imagine the classic example of library where we have Books and Authors (defined as OpenAPI 3 specification):
RESTful approach
With REST we would probably like to have endpoints similar to:
api/authors
for list of Authorsapi/authors/{author_id}
for specific Authorapi/authors/{author_id}/books
for a list of Books written by specific Authorapi/books?category=&language=
for a list of all Books, maybe filtered by category or languageapi/books/{book_id}
for specific Book
Now let’s imagine we have one view that uses only titles of Books from specific author and second that needs their details. Should we always use api/authors/{author_id}/books
and in the first case just ignore additional data, or should we introduce a new endpoint? Or maybe include list of Author’s Books in api/authors/{author_id}
and list details in the previous endpoint? But what if details about Author are not required either? I can keep going with such examples, but I guess the point is clear: with REST API you focus on API structure more than data structure.
Tackling it with GraphQL
With GraphQL, at least for me, the most important thing is the domain. That can be a big positive but also a pain in the neck at times. Our aim should be on representing the domain as clearly as possible, remembering about possible growth in the future. With REST, growth would probably mean refactor, breaking API changes and more and more endpoints. But with GraphQL it should be as easy as adding new fields in existing types allowing for more advanced queries. We’ll talk more about queries in a moment. But first, we have to define types as GraphQL is all about types and clear declaration of things. Because of this, it is not tied to any specific database/storage, all that matters is the data you define (hello there again, domain).
Object types, fields and arguments
Without further ado let’s see how our Book and Author look as GraphQL types, written in what’s called GraphQL schema language:
So now we have our types, note the Country type that would allow for later extension (remember the domain and growth?). Each field also has a type, GraphQL has built-in scalar types such as: Int, Float, String, Boolean and ID.
Fields and arguments can be marked as non-nullable (required) by !
(like in name: String!
), arguments without !
are optional. Arguments can take default value if it is optional and no value was passed: to have EN
for language
argument in Author
field books
it should be declared as books(language: BookLanguage = EN)
.
Let’s quickly go through the defined schema:
Country
: a GraphQL Object Type, it represents a type with some fields (name in this case),name
: field on theCountry
type, it is the only field on this type and only it can appear in GraphQL queries on theCountry
type,BookCategory
andBookLanguage
: enumeration types limiting value choices to given set, how they are mapped internally in the implementation language is not a concern, for the client they will always be from those sets,Date
: user defined scalar type to represent date, service has to define how it should be serialized, deserialized and validated (for our example let’s assume it is ISO 8601 formatted string),Author
: Object Type representing ourAuthor
model,id
,name
: fields onAuthor
type, as their types are marked with!
GraphQL service promises to always return a value for those fields,birthday
,country
: optional fields onAuthor
type, that may be null,books
: field onAuthor
type accepting arguments, every GraphQL object type’s field can have 0 or more arguments, all arguments are named, in this case they arecategory
of typeBookCategory
andlanguage
of typeBookLanguage
,[Book]!
: return type ofbooks
field that is an array ofBook
objects ([<type>]!
means that client can always expect an array of 0 or more<type>
values, but not null,[<type>!]
on the other hand would mean that array with at least oneBook
object is returned or null),ID
: special type that represents unique key used to identify object, represented as a String,Book
: Object Type representing ourBook
model,id
,author
,title
,language
: not-null fields onBook
type,pages
,category
: optional fields onBook
type.
The Query type
One more type is required, one that will define the entry points to our API — a root type. It is called Query and could look like the following sequence:
When a field is executed, the corresponding resolver function is called to produce the next value. Developer should provide resolvers for each field on each type, especially ones with arguments as for simple fields — that map to the model — used library would probably ‘resolve’ that for us. Let’s just assume they are done in an intuitive way, where a null argument means no filtering and providing argument filters results to ones with this value. Implementing GraphQL service using library in any language will probably tackle most of them for you, like django-graphene for Django framework in Python.
Writing GraphQL queries
Each GraphQL query consists of multiple nested objects. The root object (external {}
) can be interpreted as the Query
type query. Then, using fields on Query type, we ask for specific data — selecting fields for each object from Query’s fields, then fields of those objects and so on… As long as returned data is an object, fields to return have to be provided explicitly.
Finally, how would we provide functionality reflecting the REST endpoints from above using GraphQL query language? They could look something like (for simplicity the JSON responses are included in the same gist as GraphQL queries):
- for list of Authors where we ask for
authors
field on root object, selectingid
andname
fields from returnedAuthor
objects:
- selecting specific fields of received data is first big advantage of GraphQL over REST, using same
authors
field we could ask for all the details, simply by adding remaining fields to query:
- but wait, there’s more… we have
books
field onAuthor
object type which means we could also get list of author’s books in a single query. To get list of authors with their names and books titles:
- one more thing…
books
takes arguments, so what if we want to display only novels in english, grouped by author? Ask forauthors
and pass arguments tobooks
,NOVEL
andEN
are enum type values, so they are provided like keywords — without quotation marks
- for specific Author, for example to display his profile page, we could ask for details of author with 1 as
ID
:
- but that is not much for a profile, right? why don’t we ask for more details, in single request, maybe a list of books in each category? in GraphQL query language you can request each field multiple times, providing an alias:
- for a list of all Books, maybe filtered by category or language we would use
books
field or the root query type, resolving it almost the same like books field in Author but not limiting response to specific author:
- for specific Book we defined book field on root query type:
With all those tools, problems such as
Now let’s imagine we have one view that uses only titles of Books from specific author and second that needs their details.
seems trivial, and in fact they are, it is only a matter of requesting additional fields for books
field on author
, all using single implementation of author query:
As you can see in the example above, multiple queries can be send in one request as query
is an object type itself and can have it’s fields aliased. So all of the queries presented above could have been single request to the service, returning all required data in one response data object. More complex query, in this case for bookshop localization, could look like:
Returning not only authors, but also all books by category.
What about data writing?
Now we know how to request data from the service, what types are supported out-of-the-box, how to define own GraphQL Object Types and arguments on fields.
But what about creating and updating objects? Here comes Mutation type which is at the same level as Query type in schema, and is a second entry point to the service:
schema {
query: Query
mutation: Mutation
}
More information on mutations can be found in the official specification and guide to GraphQL, which I highly recommend as a source of knowledge.
In summary…
It is important to remember that GraphQL in itself is only a specification of query language — it does not provide or query the data itself. We have to implement GraphQL API providing service in some programming language — e.g. Python, or in my case that means Django framework.
One of the choices for Python library to build GraphQL APIs is Graphene-Python. It supports integration with frameworks like Django (Graphene-Django), SQLAlchemy (Graphene-SQLAlchemy) and Google App Engine (Graphene-GAE).
This is great news for us, as in means that there is a ready solution for bringing GraphQL powers to Django apps. Graphene-Django to the rescue, coming in follow-up article very shortly.
Further reading
Other, but not all, important topics not covered in this article are:
- fragments — making complex queries easy to write,
- interfaces — abstracting set of required fields for multiple implementations,
- union types — abstract class to represent multiple others, without specifying any common fields,
- input types — complex objects as arguments into a field,
- meta fields.
Good summary of benefits and obstacles that GraphQL brings into products can be found in an article from Netflix about their journey of aggregating multiple REST APIs with single GraphQL API.
Looking at creating your own project in GraphQL? Want to find out more about our work on GraphQL or a range of other platforms? Why not drop us an email on hello@10clouds.com or visit our website: www.10clouds.com