GraphQL Harness
GraphQLHarness is new in GraphQL.js v17. It customizes the phases used by
graphql() and graphqlSync().
graphql() is the convenience entry point that parses, validates, and executes
a GraphQL operation. In v17, those phases are represented by a harness:
type GraphQLHarness = {
parse: GraphQLParseFn;
validate: GraphQLValidateFn;
execute: GraphQLExecuteFn;
subscribe: GraphQLSubscribeFn;
};defaultHarness is the built-in harness used by graphql() and
graphqlSync().
import { defaultHarness, graphql } from 'graphql';
const result = await graphql({
schema,
source,
harness: defaultHarness,
});Why this exists
The harness is a host integration API modeled after Envelop, The Guild’s GraphQL plugin system. Envelop showed that many servers need to customize the same request phases: parsing, validation, execution, subscription execution, and the cross-cutting behavior around those phases.
GraphQL.js remains a reference implementation, not a full plugin framework.
The harness brings the broader phase types used by that ecosystem closer to the
reference implementation so frameworks and plugin systems can share a common
shape. For example, GraphQLParseFn can return a DocumentNode or a promise
for a DocumentNode, even though the built-in GraphQL.js parse() function is
synchronous.
For application servers, prefer Envelop or a framework built on Envelop over
using a raw GraphQLHarness directly. The goal is that frameworks can accept a
custom harness, and plugin systems that customize these phases can interoperate
without each framework inventing a different integration surface.
What can be customized
Each harness function receives the same arguments as the corresponding GraphQL.js phase. The difference is that a harness phase may finish immediately or by returning a promise:
type MaybePromise<T> = T | Promise<T>;
type GraphQLParseFn = (
source: string | Source,
options?: ParseOptions,
) => MaybePromise<DocumentNode>;
type GraphQLValidateFn = (
schema: GraphQLSchema,
documentAST: DocumentNode,
rules?: readonly ValidationRule[],
options?: ValidationOptions,
) => MaybePromise<readonly GraphQLError[]>;
type GraphQLExecuteFn = (args: ExecutionArgs) => MaybePromise<ExecutionResult>;
type GraphQLSubscribeFn = (
args: ExecutionArgs,
) => MaybePromise<
ExecutionResult | AsyncGenerator<ExecutionResult, void, void>
>;Any harness phase may return synchronously or asynchronously. graphqlSync()
still requires every phase and resolver it reaches to complete synchronously.
GraphQLExecuteFn deliberately returns only ExecutionResult; it does not
include the experimental incremental delivery result type.
Cached documents
A host that has a trusted document cache can replace the parse phase while keeping the default validation and execution behavior.
import { defaultHarness, graphql } from 'graphql';
const harness = {
...defaultHarness,
parse(source, options) {
const cached = documents.get(String(source));
return cached ?? defaultHarness.parse(source, options);
},
};
const result = await graphql({
schema,
source,
variableValues,
operationName,
harness,
});External validation
A host can also replace validation. This is useful for persisted operation registries that validate at build time and return stored validation results at runtime.
import { defaultHarness, graphql } from 'graphql';
const harness = {
...defaultHarness,
async validate(schema, document, rules, options) {
const cached = await registry.getValidationResult(document, schema);
return cached ?? defaultHarness.validate(schema, document, rules, options);
},
};
const result = await graphql({
schema,
source,
harness,
});Relationship to incremental delivery
graphql() remains a single-result operation pipeline. A harness does not make
graphql() return incremental delivery payloads, and the harness execute
function has the same single-result contract.
Operations that use @defer or @stream should use
experimentalExecuteIncrementally() after parsing and validation. See
Advanced Execution Pipelines for the
lower-level execution APIs and Defer and Stream for the
incremental result shape.