Skip to content

Resolver Functions

A resolver is just a function, but a function with a fixed, well-known signature. Once you know the four arguments it receives and the order they arrive in, you can write a resolver for any field on any type. This lesson opens that signature up, then traces how resolvers link into a chain.

Every resolver is called with the same four arguments, always in this order:

function resolve(parent, args, context, info) {
// ...return the field's value
}
  • parent — the value returned by the resolver one level up. For a top-level Query field there is no parent object, so this is the root value (often unused). For User.name, the parent is the User object that the field above resolved to.
  • args — an object holding the arguments passed to this field in the query. If the field is track(id: ID!), then args.id is the value the client supplied.
  • context — a value shared across every resolver in a single request. The server builds it once per request and hands the same object to each resolver. It is where you put per-request data: the authenticated user, a database connection, a logger.
  • info — metadata about the execution: the field name, the path from the root, the full query AST. You rarely need it day to day, but it is there for advanced cases.

The first two — parent and args — are the workhorses. parent tells a resolver what it is resolving a field of, and args tells it what the client asked for.

Resolvers do not run in isolation; they run in a chain driven by the query tree. The engine resolves a parent field, takes whatever that resolver returned, and passes it as the parent argument to each child field’s resolver. That single hand-off is what stitches the graph together.

flowchart TD
  Root["Query root value"] -->|"track resolver returns track"| T["track object = parent"]
  T -->|"artist resolver returns artist"| A["artist object = parent"]
  A -->|"name resolver reads parent.name"| N["name: String"]
The value each resolver returns becomes the parent argument of its children.

Follow the arrows: the track resolver returns a track object; the engine then calls the artist resolver with that track as its parent; the artist resolver returns an artist object; finally the name resolver is called with that artist as its parent and reads parent.name. The return value of one link is the parent of the next.

You do not have to write a resolver for every field. When a field has no explicit resolver, GraphQL falls back to a default resolver, which does the obvious thing: it looks at the parent object and returns the property with the same name as the field. So if the track resolver returns { id, title, artist }, the engine resolves title automatically by reading parent.title — no title resolver required.

This is why small schemas often only need resolvers on the Query root: the root resolver returns a fully populated object, and default resolvers walk it the rest of the way. You write explicit resolvers only where a field’s value needs computing, fetching, or reshaping.

The example below uses the full graphql building blocks so the resolver functions are written out explicitly, each showing its (parent, args) signature. The Query.track resolver builds a track; the Track.artist resolver receives that track as parent; the Artist.name resolver reads from its own parent. Press Run.

JavaScript

Notice that id and title have no explicit resolve function — the default resolver reads them straight off the parent object the track resolver returned. The artist resolver simply forwards parent.artist, which then becomes the parent for name. Each resolver only ever looks one level up and one field across.

In what order does a resolver receive its four arguments?
Where does the "parent" argument of a field resolver come from?
What does a default resolver do when a field has no explicit resolve function?