GraphQL API Gateway Patterns
Automatic Content Revalidation

Automatic Content Revalidation Pattern for GraphQL API Gateways

The Automatic Content Revalidation Pattern is an extension to the Persisted Operations Pattern. The Pattern ensures that we efficiently revalidate a persisted Operation leveraging HTTP caching mechanisms.

Problem

Without Persisted Operations, this pattern wouldn't be possible at all, because HTTP-based caching mechanisms require a unique resource identifier. With regular GraphQL over HTTP, all requests use the same Endpoint via HTTP POST, meaning that we're not able to leverage HTTP caching mechanisms.

With Persisted Operations though, we get a whole new set of possibilities. That is because every persisted Operation will be tied to a specific URL.

So, let's say we have a persisted Operation with the following URL, This if from our live demo, so you can actually try it out.

https://apollo-federation-nextjs.wundergraph.dev/operations/TopProducts

How can we make sure that we send the minimum amount of data over the wire when nothing has changed? E.g. if the result is more or less the same as the last time we requested it. Do we even need to send anything at all? Or can we simply tell the client that the last result is still valid?

Solution

The solution is to combine the Persisted Operations Pattern with standard HTTP caching mechanisms, such as the ETag and If-None-Match headers. Let's see how we can implement this in practice.

When a browser or any other HTTP client makes a request to the URL above, here's what we get back as a response (mock data).

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
ETag: W/"17543610068397070247"
Date: Thu, 01 Apr 2021 12:00:00 GMT
Content-Length: 123

{
  "data": {
    "topProducts": [
      {
        "upc": "1",
        "name": "Table",
        "price": 899,
        "weight": 100
      },
      {
        "upc": "2",
        "name": "Couch",
        "price": 1299,
        "weight": 1000
      },
      {
        "upc": "3",
        "name": "Chair",
        "price": 54,
        "weight": 50
      }
    ]
  }
}

Note that the response contains an ETag header. The easiest way to generate an ETag is to use a hash of the response body. An ETag is a unique identifier for the response body, a so called entity tag.

Browsers and other standardized HTTP clients are smart and understand the ETag header. On subsequent requests, they will send the ETag back to the server by adding an If-None-Match header to the request. This header tells the server that the client already has a cached version of the response with the given ETag.

The server can then resolve the request, generate a new ETag and compare it with the If-None-Match header. If the ETag is the same, the server can respond with a 304 Not Modified status code. This tells the client that the cached version is still valid and that it can use the cached version instead of the new response.

HTTP/1.1 304 Not Modified
ETag: W/"17543610068397070247"
Date: Thu, 01 Apr 2021 12:00:00 GMT

This is especially useful for large responses that don't change often, but you want to make sure that the client always has the latest version.

Real World Example

In WunderGraph, we've made this pattern usable out of the box. When defining an operation in the .wundergraph/operations directory, the file path will be used as the URL for the persisted operation. This means that we can easily use the ETag header for all Query operations.

What's best about this feature is that we don't have to implement anything on the client-side to make it work. Browsers support ETags out of the box and will automatically send the If-None-Match header if the ETag is present in a previous response.

We've built adapters for SWR, Tanstack Query, Relay and more, so this pattern works out of the box in many different frameworks.