If you're using GraphQL in production, you've probably heard about Automatic Persisted Queries (APQ). But what are they exactly, and why should you care?
In this post, we’ll walk through:
- Why GET requests can improve GraphQL performance
- What APQ is and how it works
- How to implement APQ (with code!)
- How the client knows when to send only the hash
- How to handle fallbacks gracefully
📦 Why Use GET Requests in GraphQL?
GraphQL usually uses POST requests for sending queries. However, when the operation is a read (query), using GET has a huge benefit: HTTP caching via browsers, proxies, or CDNs.
But there’s a catch: GET requests pass the query in the URL, and URLs have size limits. So for large or complex queries, it can break.
🧠 Enter: Automatic Persisted Queries (APQ)
Instead of sending the full query in the URL or request body, you just send a hash of the query. The server:
- Checks if it already knows this query
- If yes, executes it and returns the result
- If not, returns a
PersistedQueryNotFounderror - The client then retries, this time with the full query and the hash
This avoids sending the full query every time and enables GET requests with tiny URLs.
🔁 APQ Lifecycle Example
Here’s what happens step-by-step:
1. First Request (GET):
GET /graphql?extensions={
"persistedQuery": {
"version": 1,
"sha256Hash": "abcd1234..."
}
}&operationName=GetUser
2. Server Response:
{
"errors": [
{
"message": "PersistedQueryNotFound"
}
]
}
3. Client Retry (POST):
{
"query": "query GetUser { user { id name } }",
"extensions": {
"persistedQuery": {
"version": 1,
"sha256Hash": "abcd1234..."
}
},
"operationName": "GetUser"
}
4. Server stores the query and executes it. ✅
On the next identical request, the client only sends the hash!
⚙️ How Does the Client Know?
Most GraphQL clients (Apollo, urql) handle APQ automatically. You don’t need to write extra logic — just enable it in the config.
Example with Apollo Client:
import { createPersistedQueryLink } from "@apollo/client/link/persisted-queries";
import { ApolloClient, InMemoryCache, HttpLink } from "@apollo/client";
import { sha256 } from "crypto-hash";
const link = createPersistedQueryLink({ sha256 }).concat(
new HttpLink({ uri: "/graphql" })
);
const client = new ApolloClient({
cache: new InMemoryCache(),
link
});
Apollo will:
- First send a hash via
GET - Retry with full query via
POSTif not found
🔥 Bonus: Benefits of Using APQ
- Smaller payloads
- Easier CDN caching
- Increased performance for mobile clients
- Optional: block non-whitelisted queries in production
🧯 What If the First Request Fails?
If the server responds with PersistedQueryNotFound, the client automatically retries with the full query — the user never sees the failure.
Still, for better DX, you can catch and track these retries for debugging or analytics.
That’s it! APQ is a clever pattern to boost performance and reduce bandwidth — and it’s surprisingly simple to adopt.
🧠 Have questions or tips? Let me know in the comments!
Top comments (0)