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.