From a Flat Query to a Resolver Tree
A GraphQL query is nested text, but execution turns it into a tree of resolver calls that mirrors that nesting exactly. The root Query resolver runs first, and whatever it returns becomes the parent argument for each resolver on the fields selected beneath it, and so on down to the leaves. This chain means a resolver never needs to know how it was reached; it only needs to trust that its parent argument already contains what the resolver above produced.
Cricket analogy: A run chase unfolds over by over, each over's outcome shaped by the state left by the previous one, just as each resolver in the chain builds on the state left by the resolver above it.
What Lives in the Context Object
Unlike parent, which changes at every level of the tree, context is created exactly once per incoming request and passed unchanged to every resolver, no matter how deeply nested. Typical Apollo Server or graphql-js setups build context in a function tied to the server's request handler, populating it with the authenticated user (decoded from a JWT), a database client or ORM instance, request-scoped dataloaders, and sometimes feature flags. Because context is shared and mutable, some teams also use it to cache values computed once and reused by multiple resolvers within the same request.
Cricket analogy: The match umpire's decision review system data is available to every official on the field for that entire match, not recalculated per ball, just as context is built once and shared across every resolver in a request.
const server = new ApolloServer({ typeDefs, resolvers });
const { url } = await startStandaloneServer(server, {
context: async ({ req }) => {
const token = req.headers.authorization || '';
const user = await getUserFromToken(token);
return {
user,
db: dbClient,
loaders: {
bookById: createBookLoader(dbClient),
},
};
},
});
// later, in a resolver:
// author: (parent, args, context) => context.loaders.bookById.load(parent.authorId)Context Is Immutable Across the Tree, Not Across Requests
A common mistake is assuming context can be reused between requests to cache expensive work; it cannot safely be, because each incoming HTTP request gets a fresh context object, and reusing state across requests risks leaking one user's data (like their authenticated identity) into another user's response. The correct pattern for cross-request caching is a separate long-lived cache (Redis, an in-memory LRU) that context references, rather than storing per-user data directly on a module-level singleton.
Cricket analogy: A stadium's electronic scoreboard resets to zero at the start of every new match rather than carrying over the previous match's score, just as context resets fresh for every new request.
Never store user-specific data on a module-level variable or singleton outside the context function. Node.js servers handle many concurrent requests on one process, so shared mutable state outside context can leak one user's session into another's response.
Apollo Server also supports a contextValue function per request in newer API versions; the concept is identical, only the setup syntax has shifted across major versions of the library.
- GraphQL execution turns a nested query into a tree of resolver calls matching its shape.
- Each resolver receives the value its parent resolver returned via the parent argument.
- context is built exactly once per request and passed unchanged to every resolver in the tree.
- Typical context contents include the authenticated user, database clients, and dataloaders.
- Context must never persist or leak state across separate HTTP requests.
- Cross-request caching should use an external store like Redis referenced from context, not module-level singletons.
- Resolvers should trust their parent argument rather than trying to know their position in the overall query.
Practice what you learned
1. How does GraphQL turn a nested query into resolver calls?
2. How often is the context object created during a single GraphQL request?
3. What is a safe way to cache expensive data across multiple requests?
4. Why is storing user-specific data on a module-level variable dangerous in a Node.js GraphQL server?
5. What does a resolver need to know about its position in the overall query tree?
Was this page helpful?
You May Also Like
Resolver Functions Explained
Learn what resolver functions are, how their four arguments work, and how GraphQL servers turn a query into actual data.
Resolving Nested Fields
See how GraphQL resolves deeply nested object fields, including lists of objects, and why field resolution order matters for performance.
DataLoader and the N+1 Problem
Understand why naive GraphQL resolvers trigger the N+1 query problem and how Facebook's DataLoader batches and caches requests to fix it.
Error Handling in Resolvers
Learn how GraphQL's partial-response error model works, how nullability affects error propagation, and how to design custom, informative errors.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentgRPC Study Notes
Protocol Buffers · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentDjango Study Notes
Python · 30 topics