The client gets all it needs and nothing more.
Progressive Web Apps will replace web pages and native apps in the future and you should be prepared for it. But we wanted to push the prototype further and challenge the way we fetch and serve data. This was the perfect occasion to try out a promising technology: GraphQL.
A data query language
GraphQL is defined as "a query language for your API, and a server-side runtime for executing [these] queries by using a type system you define for your data". It was developed internally by Facebook in 2012 and open-sourced in 2015.
The main idea is the following: When retrieving data from a typical web service, you will send your requests with parameters and receive the data from the server. The structure of this data is defined by the server when its service was implemented. With GraphQL the expected structure of the data is passed as a query inside the request and the result is returned following this structure.
As an example, the query and result for a request "Get the name of the two first players", could be:
The client gets all it needs and nothing more
This idea is simple but solves so many problems. I have seen many client-server implementations and, when they reach a certain size, they are confronted with a similar problem.
First of all, you are transporting data over a network and you would like to avoid sending too many requests. Generally your data is organized by entities and relations between them, so you decide to group and return some of them together. But soon you have different calls that ask for the same kind of data but not exactly with the same related entities. Soon you have multiple clients that have even more different needs. Finally there may be clients using your API, which you don't have control over.
So what is the solution? Most of the time I see bloated results containing unnecessary data and/or duplication of services, differentiated by their parameters and the data structure they return. There we can see the origin of the problem: Trying to define on the server what the clients need.
With GraphQL, if I suddenly need to retrieve also the ID of the players and the name of their second team, I can simply adapt the query as below.
The benefits are numerous:
- The server does not need to be updated every time the client needs change. So there is no more time lost having to communicate between front-end and back-end. It is instantaneous.
- The client defines the related entities it needs. No more oversized results.
- The needs may be defined down to the field level, for even smaller results.
- By specifying the related nodes, it can retrieve all the data needed for a page/window in a single query.
Client usage and server-side implementation
From the point of view of the client, all the requests are sent to a single URL/endpoint.
On the server, implementing a GraphQL service is defining a schema. This schema is composed of object types, their fields, and the way to resolve these fields. Some implementations keep separated the schema definition and the resolution. Others do not.
Step1: Data model
Before we implement the service, we had to define our data model and have dummy data. Luckily, our prototype is based on an existing spare-time project. We agreed on the model and migrated the data from our previous server.
Step 2: Server library
Now that we had data and the model, we had to select the server library to implement our GraphQL service. There are libraries for all the popular languages. You should find a solution in the following list: GraphQL server libraries
Without planning to, we ended up having an interface similar to the one found subsequently in Facebook examples. While GraphQL specs are abstract and give liberty to implementations, you will not be lost when switching from one library to the other.
Step 3: Object types and fields
Everything is types and fields. At the root of your schema, you have an object of type "Query". The requests that you would usually have ("getPlayers" for examples), are contained as fields on this root "Query". Like other languages, at the leaves of these types you find scalar types. The default scalar types defined by GraphQL are the following: Int, Float, String, Boolean and ID. Below are the simplified versions of a classic domain object type 'Player' and the root object type 'Query'
Here the player has 3 fields: a non-null ID, "id", a non-null String, "name", and "teams" which return a list of teams. The resolution of these fields are functions. All of these fields are lazy-executed: the list of teams will not be retrieved if the client did not ask for it in its query!
Requests and fields are the same concept and can receive parameters like in the case of "teams" on Player and "players" on Query
Step 4: Mutations
We need to fetch data, but we also need to create/update/delete these games, players,... For this purpose, GraphQL proposes mutations. Mutations are similar to queries in their definition and the difference is more semantic. Mutations will change the state on the server while queries will only retrieve data. You define these mutations as fields of a second root object type: "Mutation"
Additional features and remarks
There are many other features and topics but it would be vain to try to list all of them. I will list some of them in disorder
In the query presented at the beginning of this article, the parameter values were fixed. In a real life use case, the numbers of players to retrieve may vary and be decided at runtime. You do not want to manipulate and concatenate your query at runtime. You want to have a fixed version of your query and manipulate only the dynamic parts .
For this case, GraphQL has variables support which are passed, usually as JSON, with the query in the request
We needed to support pagination. For this case, GraphQL proposes a best practice : Connections. Because you will encounter this structure when browsing examples, below is an adaptation of the first request using "playersConnection" instead.
GraphiQL and Introspection
Multiple tools are already implemented to help you in your usage and implementation. But one tool that we felt was essential is GraphiQL (A graphical interactive in-browser GraphQL IDE).
Thanks to the strongly typed system of GraphQL and because GraphQL specifies the format to introspect the schema, GraphiQL can display a representation of your schema and, more importantly, validate your requests without having to send them.
This schema representation is documentation generated for free. You do not need to define it a second time in another location and remove the possibility of it becoming outdated.
Since we are developing a Progressive Web App, we will not have multiple versions running at the same time. For a classic usage, GraphQL will make it easier to maintain backward compatibility. Older versions will still retrieve deprecated fields/objects but not the new ones because they are missing from their queries. Newer versions will not be polluted by obsolete data for the same reason. The result of your service will not increase in size over time because you must respect backward compatibility.
I would like to point out two missing features that forced us to complete our API with REST services.
There is no specification on how to serve binaries
There is no official specification on how to push notifications or data on server change. It exists in some implementations (Subscriptions in graphql) but not in graphql-java or in the official specifications.
When to use GraphQL?
I have opposed classic REST services to GraphQL until now. But they perfectly complement each other and that is what we have chosen in our application. I have heard about GraphQL services aggregating REST services and the opposite: REST services that actually run on GraphQL to provide a fixed version.
So it really depends on the situation. For example, if you have a service with a very small result like "getStatus" that gets hit very often and should be network cached, you may not need to have GraphQL. A fixed version using a classic REST service will fit perfectly. In general caching might be heavier with GraphQL if the query you pass has many variations for a content.
But inside an application or if you have a generic API and want to allow developers to plugin their application on your API, GraphQL could be the answer.
Usually sceptic when proposed a new technology, I quickly adopted GraphQL. It has solved multiple problems that I usually face in CRUD REST services and made my life easier during the development of the application. If you want to know more about it, please checkout the following resources:
GraphQL website - http://graphql.org
GraphQL specifications - http://facebook.github.io/graphql