Skip to content

GraphQL Batching — Grouping Operations for Performance

DodaTech Updated 2026-06-28 6 min read

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
â„šī¸ Info

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" } ] } }
]
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.

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

  1. What is the difference between query batching and aliases?
  2. How does Apollo Client's BatchHttpLink work?
  3. What are Automatic Persisted Queries?
  4. When should you avoid batching?
  5. How does DataLoader batching differ from HTTP batching?

Answers:

  1. 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.
  2. 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.
  3. 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.
  4. Avoid batching when queries are latency-sensitive (each query should execute immediately) or when different queries need different authentication contexts.
  5. 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

Does batching reduce server load?

Yes and no — batching reduces HTTP overhead (fewer requests), but each batch request may take longer to process. The net effect depends on your infrastructure and workload.

Can I batch queries and mutations together?

Yes — Apollo's BatchHttpLink supports mixing queries and mutations in one batch. However, mutations should execute sequentially, while queries can run in parallel.

What is the difference between batching and aliases?

Aliases let you run multiple operations in one query. Batching sends multiple separate queries in one HTTP call. Aliases are more efficient because the server can optimize across selections.

How do I debug batched requests?

Enable Apollo Server logging with formatError and plugins. On the client, use Apollo DevTools to inspect batched operations and responses.

Is batching compatible with subscriptions?

No — subscriptions use WebSocket, not HTTP batching. Subscriptions maintain a persistent connection, so batching is unnecessary.

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
âŦ… DataLoader Guide
➡ Error Handling

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro