Best Practices for GraphQL Clients

Joe Savona

What Is a GraphQL Client?

A GraphQL client is software that allows developers to specify queries that are then sent to the server. Once the client receives these responses, it parses them into objects and makes these objects available for use by the front-end code.

Clients should cache the data that they receive from the server for several reasons. First, it avoids fetching the same information multiple times. Second, it allows the application to work offline. Third, storing data in a single place makes it easier for changes to that data to be reflected throughout the application.


Even before sending a query to the server, developers need to define what that query looks like. The standard approach is "colocation": keeping queries and components close to each other. This gives developers two options: either using a GraphQL file that contains fragments for each component or directly embedding the GraphQL in the code.

Colocation makes it a lot easier to coordinate the user interface and the task of data retrieval. When you add a property to the user interface, you can jump directly to the fragment and keep the component in sync with its data dependencies. In addition, when a component is reused somewhere in the application, you can reference its fragments to make sure that all the data is fetched in order to render the interface.

Query Construction

The first attempt at query construction in GraphQL might look something like what's done in Relay, a JavaScript framework developed at Facebook for writing data-driven React applications. At build time, query strings are parsed and a simple, abstract syntax tree is created to represent the query. Then, at runtime, the AST is executed, fragments are embedded into their parents, and the application can convert the query to a string and send it to the server.

Although this approach works for smaller applications, it doesn't necessarily scale well. As the application gets more complex, it will include more user interface components and types, which makes your queries larger even as the amount of data retrieved stays the same. At runtime, the application is spending more time generating the query representation and converting it into a string.

Persisted Queries

An alternate solution to query construction is what's known as persisted queries. The key to this idea is that the conversion from an AST to a query string doesn't have to happen at runtime, as it did in the previous method. Since you know that queries are static pieces of text that are either inside GraphQL files or embedded in the code, the aggregation and conversion can happen at build time instead of runtime.

Once the fragments are assembled, the entire string can be hashed in order to obtain a unique ID for the query. That ID is sent to the server, while the full query is stored in a database. At runtime, instead of sending the entire string to the server, the application can simply send the ID and any variables as necessary.

Unlike the previous solution, persisted IDs don't grow as the application gets more complex. Although the amount of data sent via variables will grow, this factor is negligible when compared with the cost of sending over the entire string.


When the user exits and then reopens the application, you don't want to have to download all the relevant data all over again. Just as with REST, you can add a response cache to increase your application's performance.

To perform caching, the application keeps track of recent query IDs and their associated variables. These IDs and variables are hashed, and the hash value is checked against the contents of the cache to see if the application needs to make a request from the server. If the key isn't in the cache, then the application talks to the server, receives the requested data, stores it in the cache and sends the relevant data to the user interface.

Data Consistency

Caching is very useful and helps with your application's performance, but it also introduces a new set of issues. How can you be sure that the data is up to date and consistent throughout your application? Without some form of coordination, your caches will get out of sync.

There are various approaches to this problem, and one of the most common is what's known as a normalized store. The store sits between the application and the cache and serves as a repository for all the queries fetched by the application. When another query asks for updated information from the server, the store updates its values and notifies the application to change the user interface if necessary.


When it comes to pagination and working with lists, there are a few things to keep in mind:

When the ranking of items in a list changes after talking to the server, it's up to the individual developer to decide how to handle it. If you've already rendered a view, you may not want to have an item suddenly jump to another location when rendering again.

The Future of GraphQL

Throughout GraphQL's four-year history, the suggested practices and approaches have changed as various innovations have emerged. If you're starting out on your GraphQL journey, the best advice is to focus on your specific use cases, then solve other, larger problems as you encounter them. GraphQL is still very much in the exploratory phase as a technology, and the community remains open to new ideas and experimentation.

Watch the video