Queries & Resolvers
⚙️ Queries & Resolvers
What a Resolver Is
A resolver is a function attached to one specific field, responsible for returning that field's value. Most scalar fields don't need custom code at all — if a field's name matches a property already present on its parent object, the runtime's default resolver just reads it directly.
The Resolver Signature
| Parameter | What It Is |
|---|---|
parent | Whatever the parent field's resolver returned — not the original query's top-level arguments |
args | The arguments passed to this specific field in the query (e.g. id: 42) |
context | Shared data available to every resolver in one request — the authenticated user, a database connection (Chapter 8 builds on this) |
info | Metadata about the query itself (field name, path, the full AST) — rarely touched directly |
🪆 Resolving Nested Fields, Step by Step
Take this query against Chapter 2's schema:
Query.user's resolver runs first, withargs.id === 42. It looks up and returns aUserobject.- For
name: no custom resolver exists, so the default resolver just readsparent.nameoff theUserobject from step 1. - For
posts:User.posts's resolver runs, withparentnow being that sameUserobject — it typically readsparent.idto look up that user's posts. - For each returned post's
title: again, a default resolver readsparent.title—parenthere is now one individualPostobject from step 3.
Notice that parent changes meaning at every level — it's always "whatever the field one level up just returned," never the original top-level id: 42 argument.
The Resolver Map
When You Need a Custom Resolver
Whenever a field's value isn't already sitting on the parent object with a matching name — a relationship to look up, a computed value, or data pulled from a different source entirely.
When the Default Suffices
Most scalar fields — User.name, Post.title — need zero resolver code, since the parent object already carries that exact property.
One Handler vs Many Small Functions
REST
One route handler builds and returns the entire response body for an endpoint — all the logic for that shape lives in one function.
GraphQL
Many small, focused resolvers — one per field — compose together automatically into whatever shape the client's query actually asked for.
💻 Coding Challenges
Challenge 1: Write the Post.comments Resolver
Using Chapter 2's Comment type, write the resolver for Post.comments that looks up all comments belonging to a given post, assuming a db.comments.findByPostId(postId) function exists.
Goal: Practice writing a resolver that correctly uses parent to find related data.
Challenge 2: Trace a Three-Level Query
For the query { user(id: 5) { posts { comments { text } } } }, list each resolver that runs, in order, and state what parent is at each step.
Goal: Practice tracing execution through more nesting than the chapter's own worked example.
Challenge 3: Fix a Resolver Using the Wrong Argument
A buggy User.posts resolver is written as (parent, args) => db.posts.findByAuthorId(args.id), and it fails whenever posts is queried without an id argument directly on it. Explain the bug and provide the fix.
Goal: Practice recognizing the exact mistake this chapter's tip box warns about.
parent With the Original Query's ArgumentsComing from REST, where one handler has access to the entire request at once, it's natural to expect a nested resolver to somehow still have access to the original top-level arguments (like id: 42 from the user field). It doesn't — a resolver only ever sees its own field's args, plus whatever parent the field one level up returned. A User.posts resolver has no id argument of its own; it needs to read parent.id — the id property already present on the User object passed down to it — not reach for some argument that was never passed to this specific field at all.
🎯 What's Next
With reading data covered, the next chapter turns to writing it: Mutations & Input Types — the Mutation root type in practice, input types, and returning updated data after a write.