Resolvers: serving a schema

GraphQL · Essentials · 15 min

Resolvers are where a GraphQL server actually fetches data.

A server pairs the schema with resolvers: one function per field that produces its value. Most JavaScript servers (Apollo Server, GraphQL Yoga, graphql-tools) use a resolver map keyed by type and field.

Each resolver receives four arguments:

  • parent: the value returned by the parent field's resolver (the book, when resolving Book.author).
  • args: the field's arguments, already validated and coerced.
  • context: a per-request object you create, holding the logged-in user, database handles and loaders.
  • info: details about the current field and query (used by advanced tooling).

A resolver may return a value or a promise; the executor awaits it. Fields without a resolver use the default resolver, which reads the property of the same name from parent, so you only write resolvers where the stored shape differs from the schema.

Execution walks the query top-down: resolve Query.book, then for that result resolve title and author, and so on. The result value is then completed: checked against the field type, with lists mapped and non-null rules enforced.

Keep resolvers thin: authorization checks and data access belong in a service layer that the resolver calls, so REST endpoints, jobs and GraphQL share the same rules.

Example

import { makeExecutableSchema } from "@graphql-tools/schema";
import { graphql } from "graphql";
const authors = [{ id: "a1", name: "Ursula K. Le Guin" }];
const books = [{ id: "b1", title: "The Dispossessed", authorId: "a1" }];
const typeDefs = `
  type Author { id: ID!  name: String! }
  type Book { id: ID!  title: String!  author: Author! }
  type Query { book(id: ID!): Book }
`;
const resolvers = {
  Query: {
    book: (_parent, { id }) => books.find((b) => b.id === id) ?? null,
  },
  Book: { // title uses the default resolver (book.title)
    author: (book) => authors.find((a) => a.id === book.authorId),
  },
};
const schema = makeExecutableSchema({ typeDefs, resolvers });
const res = await graphql({ schema, source: '{ book(id: "b1") { title author { name } } }' });
console.log(JSON.stringify(res.data));
// {"book":{"title":"The Dispossessed","author":{"name":"Ursula K. Le Guin"}}}

Remember

  • Resolver signature: (parent, args, context, info)
  • Return values or promises; the executor awaits them
  • Default resolver reads parent[fieldName]
  • Put per-request state (user, loaders) in context

Watch out for

  • Storing per-request data in module-level variables, leaking it between users.

Check yourself

  1. When resolving `Book.author`, what is `parent`?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: graphql.org/learn/execution/