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
PersistedQueryNotFound
error - 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
POST
if 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)