GraphQL query batching

Intercept and mock batched GraphQL queries.

Query batching is a performance mechanism provided by some GraphQL clients to optimize the number of operations made by grouping them togeher in a single query. While this feature has its practical benefits, query batching is not a part of the GraphQL specification (neither the GraphQL-over-HTTP specification), lacking any standard consensus on the syntax and behavior of batched queries. Because of this, MSW does not provide a built-in way of handling such queries.

We highly recommend implementing the support for batched GraphQL queries as a part of your MSW setup. Below, you can find a couple of examples of how to achieve that.

General knowledge

At its core, mocking a batched GraphQL query comes down to the following steps:

  1. Intercept the batched GraphQL query;
  2. Unwrap the batched query into individual GraphQL queries;
  3. Resolve the individual queries against the existing request handlers;
  4. Compose the batched response.


Apollo provides Query batching by grouping multiple queries in a single root-level array.

  query GetUser {
    user {
  query GetProduct {
    product {

This grouping is later reflected in the payload structure received in response to a batched query:

  { "data": { "user": { "id": "abc-123" } } },
  { "data": { "product": { "name": "Hoover 2000" } } }

You can mock batched GraphQL queries in Apollo by introducing a custom batchedGraphQLQuery higher-order request handler that intercepts such batched queries, unwraps them, and resolves them against any given list of request handlers using the getResponse function from msw.

import { http, HttpResponse, getResponse, bypass } from 'msw'
function batchedGraphQLQuery(url, handlers) {
  return, async ({ request }) => {
    const payload = await request.clone().json()
    // Ignore non-batched GraphQL queries.
    if (!Array.isArray(payload)) {
    const responses = await Promise.all( => {
        // Construct an individual query request
        // to the same URL but with an unwrapped query body.
        const queryRequest = new Request(request, {
          body: JSON.stringify(operation),
        // Resolve the individual query request
        // against the list of request handlers you provide.
        const response = await getResponse({
          request: queryRequest,
        // Return the mocked response, if found.
        // Otherwise, perform the individual query as-is,
        // so it can be resolved against an original server.
        return response || fetch(bypass(queryRequest))
    // Read the mocked response JSON bodies to use
    // in the response to the entire batched query.
    const queryData = await Promise.all( => response?.json()),
    return HttpResponse.json(queryData)

Then, use the batchedGraphQLQuery function in your request handlers:

import { graphql, HttpResponse } from 'msw'
const graphqlHandlers = [
  graphql.query('GetUser', () => {
    return HttpResponse.json({
      data: {
        user: { id: 'abc-123' },
export const handlers = [
  batchedGraphQLQuery('/graphql', graphqlHandlers),


The batched-execute package provides Query batching by hoisting multiple operations on a single query and achieving grouping by using field aliases.

query {
  user_0: user {
  product_0: product {

The client then remap the field aliases to the original operations, producing a flat response object.

You can mock batched GraphQL queries in batched-execute by introducing a custom batchedGraphQLQuery higher-order request handler that intercepts such batched queries and resolves them against a mocked schema. We recommend a schema-first API mocking in this case to support anonymous queries.

import {
  graphql as executeGraphQL,
} from 'graphql'
import { http, HttpResponse, bypass } from 'msw'
// Describe the GraphQL schema.
// You can also use an existing schema!
const schema = buildSchema(`
type User {
  id: ID!
type Query {
  user: User
function batchedGraphQLQuery(url, handlers) {
  return, async ({ request }) => {
    const payload = await request.json()
    // Resolve the intercepted GraphQL batched query
    // against the mocked GraphQL schema.
    const result = await executeGraphQL({
      source: payload.query,
      variableValues: data.variables,
      rootValue: {
        // Mock individual queries, fields, and types.
        user: () => ({ id: 'abc-123' }),
      async fieldResolver(source, args, context, info) {
        // Resolve the known fields from the "rootValue".
        if (source[info.fieldName]) {
          return defaultFieldResolver(source, args, context, info)
        // Proxy the unknown fields to the actual GraphQL server.
        const compiledQuery = info.fieldNodes
          .map((node) => print(node))
        const query = `${info.operation.operation} { ${compiledQuery} }`
        const queryRequest = new Request(request, {
          body: JSON.stringify({ query }),
        const response = await fetch(bypass(queryRequest))
        const { error, data } = await response.json()
        if (error) {
          throw error
        return data[info.fieldName]
    return HttpResponse.json(result)