Episode 6 dissects the resolver as the heart of GraphQL: the anatomy of a resolver function, the four parameters — parent, args, context, and info — resolver chains, the context object for sharing data between resolvers, async resolvers, and modular file organization.

If the schema is the contract, the resolver is the executor. Every field in the schema — from the root query down to the deepest field — has a resolver that determines where the data comes from. Episode 6 dissects resolvers thoroughly.
We'll learn the resolver signature with its four parameters (parent, args, context, info), how data flows between resolvers (resolver chains), how to build the context object, async resolver patterns, and strategies for organizing resolvers so they stay maintainable as the project grows.
In general, a resolver is a function with four parameters:
async function userResolver(parent, args, context, info) {
return context.dataSources.users.findById(args.id);
}Those four parameters are:
parent: the value returned by the parent field's resolver.args: the object containing the arguments provided by the client.context: the shared object for all resolvers within one request.info: execution metadata such as the AST of the selected fields.Resolvers are optional. If a field has no resolver, GraphQL uses the default resolver: it reads a property with the same name from the parent object. So if the user resolver returns the object { id, username }, the username field is automatically populated without needing an additional resolver.
export const resolvers = {
Query: {
user: (_, args) => ({ id: args.id, username: "arman" }),
},
};Here the username field takes its value directly from the returned object, with no explicit resolver.
The parent parameter connects the parent resolver to the child resolver — this is what's called a resolver chain:
export const resolvers = {
Query: {
post: async (_, args, ctx) => ctx.db.posts.find(args.id),
},
Post: {
author: async (post, _, ctx) =>
ctx.db.users.find(post.authorId),
},
};Notice the flow: the post resolver returns a post object, then the Post.author resolver receives that object as parent and uses post.authorId to fetch the author. Data flows top to bottom, and each level can fetch new data based on the value from the level above.
For deeply nested queries (like post.author.company), each level builds the next resolver chain. The benefit: resolvers are only called for fields the client actually requests. This is also what causes the N+1 problem if you're not careful — a topic dissected in episode 9.
Context is an object built once per request and shared with every resolver. The right place for the logged-in user, database connections, data loaders, and important headers:
const server = new ApolloServer({
typeDefs,
resolvers,
});
const { url } = await startStandaloneServer(server, {
context: async ({ req }) => ({
user: await authenticateUser(req.headers.authorization),
db: createDbConnection(),
}),
});Notice the pattern above: context is built with a context function that receives the request object. Inside it, the token from the authorization header is used to validate the user — the full authentication details will be covered in episode 13.
Every resolver can access the context, so data like context.user and context.db doesn't need to be fetched again in every resolver. This also keeps resolvers "thin": resolvers just call functions from the context instead of writing database logic inline. We'll return to this pattern when building data loaders in episodes 9 and 20.
The info parameter (typed GraphQLResolveInfo) contains execution details, including the fields the client selected. It's rarely used, but useful for advanced optimization — for example, only querying the database columns requested, or building dynamic queries. Don't reach for info before you need it; its complexity is high.
Resolvers that fetch data from a database or API are asynchronous. The pattern used is async and await:
export const resolvers = {
Query: {
users: async (_, __, ctx) => ctx.db.users.findAll(),
},
};If an error occurs inside an async resolver, throw that error. GraphQL will catch it, return the error to the client, and make sure other independent fields still run — the details of this error strategy will be covered in episode 11.
As the schema grows, a single resolver file becomes unmaintainable. A common modular pattern:
src/
resolvers/
user.resolver.ts
post.resolver.ts
comment.resolver.ts
schema/
user.graphql
post.graphqlYou can create this structure with mkdir -p src/resolvers src/schema. Each module exports its own resolvers, then they're merged at a single point. This decomposition follows the modular resolver pattern and makes code review easier. When the project gets very large, consider federation (episode 22) or a code-first library like Type-GraphQL (episode 30).
Key takeaways:
parent, args, context, and info.parent.async and await pattern; always throw errors so GraphQL can handle them.In the next episode, episode 7, you'll learn about building a GraphQL server with Apollo Server — installation, schema construction, Express and Next.js integration, Apollo Sandbox for testing, and a TypeScript development setup. This is the first episode where all the concepts come together into a server that actually runs!