Choosing Between GraphQL Persisted Queries and Full Query Strings: A Decision Guide
Learn when to use GraphQL persisted queries versus full query strings, with a decision table, APQ setup example, and verification steps.
16 Apr 2026, 08:25 UTC

Problem
Your GraphQL client sends the full query string in every POST request. As the application grows, request payloads become large, increasing bandwidth costs and slowing down users on metered connections. You need to decide whether to adopt persisted queries (sending only a query identifier) or keep sending full queries.
Takeaway
If you can tolerate a one‑time extra round‑trip for uncached queries and you are using Apollo Server 2+ (or a compatible implementation) with Apollo Client, enable Automatic Persisted Queries (APQ) to cut request size by roughly 60‑90 % after the first request. Otherwise, stay with full query strings to avoid server‑side registry complexity and guarantee immediate operation.
Decision Factors
- Network savings – persisted queries replace the query document with a short hash (usually SHA‑256, 32 bytes) plus a tiny overhead.
- Server complexity – requires a query registry or reliance on APQ’s fallback mechanism.
- Client latency – first use of a new query triggers a GET with the full query, then a POST with only the ID; subsequent requests are faster.
- Compatibility – full query strings work with any GraphQL server; persisted queries need server support for the
extensions.persistedQueryfield.
Comparison Table
| Aspect | Persisted Queries (APQ) | Full Query Strings |
|---|---|---|
| Request size after warm‑up | ~30‑50 bytes (ID) + small JSON overhead | Full query document (often 200‑800 bytes+) |
| First‑request latency | One extra GET round‑trip (full query sent via URL) | Single POST with full query |
| Server‑side state | Query registry (in‑memory or Redis) or APQ fallback logs | None |
| Implementation effort | Add persistedQueries link on client; enable APQ on server |
None (default) |
| Compatibility | Apollo Server 2+, Apollo Client 3+, or any server that echoes extensions.persistedQuery |
Any GraphQL over HTTP implementation |
When to Choose Persisted Queries
Choose this path if:
- Your client and server are both Apollo‑based (or you have a gateway that supports APQ).
- You observe that query strings are a noticeable portion of your outbound traffic (e.g., >150 bytes per request).
- You can absorb the extra GET latency for uncached queries (typically a few milliseconds on LAN, higher on high‑latency mobile networks).
- You have a mechanism to monitor the registry size (e.g., Redis
INFOor Apollo Server logs) to prevent unbounded growth.
When to Stick with Full Query Strings
Stay with full queries when:
- You use a non‑Apollo GraphQL server (e.g., Yoga, Express‑GraphQL) that does not expose the persisted‑query extension.
- Your deployment environment cannot reliably maintain a query registry (stateless serverless functions without external storage).
- You prioritize deterministic latency: every request must succeed in a single round‑trip.
- Bandwidth savings are negligible compared to other costs (e.g., large payloads from mutations or file uploads).
Concrete Implementation: Enabling APQ
The following steps assume you are running Apollo Server 4 and Apollo Client 3 in a JavaScript/TypeScript project.
1. Server Setup
// server.js
const { ApolloServer } = require('@apollo/server');
const { expressMiddleware } = require('@apollo/server/express4');
const express = require('express');
const app = express();
app.use(express.json());
const server = new ApolloServer({
typeDefs, // your GraphQL schema
resolvers,
// APQ is enabled by default in Apollo Server 2+
// you can optionally set a custom cache for the registry:
// persistedQueries: { cache: new InMemoryLRUCache() }
});
await server.start();
app.use('/graphql', expressMiddleware(server));
app.listen({ port: 4000 }, () =>
console.log('🚀 Server ready at http://localhost:4000/graphql'));
Run this file with node server.js. No special permissions are needed beyond the ability to bind to port 4000.
2. Client Setup
// apolloClient.js
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';
import { PersistedQueryLink } from '@apollo/client/link/persisted-queries';
const httpLink = new HttpLink({ uri: '/graphql' });
const persistedQueryLink = new PersistedQueryLink({
// Send a GET with the full query when the hash is unknown
useGETForHashedQueries: true,
});
const client = new ApolloClient({
link: persistedQueryLink.concat(httpLink),
cache: new InMemoryCache(),
});
export default client;
Install the required packages:
npm install @apollo/client @apollo/client/link/persisted-queries
Run the client code in your web application (e.g., React, Vue, or plain JavaScript). No elevated privileges are required; just ensure the bundle is served to the browser.
3. Verification Steps
- Open Chrome DevTools → Network tab.
- Filter requests to
/graphql. - Perform a query that has never been sent before (e.g., open a fresh incognito tab).
- Observe two requests:
- A
GETto/graphql?query=…containing the full query string in the URL. - A subsequent
POSTwith a JSON body like{ "extensions": { "persistedQuery": { "version": 1, "sha256Hash": "<32‑char hex>" } } }and noqueryfield.
- A
- Repeat the same query; you should now see only the POST with the persisted‑query hash.
- Check the request size: the POST payload should be roughly 30‑50 bytes (plus HTTP headers). Compare this to the original POST size (often >200 bytes). A reduction of 60‑90 % indicates APQ is working.
Limitations and Practical Checks
- Registry size – If you enable APQ without an external cache (e.g., using the default in‑memory map), the server will keep every distinct query hash forever. In long‑running services this can cause memory growth. Mitigation: use a Redis-backed cache or set a TTL.
- Hash collisions – SHA‑256 makes collisions astronomically unlikely, but if you ever suspect a collision, clear the registry and redeploy.
- Security – Persisted queries do not replace authentication, authorization, or input validation. Continue to validate incoming operations against your schema.
- Fallback loops – If the client link and server versions mismatch, the client may repeatedly fall back to GET, causing extra latency. Verify that
@apollo/serverversion ≥2 and@apollo/client/link/persisted-queriesversion match the client major version.
Summary
Persisted queries trade a small, one‑time latency cost for significant bandwidth savings after the first request, but they require a server‑side query registry and compatible client/server stacks. If your environment meets those constraints and you need to reduce outbound traffic, enable APQ as shown. Otherwise, keep using full query strings for simplicity and guaranteed single‑round‑trip behavior.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.