Fundamentals

Resolvers

How resolver functions produce a field's value, and what they can return.

A resolver is the function that produces a field's value. When a field appears in a query, the graphql-js executor calls that field's resolver and expects back a value matching the field's type. The t.expose* helpers from the Fields guide write a resolver for you that reads a property off the backing object; this guide is about writing the resolve function yourself.

characterCount: t.int({
  resolve: () => characters.length,
}),

characterCount is an Int field whose resolver returns a number. The executor runs it each time the field is selected in a query, and the returned value becomes the field's value in the response.

The four arguments

The executor calls a resolver with four positional arguments, (parent, args, context, info):

character: t.field({
  type: Character,
  nullable: true,
  args: { id: t.arg.id({ required: true }) },
  resolve: (parent, args) =>
    characters.find((character) => character.id === args.id) ?? null,
}),
  • parent is the value the field's parent resolver returned, which is the backing model for that type (covered in Object types). On a field of the root Query, Mutation, or Subscription type there is no parent object, so parent is the rootValue the server passed to the executor, usually undefined.
  • args is the field's arguments, already coerced and type-checked against the field's args definition. The Arguments guide covers declaring them.
  • context is the per-request context: the object your server builds for each request, where values like the authenticated ctx.user and database handles live.
  • info is information about the current query and field selection, which plugins like Prisma and Drizzle read to plan a single database query for the fields a request selected.

Most resolvers only need one or two of these, and since the parameters are positional you can leave off any trailing ones you don't use.

What a resolver returns

A resolver returns a value matching the field's type, or a promise for one; the executor awaits the promise before continuing. A synchronous resolver returns its value directly, and an async resolver returns a promise for it.

featuredCharacter: t.field({
  type: Character,
  nullable: true,
  resolve: async () => {
    const character = await loadCharacter('1');
    return character ?? null;
  },
}),

The field's type decides what counts as a valid value, and Pothos checks it in TypeScript. A scalar field returns that scalar. A field returning an object type returns the backing model for that type rather than a GraphQL-shaped object, so you can hand back a database row or a plain object without reshaping it first. A list field accepts any iterable of those values, and a nullable field may also return null or undefined; character above returns null when no character matches the requested id.