If you’ve ever tried to design an API you probably know it’s not an easy task. First of all you have to choose an architectural style for your web service. Nowadays, the choice is relatively easy, as the most common approach is REST. The core concept behind REST is that everything is a resource. Often RESTful APIs are growing fast, in an unpredictable manner, lacking essential documentation, all of which renders them hard to maintain. It’s difficult to properly model data and usually from a single API call you’ll receive either too much or too little data than you actually need (overfetching and underfetching). Entities are not always consistent between even closely related endpoints, everything starts to get overcomplicated with endpoints that provide similar functionality to one another cropping up in different places across the API.
How does GraphQL solve this? It’s like comparing a loosely-typed language with a strongly-typed one. The advantages of the latter are obvious: errors are more likely to happen at compile time, and type definitions (called a “schema” in GraphQL) help to keep data more consistent. What’s more, a graph-based representation of the schema makes the structure more coherent, especially given that the graph is connected (there are no loose parts). Thanks to that, GraphQL does not deal with dedicated resources like REST does, but it is more data-driven (i.e. describes the data to be matched and the processing required rather than defining a sequence of steps to be taken — wiki).
Here is a quick comparison:
- like REST, GraphQL provides interoperability between computer systems on the Internet (mainly client-server); it can utilize the HTTP protocol as well (in a different manner, as GraphQL is protocol agnostic), and returns JSON, as REST usually does;
- both are stateless;
REST endpoints are tightly connected with a resource/object (multiple HTTP endpoints), whereas GraphQL serves the entire API over a single HTTP endpoint, which is used to fetch as well as modify a subset of the data;
- GraphQL has read/write model separation;
- GraphQL allows for fetching an arbitrary subset of fields of an object out of the box (leading to fewer underlying database queries);
- queries can traverse related objects and their fields, letting clients fetch lots of related data in a single request, instead of making several roundtrips as one would need in a classic REST architecture;
- request and response are directly related: queries and returned data use a similar language for network communication; queries mirror the shape of the response;
- GraphQL has, by design, the ability to query multiple resources in a single HTTP request;
- data has to be more structured and typed and thus creates an arbitrary graph, whereas REST represents data more like a tree structure (resource-based);
GraphQL is not only a buzzword but a widely-adopted way to interact with servers. But what is it exactly? From graphql.org:
GraphQL is a query language for your API, and a server-side runtime for executing queries by using a type system you define for your data. GraphQL isn’t tied to any specific database or storage engine and is instead backed by your existing code and data.
What does it mean?
In a nutshell, GraphQL is a specific type system in which you define your data. Basically it’s all about data. A GraphQL server is based on a so-called “schema” (the shape of your data), which is a graph, as you will see later. The schema is the definition of types and fields on those types. Let’s define a simple one:
This should be more or less self explanatory: there is the definition of two types (User and Query). User in our example has three fields, all of which are scalars, but only id is required (non-null). By default GraphQL has five scalar types:
- Boolean and
but you can easily define more yourself:
The second type, Query, is a special type, called an “operation type” as defined in the GraphQL specification. It’s the root entrypoint for all queries. In the schema above we have the me query, which should return an entity of the User type (or null, since the result type is defined as optional).
GraphQL specification defines two more operation types: Mutation (to create or update an entity) and Subscription (which streams data). Internally, the GraphQL root schema type looks as follows:
Every GraphQL service has a Query type and optionally Mutation / Subscription types. These types are special because they define the entry point of GraphQL query.
Our schema could be represented as a simple graph:
Secondly, GraphQL is a query language for your API. On the client you can query, change or even subscribe to data changes using a specific query format. The data that you will receive will be in the exact same shape as you’ve requested. For example, to get only a user’s email you’d run the following query on the schema from the example above:
which is a short form of:
It could produce a JSON result such as:
The beauty of GraphQL is its clear separation between definition (schema) and implementation (resolvers).
You already know how to create a simple GraphQL schema and to run a query on it. Now I’m going to focus on building the schema itself.
I’ve introduced built-in scalar types before (Int, String, etc), custom scalars (scalar) and the object type (type), but there are more:
- interface — abstract type,
- union — union of one or more objects,
- enum — a set of constants,
- input — defines arguments for query function.
You can also modify types by adding “!” at the end (which makes given field required/non-nullable). It is allowed to define lists as well:
- [String] — a list of nullable Strings or nothing (null)
- [String]! — non-null list of nullable Strings
- [String!]! — non-null list of non-null Strings
What’s more, GraphQL is self-documenting, which means you can (optionally) add comments to any element of the schema (see example below) — GraphQL server generates beautiful and interactive documentation in the easy-to-use web-based playground.
which should generate something like (DateTime is a custom scalar defined outside):
Since the schema already describes all of the fields, arguments, scalars and result types, the only thing left is to write functions that return proper values. We have to add an implementation, separately from the schema. The function which implements a given field and returns a value is called a resolver. Similarly, mutation resolvers modifies data.
Again, from graphql.org:
You can think of each field in a GraphQL query as a function or method of the previous type which returns the next type. In fact, this is exactly how GraphQL works. Each field on each type is backed by a function called the resolver which is provided by the GraphQL server developer. When a field is executed, the corresponding resolver is called to produce the next value.
It is possible to define a resolver function for any single scalar field (like, for example, the number of user comments) or a whole object (i.e. a user entity) as well. Resolvers can be asynchronous. The GraphQL server would execute it if and only if the given field is included in a query. No more ambiguous database queries, as you can better control and limit SQL joins and other queries required to fetch all related data. From the same reason, no more overfetching or underfetching, which should result in less number of HTTP requests.
In contrast to a schema definition, the implementation details depend on the framework or programming language you use, but this is beyond the scope of this article.
GraphQL idea is not a revolution in the API world; it’s more of an evolution. You could easily imagine all these features implemented in a custom-made RESTful server. But even complying with pure REST level 3 HATEOAS constraint, defined in Roy Fielding’s doctoral dissertation about REST, doesn’t make it easier and requires a lot of work on the backend (server) and frontend (client side) as well.
The main advantage of GraphQL is that it’s ready out-of-the-box, it’s really easy to maintain, it has compatible servers and clients (even if written in different languages/frameworks). It’s also pretty hard to break — as schema is a strict definition of your data shape, the server disallows you to return any data which do not fit. And vice versa, you can’t query for anything that isn’t defined in the schema. In general, you would need much more effort to provide the same functionality using REST (unless your API is really simple or runs on very limited hardware). But if you are still not convinced, you could check these solutions, which are in my opinion a bit more complicated than pure REST and still don’t achieve GraphQL’s advantages:
I invite you to read more and discover details on: