GraphQL Client-Side Caching

Sam Silver
7 min readJun 6, 2019

--

Clients, Caches, GraphQL, and the search for the best developer experience.

The Modern Web

At 30 years of age, the World Wide Web is moving faster than ever before. A multitude of new languages, tooling, frameworks, and infrastructure have emerged that promise to make applications on the Web more engaging for users and simpler to develop for engineers. As web applications creep closer in functionality to their native counterparts, the capability gap narrows.

In terms of user experience, there’s no metric as unequivocally impactful as page load time. A recent Google study concluded that an increased page response time of only three seconds results in 53% of visitors leaving a page. Hubspot research reports that a one second delay in page load time can cause a 7% reduction in conversions. Furthermore, 79% of customers report that they’re less likely to buy again from sites with poorly perceived performance. The takeaway is that milliseconds count, regardless of the nature of your online operation.

A similar Google study revealed that as page-load time goes from one to 10 seconds, the probability of a site visitor bouncing increases to a staggering 123%. Long story short: if you build it slow, they leave.

If your goal is to reduce page load times and provide a smooth and friendly end-user experience for your application, you can spend hours optimizing your JavaScript, lazily loading your images, and minifying your code, but the elephant in the room is the network latency surrounding your requests to remote servers.

Last year, the average worldwide round trip time (RTT) to Google was ~100ms, and around 70ms in the United States. Without even considering the computation that occurs on the remote server, this is a significant cost and an unavoidable reality of building web applications. It’s interesting that the speed of light itself has become a constraining factor for application performance.

To avoid costly round trips to remote servers, various strategies can be pursued. Caching generally refers to any high-speed data storage (in this case, on the client) that stores a subset of application data, typically transient in nature, with the purpose of quickly serving responses to future requests without having to access the data’s primary storage location, which is often a remote database. Additionally, caching past requests eases the load on the servers so that they don’t have to serve all client requests.

When infrastructure isn’t globally distributed, network latency can make certain applications untenable. In lieu of distributing your infrastructure or moving it closer to your users, Caching data is an attractive alternative.

In a REST scenario, a given endpoint and unique query string represent a globally unique identifier — only the combination of those two parameters will run a particular procedure on the server. After caching past responses for HTTP GET requests, future requests can be easily routed through the cache and a historical response can be returned if possible.

In a GraphQL scenario, we don’t have the same URL-like primitive to provide a globally unique identifier, and devising a performant caching solution is much more tricky!

But wait a second, what IS GraphQL?

For those who have somehow avoided the buzz, GraphQL is an open-source API query language provided by Facebook in 2016, after years of internal usage. Its specification enables clients to declare the exact data and shape that they require in response from the server. If you’re looking for a performant, flexible, and maintainable alternative to REST that makes the developer experience on the client side a breeze, I recommend checking out GraphQL (https://howtographql.com).

For those not familiar with the technology, the benefits of GraphQL on the client with respect to application performance are significant. Consider a blogging application where the app needs to display the titles of the posts of a specific user. The same screen will also display the names of the last 3 follows of that user. How would that situation be solved with REST and GraphQL?

In the REST case, it’s totally conceivable that the application would need to hit three different API endpoints, perhaps serially, to fetch the required information. That could be, in some cases, more than 500ms of unavoidable latency. How does GraphQL fare? With GraphQL, the client specifies exactly the data that it needs in a format consistent with the shape of the data described on the server. The client sends only one request, entirely avoiding the triple-fetch problem as complexity is pushed to the server side, and the requisite response information is constructed on the server side by GraphQL resolvers.=

In a REST paradigm, the server-side has total control in determining what data will be sent as a response for a given endpoint. Endpoints are generally not configured to match the data needs of components in the front-end, meaning multiple round-trip requests/responses will have to fly between the client and server.

Okay, great. GraphQL can help create more performant applications by reducing round trips between the client and server when the client requests data for its components. How does this relate to caching, again? Perhaps you’ve heard of the term “GraphQL clients” in the context of open-source tools like Apollo Client or Relay. These clients help you send correctly formed GraphQL requests, keep your UI consistent across queries, maintain and update a local cache, and help execute difficult but common tasks like pagination, mutations, and subscriptions. GraphQL clients must reimplement caching themselves in the context of GraphQL, which allows for new abstractions that make it easier for developers to interact performantly with GraphQL.

So which tool should you use?

Tools like Apollo and Relay are fully-featured, sophisticated, production-ready tools in use by enormous companies like Facebook, Flexport, The New York Times, any many more. They offer bindings to libraries like React, even offering higher-order components that tightly integrate queries into components by taking advantage of React’s various lifecycle methods. However, libraries like Relay and Apollo are perhaps too feature-rich for many applications. They’re heavyweight libraries that exhibit sprawling, and often spotty, documentation and quickly-moving codebases.

In recent years, alternatives such as URQL, FetchQL, Lokka, and more have emerged as lighter-weight alternatives to Relay and Apollo. URQL recently had a major upgrade that’s worth checking out.

A new, recently updated alternative to these libraries is FlacheQL.
FlacheQL first made its appearance in the development community in July of 2018 as a fast and flexible client-side cache for GraphQL. An open-source alternative to the larger libraries, FlacheQL offers the advantage of a lightweight API and incredible integration into any application.
FlacheQL offers lightning fast retrievals for cached queries, typically on the magnitude of 150% faster than those offered by Apollo, as well as offering partial retrieval of cached data based on search parameters — a feature that no other GraphQL library offers. This means that if a user queries the Yelp GraphQL API for 30 restaurants within a certain city, then subsequently dispatches a query with the same parameters but for 10 restaurants, FlacheQL will immediately return the results from the cache. Libraries like Apollo and Relay would have to make another trip to the database, increasing cost, response latency, and server load.

In the latest release of FlacheQL, new features include cache persistence, expiration of cached items, and “superset” partial querying. FlacheQL uses IndexedDB, a JS-based object-oriented DB that runs in your browser, to persist cached data between user sessions. It provides myriad options to cache data and meaningful ways to access persistent data, and can facilitate storing large amounts of complex data in the browser, providing developers more control with how they interact with it. Integrating FlacheQL into any application will significantly improve the user experience by reducing round-trips to the server, especially with applications that commonly query similar data for its view layer.

FlacheQL implements a time-to-live (TTL) cache invalidation strategy. Depending on the nature of an application’s data, the TTL setting can be configured on instantiation to guarantee that cached data is usably fresh.
The final feature of the new release includes the ability to query supersets of data from the cache. If a request is a match on parameters, but represents a field superset of a previously cached query, FlacheQL reconstructs the outbound query to only fetch the fields that are not present in the cache, before stitching the query response documents back together on the client side. This results in fewer invocations of resolvers on the client side, reducing the load on servers and databases even more. Combined with a properly-configured TTL based on the application needs, developers can expect that the constructed response document is fresh.

These new features constitute an interesting step forward in GraphQL client capability — we’re excited to see what the open source community thinks of some of our innovations. We happily encourage discussion, pull requests, and the raising of issues.

Check out FlacheQL on Github @ https://github.com/FlacheQL/FlacheQL, and see how it stacks up against the competition at http://www.flacheql.io/#/.

Recent contributors to the project include Aidan Noble-Goodman (anoblegoodman), Alyvia Moss (alyviam), Sam Silver (sasilver75), and Tianhao Yao (thaaronyao).

--

--

Sam Silver

Software Engineer in Los Angeles. JavaScript nerd and excited about GraphQL, Serverless, and the future of the Web!