The Guild LogoThe Guild Monogram

Search docs

Search icon

Products by The Guild

Products

Hive logoHive blurred logo

Hive

Schema Registry for your GraphQL Workflows

GraphQL Tools

GraphQL Tools

A set of utilities for faster GraphQL development

Get Started

Batch execution#

Batch execution is a technique for consolidating multiple operations that target a single schema. Rather than executing each operation individually, all operations can be combined and executed as one.

For example, given the following GraphQL operations:

query($arg: String) { field1 field3(input: $arg) } query($arg: String) { tricky: field2 field3(input: $arg) }

These can be merged into one operation, and the resulting data can be unpacked into the original shape of the multiple requests:

query($_0_arg: String, $_1_arg: String) { _0_field1: field1 _0_field3: field3(input: $_0_arg) _1_tricky: field2 _1_field3: field3(input: $_1_arg) }

Batch execution is useful because:

  • Multiple operations can be combined into one network request when targeting remote services.
  • Combined operations are guarenteed to multiplex, even with servers that execute incoming requests serially.
  • Smaller and more granular GraphQL queries may be composed and cached individually, and then batched. This offers a strategy for sub-request caching.

Note: an alternative batching pattern transmits multiple operations as an array, and requires services to be specially configured for array inputs. GraphQL Tools uses the plain GraphQL approach devised by Gatsby, and is compatible with any standard GraphQL service.

Build an executor#

An executor is a generic function that wraps a GraphQL execution with the following interface:

type Executor = (request: ExecutionRequest) => Promise<ExecutionResult>; type ExecutionRequest = { document: DocumentNode, variables?: Object, context?: Object, info?: GraphQLResolveInfo, };

A simple executor for a remote service looks like this:

import { fetch } from 'cross-fetch'; import { print } from 'graphql'; async function myExecutor({ document, variables }) { const query = print(document); const fetchResult = await fetch('http://example.com/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query, variables }), }); return fetchResult.json(); };

A simple executor for a locally-executable schema looks like this:

import { graphql, print } from 'graphql'; const schema = buildMyExecutableSchema(); async function myExecutor({ document, variables }) { return graphql({ schema, source: print(document), variableValues: variables, }); };

Batch the executor#

Once you have an executor for your service, you may call it directly or wrap it with a batch executor using the createBatchingExecutor method from @graphql-tools/batch-execute:

import { createBatchingExecutor } from '@graphql-tools/batch-execute'; import { parse } from 'graphql'; const myExec = ({ document, variables }) => { ... }; const myBatchExec = createBatchingExecutor(myExec); // Perform a batch: const [first, second] = await Promise.all([ myBatchExec({ document: parse('query($input:String) { a:field1 b:field2(input: $input) }'), variables: { input: 'hello' }, operationType: 'query' }), myBatchExec({ document: parse('query($input:String) { field2(input: $input) }'), variables: { input: 'world' }, operationType: 'query' }), ]); // query($_0_input: String, $_1_input: String) { // _0_a: field1 // _0_b: field2(input: $_0_input) // _1_field2: field2(input: $_1_input) // }

A minimum of document and operationType params are required when invoking a batched executor. A document's operation type may be computed using:

import { getOperationAST } from 'graphql'; request.operationType = getOperationAST(document, operationName).operation;

When using a batch executor, remember that multiple calls must be performed synchronously and all results awaited as one. Awaiting the results of each batching call individually will behave like a normal executor.

Merging algorithm#

Batch merging uses several transformations to build a request:

  1. Replace root-level fragment spreads with inline fragments.
  2. Add uniquely prefixed aliases to all root-level fields.
  3. Uniquely prefix all variable definitions and their references.
  4. Uniquely prefix all fragment definitions and their spreads.
  5. Prune orphaned fragment definitions.

The results are then extracted with a series of reversals:

  1. Redistribute prefixed fields among original requests.
  2. Restore original root field aliases.
  3. Redistribute errors among their corresponding requests.