Parse, validate, execute

GraphQL · Expert · 15 min

Knowing the pipeline lets you write custom rules, cache intelligently and debug odd behaviour.

Every GraphQL server runs the same three-phase pipeline from the spec, which graphql-js exposes as separate functions:

  • parse(source) turns the text into a document AST (OperationDefinition, Field, FragmentSpread nodes with locations). Parsing is pure, so servers cache ASTs by query hash.
  • validate(schema, document, rules) runs the validation rules (fields exist, arguments typed, fragments used, no conflicting response keys, variables defined...). You can add rules, e.g. a max-depth rule. Validation is also cacheable per schema and document.
  • execute({ schema, document, variableValues, contextValue }) coerces variables, then collects fields (merging fragments and same-named fields), resolves them, and completes values: serializing scalars, mapping lists, resolving abstract types, and enforcing non-null.

Execution details experts rely on:

  • Query root fields may execute in parallel; mutation root fields run serially.
  • Field collection merges selections with the same response key, so { a: f a: f } resolves once.
  • The info argument contains fieldNodes, returnType, path and fragments, enough to look ahead and build an optimal database query.
  • graphql() is just parse + validate + execute in one call.

Tooling builds on the AST: visit() walks and transforms documents, print() turns an AST back into text, and TypeInfo tracks types during a visit.

Example

import { buildSchema, parse, validate, execute, visit, Kind } from "graphql";
const schema = buildSchema(`type Query { a: Int  b: Int }`);
const document = parse("query Q { a b ...F } fragment F on Query { a }");

const fields = [];
visit(document, { Field: (node) => { fields.push(node.name.value); } });
console.log(fields); // [ 'a', 'b', 'a' ]

console.log(validate(schema, document).length); // 0 -> valid
const result = await execute({
  schema, document,
  rootValue: { a: () => { console.log("resolve a"); return 1; }, b: 2 },
});
// "resolve a" is printed once: same-named fields are merged
console.log(JSON.stringify(result.data)); // {"a":1,"b":2}
console.log(document.definitions[0].kind === Kind.OPERATION_DEFINITION); // true

Remember

  • Pipeline: parse to AST, validate with rules, execute and complete
  • Parsing and validation are cacheable per document
  • Field collection merges fields with the same response key
  • info exposes the selection for look-ahead

Watch out for

  • Re-parsing and re-validating identical documents on every request in a hot path.

Check yourself

  1. A query selects `a` directly and again through a fragment. How many times does the resolver for `a` run?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: spec.graphql.org/September2025/#sec-Execution