GraphQL Batching â Grouping Operations for Performance
In this tutorial, you will learn about Graphql Batching. We cover key concepts, practical examples, and best practices to help you master this topic.
GraphQL batching groups multiple operations into fewer requests and database queries, reducing network overhead and database load for faster API responses.
What You'll Learn
You will learn query batching (sending multiple queries in one request), mutation batching, automatic persisted queries, DataLoader batching, and HTTP batching strategies.
Why Batching Matters
Every HTTP request has overhead â DNS resolution, TCP handshake, TLS negotiation, headers. A dashboard that needs 10 separate queries makes 10 HTTP round trips. Batching combines them into one request. DodaTech's Durga Antivirus Pro dashboard batched 12 individual data requirements into one GraphQL query, reducing page load time from 3.2 seconds to 400ms.
flowchart LR
A["Without Batching\n10 HTTP requests"] --> B["10x TCP/TLS overhead"]
A --> C["10x response parsing"]
A --> D["3000ms total"]
E["With Batching\n1 HTTP request"] --> F["1x TCP/TLS overhead"]
E --> G["1x response parsing"]
E --> H["400ms total"]
style A fill:#fca5a5,stroke:#dc2626
style E fill:#bbf7d0,stroke:#16a34a
Prerequisites: GraphQL queries, mutations, and DataLoader concepts.
Query Batching (One Request, Multiple Queries)
# Single request with multiple queries
query GetUser {
user(id: "user-001") { id name email }
}
query GetDevices {
devices { id name os status }
}
query GetThreats {
threats(severity: CRITICAL) { id name detectedAt }
}
// Variables for the batched request
[
{ "query": "query GetUser { user(id: \"user-001\") { id name email } }" },
{ "query": "query GetDevices { devices { id name os status } }" },
{ "query": "query GetThreats { threats(severity: CRITICAL) { id name detectedAt } }" }
]
// Expected response
[
{ "data": { "user": { "id": "user-001", "name": "Alice", "email": "alice@dodatech.com" } } },
{ "data": { "devices": [ { "id": "dev-001", "name": "Office-PC", "os": "Windows 11", "status": "ONLINE" } ] } },
{ "data": { "threats": [ { "id": "thr-001", "name": "Emotet", "detectedAt": "2026-06-28T10:00:00Z" } ] } }
]
Apollo Client Batch Link
import { ApolloClient, InMemoryCache, gql } from '@apollo/client';
import { BatchHttpLink } from '@apollo/client/link/batch-http';
const batchLink = new BatchHttpLink({
uri: 'http://localhost:4000/graphql',
batchMax: 10, // Max operations per batch
batchInterval: 10, // Wait 10ms to collect operations
});
const client = new ApolloClient({
link: batchLink,
cache: new InMemoryCache(),
});
// These will be batched into one HTTP request
const userQuery = client.query({ query: gql`query { user(id: "1") { name } }` });
const deviceQuery = client.query({ query: gql`query { devices { id name } }` });
const threatQuery = client.query({ query: gql`query { threats { id name } }` });
const results = await Promise.all([userQuery, deviceQuery, threatQuery]);
Mutation Batching
# Multiple mutations in one request
mutation BatchOperations {
createDevice(input: { name: "PC-1", os: "Windows", userId: "u1" }) {
id
name
}
createDevice(input: { name: "PC-2", os: "macOS", userId: "u1" }) {
id
name
}
updateDevice(id: "dev-001", input: { name: "Office-PC-Updated" }) {
id
name
}
}
// Expected response
{
"data": {
"createDevice": { "id": "dev-010", "name": "PC-1" },
"createDevice": { "id": "dev-011", "name": "PC-2" },
"updateDevice": { "id": "dev-001", "name": "Office-PC-Updated" }
}
}
Automatic Persisted Queries (APQ)
APQ reduces request size by sending a hash instead of the full query string:
// Server setup
const server = new ApolloServer({
typeDefs,
resolvers,
persistedQueries: {
ttl: 900, // Cache persisted queries for 15 minutes
},
});
// Client setup
import { createPersistedQueryLink } from '@apollo/client/link/persisted-queries';
import { createHttpLink } from '@apollo/client/link/http';
const link = createPersistedQueryLink().concat(
createHttpLink({ uri: 'http://localhost:4000/graphql' })
);
const client = new ApolloClient({ link, cache: new InMemoryCache() });
// First request: sends hash + query
// Server stores query, returns result
// Subsequent requests: sends hash only
// Server looks up cached query, returns result
DataLoader Automatic Batching
// DataLoader batches individual loads automatically
async function batchUsers(ids) {
const users = await db.users.findByIds(ids);
return ids.map(id => users.find(u => u.id === id));
}
const userLoader = new DataLoader(batchUsers);
// These three loads happen in the same tick
// DataLoader sends one batch query
const user1 = userLoader.load('u1');
const user2 = userLoader.load('u2');
const user3 = userLoader.load('u3');
const [u1, u2, u3] = await Promise.all([user1, user2, user3]);
// Only ONE database query: SELECT * FROM users WHERE id IN ('u1', 'u2', 'u3')
Common Mistakes
1. Batching Unrelated Queries
Batching queries that are independent is fine, but if one query depends on another's result, they cannot be batched. Use aliases within a single query instead.
2. Ignoring Batch Link Timeout
The batchInterval (default 10ms) adds latency per batch. If your app is not I/O-bound, individual requests may be faster. Measure before optimizing.
3. Not Setting batchMax
Without batchMax, Apollo batches unlimited operations. Set batchMax: 10 to prevent one massive request that ties up the server.
4. Batching Mutations Without Error Handling
If one mutation in a batch fails, Apollo still processes the others â but error tracking becomes complex. Handle errors per-mutation, not per-batch.
5. Forgetting Persisted Queries in Production
Persisted queries reduce bandwidth by 90%+ for repeated queries. Without them, every page load sends the full query string for every component.
Practice Questions
- What is the difference between query batching and aliases?
- How does Apollo Client's BatchHttpLink work?
- What are Automatic Persisted Queries?
- When should you avoid batching?
- How does DataLoader batching differ from HTTP batching?
Answers:
- Batching sends multiple separate queries in one HTTP request. Aliases run multiple selections within a single query, which is more efficient because the server processes them together.
- BatchHttpLink collects queries made within a short interval (default 10ms) and sends them as an array in one POST request. The server processes each independently and returns an array of responses.
- APQ sends a hash of the query instead of the full query string. The server caches the query on first encounter. Subsequent requests send only the hash, reducing bandwidth.
- Avoid batching when queries are latency-sensitive (each query should execute immediately) or when different queries need different authentication contexts.
- DataLoader batching coalesces multiple data-loading calls into one database query within a single request. HTTP batching sends multiple GraphQL operations in one network request.
Challenge: Implement a batching Strategy for DodaTech's dashboard that combines user data, device list, threat feed, system health, and notification count into a single batched query. Use Apollo Client's BatchHttpLink with appropriate max and interval settings. Add persisted queries for the most common operations.
FAQ
Mini Project
Implement a comprehensive batching strategy for DodaTech's dashboard. Use query batching for initial page load (user, devices, threats, notifications, system status), mutation batching for bulk operations (update multiple devices, dismiss multiple alerts), DataLoader batching for all nested resolvers, and automatic persisted queries for repeated queries.
What's Next
| Topic | Description |
|---|---|
| Error Handling | Error patterns in GraphQL |
| Performance Optimization | Caching, cost analysis, optimization |
| DataLoader Guide | Batching database queries |
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro