Queries & Resolvers
In the foundations module you learned that a schema is a contract and a query is a path traced through a graph of types. This module is about the two halves that make that path come alive: the query that asks for data, and the resolvers that produce it. A query is pure intent — it names fields and says nothing about where the values come from. Resolvers are the logic the server runs, field by field, to fill those names with real values.
Queries read; resolvers produce
Section titled “Queries read; resolvers produce”Keep these two roles separate in your mind and everything else follows:
- A query is a read operation written by the client. It selects fields and may pass arguments, but it contains no logic. It is a shopping list.
- A resolver is a server-side function attached to a single field. When execution reaches that field, the resolver runs and returns the value. It is the stock clerk who fetches each item on the list.
The server never guesses. For every field in the query it calls the matching resolver, collects what comes back, and assembles a JSON response shaped exactly like the query.
The Query root
Section titled “The Query root”Every read has to start somewhere, and that somewhere is the special root object type named Query. The fields directly under Query are the entry points of the API — the only places a read may begin. From there, fields can return object types whose own fields lead deeper into the graph.
type Query { currentUser: User track(id: ID!): Track}
type User { id: ID! name: String!}
type Track { id: ID! title: String!}A client may begin a read with currentUser or track, because those are fields of Query. It may not begin with title, because title lives on Track, deeper in the graph — you reach it only after stepping through a field that returns a Track.
The resolver chain
Section titled “The resolver chain”Resolution is not one big function; it is a chain of small ones. Execution starts at the query’s top-level fields (the Query root), runs their resolvers, then descends into each selected sub-field and runs its resolver, and so on until every selection bottoms out at a scalar. The value a parent resolver returns becomes the input to its children’s resolvers — that is the thread that ties the chain together.
flowchart TD Q["Query root"] -->|"track(id) resolver"| T["Track object"] T -->|"title resolver"| Title["title: String"] T -->|"artist resolver"| A["Artist object"] A -->|"name resolver"| Name["name: String"]
Read the diagram top to bottom: the track field resolver runs first and returns a track object; the engine then resolves each requested sub-field of that object — title directly, and artist, whose resolver returns an artist object that the engine resolves one level further into name. Each box is a resolver call, and each arrow is a parent value flowing into a child.
What this module covers
Section titled “What this module covers”By the end of these five lessons you will be able to write queries by hand and write the resolvers that answer them. The lessons are:
- Queries & Resolvers (you are here) — the read/produce split, the Query root, and the resolver chain.
- Writing Queries — fields, nested selection sets, and operation names.
- Resolver Functions — the four arguments every resolver receives and how default resolvers work.
- Arguments & Variables — field arguments and reusable query variables like
$id. - Fragments & Aliases — reusing selection sets and renaming fields in the response.
See the chain run
Section titled “See the chain run”The example below defines a Query with one entry point, track, whose resolver returns a track object. The engine then resolves the track’s artist field, and the artist’s name. Press Run and watch one query travel the whole chain.
The output mirrors the query: track holds a title and a nested artist, and artist holds a name. Nothing in the response is there by accident — each value was produced by a resolver the engine called as it walked the chain from the Query root down to the leaves.