Data Query Patterns for Apollo GraphQL client
Apollo GraphQL Client (JavaScript) offers extensive options for building efficient data queries using the cache, Optimistic UI, and error handling. Developers can use various ways to fetch data, store it in the local cache or handle different types of errors.
With so many different ways to query data developers can often write suboptimal queries or rely on defaults that may give different results than expected. Apollo GraphQL documentation provides very comprehensive coverage for network and cache updates, however, they are documented in many places. To get full insights developers need to jump between different pages of documentation and sometimes even GitHub issues.
In this blog post, we going to summarize most of the fetch options that Apollo GraphQL offers and map them to patterns that can be employed by developers in their applications.
Note: Examples in this blog post using plain Apollo JavaScript Client, however, patterns and options can be used in any framework-specific implementations like React, Angular and Native Clients. This post will not cover client-side data storage and direct cache access.
What is the cache and why we need it?
One of the prerequisites for better query efficiency is to use Apollo Normalized Cache. The Cache will give us the ability to reduce the number of queries that are sent to the server and reuse already existing data. The cache layer is optional and it can be configured when creating Apollo Client:
The cache layer will be kept in memory and removed on the website/application restart. To keep cache across restarts, developers can use a separate package — apollo-cache-persist
Apollo client offers a query and mutate methods used to execute GraphQL operations. Additionally to query and variables properties, both methods come with very important flags that can be used to interact with the network and cache layer.

In the next section, we going to explain the meaning of flags and how they interact with each other.
Caching GraphQL Queries
For the client query method, we can supply the number of properties. Most important ones are:
- fetchPolicy — How you want your component to interact with the Apollo cache.
- errorPolicy — How you want your component to handle network and GraphQL errors. Users
Demystifying fetch-policy
“The fetch policy is an option which allows you to specify how you want your component to interact with the Apollo data cache. By default, your component will try to read from the cache first, and if the full data for your query is in the cache then Apollo simply returns the data from the cache. If the full data for your query is not in the cache then Apollo will execute your request using your network interface. By changing this option you can change this behavior.”
Fetch policy accepts the following options:
cache-first
: When using this policy Apollo will make network connection only for the first time and utilize cache for the next requests. When data on the server changes it will be not reflected in the cache. This option is perfect when fetching large amounts of data that changes rarely. For example, when making a query to fetch UK Post Zip-Codes codes will be fetched only once and then they will be available for the entire lifecycle of the application. This option will be enabled by default and in most cases, developers will need to change it as it will prevent from fetching fresh data from the backend.

cache-and-network
: In this policy, we are getting data fast from cache (if exist) and then supplement it with the response from the server.
If all the data needed to fulfill query is in the cache then that data will be returned. However, regardless of whether or not the full data is in your cache, thisfetchPolicy
will always execute the query with the network This fetch policy optimizes for users getting a quick response while also trying to keep cached data consistent with your server data at the cost of extra network requests. This policy will be best if you looking for simplicity for most of the general cases and can afford to reach the server with every query.

network-only
: This fetch policy will always make the request to the server and update the cache. Using this fetch policy will keep the cache up to date and always deliver fresh results from the server. The cache will be never read by this query itself but it can be used in other queries. Users will see results only when the network request finishes.

cache-only
: This fetch policy will never execute a query using your network interface. Instead, it will always try reading from the cache. If the data for your query does not exist in the cache then an error will be thrown. This fetch policy allows you to only interact with data in your local client cache without making any network requests which keeps your component fast, but means your local data might not be consistent with what is on the server.

At this point, we should know all the options that will help us to manipulate our cache.

Cache Patterns
Working with the cache is hard
https://twitter.com/codinghorror/status/506010
When implementing a cache for the first time developers often need to change their mindset and design application assuming that cache will be used. The misconfigured cache can give users old data and lead to query inefficiency. Hopefully, we can use proven patterns to make sure that our data is always up to date.
- REST-like experience: Online NetworkOnly
TL;DR
IF(ONLINE)
Query Network-Only
ELSE
Query Cache-Only
This pattern provides a very simple and convenient way for GraphQL caching that will always give developers the latest data. Clients will always make requests to the server if there is a network connect. If requests will fail or if there is no network connection developers can always fallback to the cache that will be up to date thanks to network only strategy.
This strategy is recommended for the applications that want to achieve the following targets:
- Always up to date data no matter what is in the cache
- Provide seamless offline experience from cache
- Avoid advanced topis like subscriptions, cache invalidation etc.
The obvious drawback of this approach is an intensive chattiness of the client-server communication and no ability to see the results faster (although OptimisticUI can still be used)
2. Spare the server! Subscription-based cache Refill
TL;DR
On Start: Query Network-Only
Later: Subscribe DATA
Query Cache-Only
In cases where developers want to reduce the number of queries to the server and utilize subscriptions, a CacheRefill strategy can be used. This strategy focused on using the cache layer extensively as the main source of the data. Then developers can configure an alternative cache refill strategy:
- Pooling
- Subscriptions
Despite using subscriptions clients will still go offline. This strategy works with applications like chat where in most of the cases local cache can be used to deliver a seamless experience. However, subscriptions would be working only for some specific amount of time (chat view opened etc.) so network-only queries are still needed.
3. Master-detail — Reuse Cache
When working with the master-detail view we can always prefetch data on master view and reuse cached data in the detail view — eventually prefetch extra data that is needed for details. This pattern is considered a simple query optimization.
4. Optimistic fetch
Optimistic fetch is a variation of the Online Network-Only strategy
TL;DR
Query Cache-Only
IF(ONLINE)
Query Network-Only
What “optimistic fetch” does is it tries to first read a query’s response from the cache, but if (and only if!) a network connection is available will get the server’s response in the background and write it to the cache (at that point e.g. wrapped React components will update a second time).
Basically this means your UI’s queries will always work if the requested data is available in the local cache and it will always keep the cached data consistent with your server data if it can be reached.
5. Direct cache manipulation
Apollo client allows to directly interact with the cache to use it as a database. For most use cases this is not needed thanks to refetchQueries feature that will help to update queries that could be affected. RefetchQueries is a very verbose way to work with that can degrade client performance.
That is why Apollo and other frameworks provide cache update functions where developers want to have direct access to the cache or manipulate optimistic UI objects.
NOTE: Apollo 3.0 which is currently beta is going to provide another set of useful helpers for better cache manipulation.

Ocean of Boilerplate
Once we learn about all options and practices is very easy to see a repeating pattern across the code. Most of the update functions will look almost the same. Optimistic responses will be almost always build using data that was provided in input etc.

If you looking for a very quick way to adopt the cache strategies feel free to take look into offix-cache package that provides helpers for common cache updates and data deduplication for Apollo Clients.
https://offix.dev/docs/offix-cache