GraphQL, developed by Facebook and released as an open standard for all to use, provides a way to create and consume web-based APIs that is designed to be an alternative to REST APIs. With GraphQL, queries and returned data use formal schemas and a type system to guarantee consistency.
In this article we’ll walk through the basics of designing and implementing a GraphQL API, and identify many of the key considerations and choices you’ll make during the process.
Languages and frameworks for GraphQL
If you’re planning to use GraphQL for the API of your web application, there is a very good chance the language and data components you’re already using will support your efforts. GraphQL libraries are available for most every major language in production use. Clients are available for C#/.NET, Go, Java/Android, JavaScript, Swift/Objective-C, and Python, and the server libraries cover even more ground.
If you’re starting entirely from scratch, you’re still best off picking whatever language, runtime, and data layer you’re most familiar with from other projects. Using GraphQL doesn’t impose many restrictions on the server or client, and it is database agnostic. However, you may need to perform more or less manual integration of your data layer depending on what that is. (More on this in the next section.)
For the sake of this article, we’ll use the Python implementation of GraphQL for reference, since the concepts and functionality echo the implementations for other languages.
GraphQL data query schema
GraphQL takes in queries constructed from strongly typed fields in various hierarchical arrangements. The one part of creating a GraphQL API you should be most judicious about is determining the schema to provide for queries.
In many cases, the query fields can be mapped one-to-one to an underlying data source, to expose all the relevant fields in the database (or other data source) for your queries. Because GraphQL queries can be a good deal more open-ended and varied than their REST counterparts, you should plan from the beginning which fields can be queried and how those will map to your database.
You must also ensure that the fields you expose through GraphQL use types that correctly match the underlying data. For instance, GraphQL does not have a native “date” or “datetime” data type, in big part because of the sheer diversity of implementations for such things. If you want to allow searches by date ranges, you will need to enforce the formatting of the dates as taken in through the API, and also ensure that those date requests are translated into their proper counterparts for the back-end database when you query it.
Depending on the framework you’re using, this work might already have been done for you. Graphene, the GraphQL library for Python, provides ISO-8601 formatted date-time values as a type native, so you don’t have to wrangle that yourself.
If your data set has many fields, start by exposing the smallest functional subset of those fields that don’t require complex type enforcements—e.g., simple string or numerical queries. You can then gradually expand the available fields as you figure out how to implement queries for them through the GraphQL connector you’re using.
Storing and retrieving GraphQL data
Storing and retrieving data from your back end is typically accomplished by way of the middleware supported by the GraphQL library for your language. Chances are, if you’re using a common data layer, it will be supported natively.
Python’s Graphene library for GraphQL, for instance, supports the Django web framework, along with Django’s built-in ORM. Graphene also supports the SQLAlchemy ORM, and has work in progress for the Peewee ORM (a small, lightweight ORM that can be dropped into most any kind of project), the Google App Engine’s data connectors, and the Relay JavaScript framework (used by React).
If you’re using a data layer that is not described by any of these components, you can use Graphene’s middleware
and DataLoader
objects to close the gap. These provide you with places to manually plug in the integration you need with your data layer. With DataLoader
, you have a way to coalesce multiple, concurrect requests for related data and thus reduce the number of round-trips to your back end.
None of this, by the way, precludes you from performing your own caching at any layer of the app. For instance, the responses you return could be cached by way of a proxy, while the back-end data could be cached by way of Memcached or Redis. That said, it would be your responsibility to make sure those caches are evicted whenever data changes.
GraphQL queries and mutations
GraphQL uses a specific query format, called a “mutation query,” to create, update, or delete elements from a data set. Give some thought to the way these queries will work—not just which queries you’ll allow and what fields you’ll require for them, but also what data you will return from the query after the mutation.
When you design a mutation query, you can allow the return of any number of output fields. In short, you have a lot of flexibility. That said, it’s probably not a good idea to nest response objects more than one or two layers deep, as that makes the results difficult to parse—both when looking at the query itself and when writing code to handle the results.
Another important caveat is not to let old REST API design habits dictate the way you organize your mutation queries. For instance, rather than create multiple mutation queries to handle different kinds of changes on the same object—a pattern common to REST — you could consolidate them into a single mutation query. One way to do that would be to use distinct, non-optional fields to record each possible operation, as per the “upvote/downvote” in this example. Another would be to use a value field plus an enum type to describe the desired behavior with that value.
GraphQL caching and performance acceleration
Underneath it all, a GraphQL query polls and retrieves data the same as any other query. That means it can be accelerated by many of the same methods used to speed up querying APIs.
- Caching: Any service that has a database as a back end, or returns data from a front end, can benefit from caching on both ends. Keep in mind that the responsibility for expiring those caches falls to you, so you’ll probably have to use the GraphQL framework’s middleware hooks (like the ones described above for Graphene) to trigger such things. It’s recommended that you use unique identifiers whenever possible to support client-side caching.
- Cursors and pagination: A request should have some default upper limit for how many records it returns at once, to keep both the client and the server from being flooded. It also makes sense to allow clients to explicitly describe the maximum number of records to return, and which “page” of records to ask for. The official GraphQL documentation has some useful tips on how to integrate pagination metaphors into the GraphQL request format.
GraphQL tools
In addition to the libraries available for various languages, GraphQL has a slew of native and third-party tools to make it easier to develop clients, servers, schemas, and query processing layers.
-
Apollo GraphQL devotes its resources to creating open source tooling for GraphQL, including GraphQL clients and GraphQL servers. It also maintains GraphQL Tools, a set of utilities for generating and mocking GraphQL schemas and “stitching” multiple APIs into a single API—carrying out GraphQL’s stated mission of consolidating multiple API endpoints and making them more manageable.
-
If you’re looking at porting an existing Swagger-generated API to GraphQL, the Swagger2GraphQL tool was made for the job. It also allows side-by-side maintenance of a legacy Swagger-generated API, so you can use both standards during a transition period.
-
Finally, Facebook’s own GraphQL group has a few tools worth noting. GraphiQL is an in-browser IDE for creating GraphQL queries; it can be used internally or as a public-facing solution. There’s also a JavaScript implementation of GraphQL, a GraphQL middleware server based on Express, and a GraphQL Language Service for IDEs.