GraphQL path · Execution · Topic 3 of 12

Resolvers and the execution model

Not started

When a GraphQL request arrives, the server does three things in order. It **parses** the document into a syntax tree, **validates** that tree against the schema (do these fields exist, do the argument types match, are the fragments well formed), and then **executes** the chosen operation. Execution walks the selection set from the root type downwards. For each field it calls that field's resolver, gets a value, and if the field's type is an object or a list of objects, repeats the process for the sub-selection with that value as the parent. The response is assembled in the same shape as the query as those calls complete.

Why it matters: Every GraphQL performance problem and most of its security model follow from one rule: the server resolves the query field by field, passing each result down to the fields below it. See that once and N+1, auth and cost limits make sense.

  1. 1Three phases

    The server parses the document, validates it against the schema, and only then executes it. Errors in the first two return no `data` at all.

  2. 2A function per field

    Each field has a resolver `(parent, args, context, info)`. Fields without one use a default that reads the property of the same name from the parent.

  3. 3Parent before child

    A field's resolver runs only after its parent's value exists, and receives it as `parent`. Siblings can run concurrently.

  4. 4Context is per request

    The `context` object is created for each request and passed to every resolver: the place for the current user, database handles and loaders.

1

How it works

The explanation, with examples you can run.

Resolvers

A resolver is a function (parent, args, context, info) => value | Promise<value>:

  • parent: the value the parent field resolved to (for root fields, a root value the server provides).
  • args: the field's arguments, already coerced to their declared types.
  • context: one object per request, shared by every resolver in it.
  • info: the field's position in the query, its return type and the selection below it.

Most fields don't need a resolver. The default one returns parent[fieldName], so if Query.posts returns objects with a title property, Post.title just works. You write resolvers for root fields, for relationships (Post.author given authorId), and for computed values.

Watching it run

⌘/Ctrl + Enter
OutputNot run yet

Press Run to see what your code prints, in order.

Runs in this browser, in an isolated frame with no access to this page. console.log, info, warn and error are shown; code runs as a classic script, so use an async function for await.

Post.author runs once per post, after Query.posts has produced the list and with each post as its parent. User.name then uses the default resolver. The list items are completed concurrently, which is why the lines for the two posts interleave. A real server such as graphql-js does the same walk, plus argument coercion, abstract type resolution, error handling and null checks.

Ordering

The spec lets a server resolve sibling fields of a query in any order, including in parallel; only top-level mutation fields must run serially. Children always wait for their parent's value. That makes the cost of a query depend on its shape: a list field multiplies the work of everything selected below it, and nothing in the execution model batches those calls for you. The next topic is about doing exactly that.

2

Check yourself

3 questions. Get them all right to finish this topic.

  1. 1.For `{ posts { author { name } } }` with 10 posts, how many times does the `Post.author` resolver run?
  2. 2.`Post.title` has no resolver. What does the server return for it?
  3. 3.Where should a resolver find the logged-in user?

0 of 3 right so far. Get all three to finish this topic.