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,FragmentSpreadnodes 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
infoargument containsfieldNodes,returnType,pathandfragments, 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); // trueRemember
- 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
infoexposes the selection for look-ahead
Watch out for
- Re-parsing and re-validating identical documents on every request in a hot path.