GraphQL Summit is back for three days of insights, hands-on learning, and fun to celebrate the GraphQL community. Join us in San Diego Oct 3-5.
Docs
Try Apollo Studio

Resolvers

How Apollo Server processes GraphQL operations


Apollo Server needs to know how to populate data for every field in your schema so that it can respond to requests for that data. To accomplish this, it uses resolvers.

A resolver is a function that's responsible for populating the data for a single field in your schema. It can populate that data in any way you define, such as by fetching data from a back-end database or a third-party API.

If you don't define a resolver for a particular field, Apollo Server automatically defines a default resolver for it.

Defining a resolver

Base syntax

Let's say our server defines the following (very short) schema:

type Query {
numberSix: Int! # Should always return the number 6 when queried
numberSeven: Int! # Should always return 7
}

We want to define resolvers for the numberSix and numberSeven fields of the root Query type so that they always return 6 and 7 when they're queried.

Those resolver definitions look like this:

const resolvers = {
Query: {
numberSix() {
return 6;
},
numberSeven() {
return 7;
},
},
};

As this example shows:

  • You define all of your server's resolvers in a single JavaScript object (named resolvers above). This object is called the resolver map.
  • The resolver map has top-level fields that correspond to your schema's types (such as Query above).
  • Each resolver function belongs to whichever type its corresponding field belongs to.

Handling arguments

Now let's say our server defines this schema:

type User {
id: ID!
name: String
}
type Query {
user(id: ID!): User
}

We want to be able to query the user field to fetch a user by its id.

To achieve this, our server needs access to user data. For this contrived example, assume our server defines the following hardcoded array:

const users = [
{
id: '1',
name: 'Elizabeth Bennet',
},
{
id: '2',
name: 'Fitzwilliam Darcy',
},
];

Now we can define a resolver for the user field, like so:

const resolvers = {
Query: {
user(parent, args, context, info) {
return users.find((user) => user.id === args.id);
},
},
};

As this example shows:

  • A resolver can optionally accept four positional arguments: (parent, args, context, info).
  • The args argument is an object that contains all GraphQL arguments that were provided for the field by the GraphQL operation.

Notice that this example doesn't define resolvers for User fields (id and name). That's because the default resolver that Apollo Server creates for these fields does the right thing: it obtains the value directly from the object returned by the user resolver.

Passing resolvers to Apollo Server

After you define all of your resolvers, you pass them to the constructor of ApolloServer (as the resolvers property), along with your schema's definition (as the typeDefs property).

The following example defines a hardcoded data set, a schema, and a resolver map. It then initializes an ApolloServer instance, passing the schema and resolvers to it.

Note that you can define your resolvers across as many different files and objects as you want, as long as you merge all of them into a single resolver map that's passed to the ApolloServer constructor.

To learn how to fetch data from a REST API, see Fetching from REST.

Resolver chains

Whenever a query asks for a field that returns an object type, the query also asks for at least one field of that object (if it didn't, there would be no reason to include the object in the query). A query always "bottoms out" on fields that return a scalar, an enum, or a list of these.

For example, all fields of this Product type "bottom out":

type Product {
id: ID!
name: String
variants: [String!]
availability: Availability!
}
enum Availability {
AVAILABLE
DISCONTINUED
}

Because of this rule, whenever Apollo Server resolves a field that returns an object type, it always then resolves one or more fields of that object. Those subfields might in turn also contain object types. Depending on your schema, this object-field pattern can continue to an arbitrary depth, creating what's called a resolver chain.

Example

Let's say our server defines the following schema:

# A library has a branch and books
type Library {
branch: String!
books: [Book!]
}
# A book has a title and author
type Book {
title: String!
author: Author!
}
# An author has a name
type Author {
name: String!
}
type Query {
libraries: [Library]
}

Here's a valid query against that schema:

query GetBooksByLibrary {
libraries {
books {
author {
name
}
}
}
}

The resulting resolver chain for this query matches the hierarchical structure of the query itself:

Query.libraries()
Library.books()
Book.author()
Author.name()

These resolvers execute in the order shown above, and they each pass their return value to the next resolver in the chain via the parent argument.

Here's a code sample that can resolve the query above with this resolver chain:

If we now update our query to also ask for each book's title:

query GetBooksByLibrary {
libraries {
books {
title
author {
name
}
}
}
}

Then the resolver chain looks like this:

Query.libraries()
Library.books()
Book.title()
Book.author()
Author.name()

When a chain "diverges" like this, each subchain executes in parallel.

Resolver arguments

Resolver functions are passed four arguments: parent, args, context, and info (in that order).

You can use any name for each argument in your code, but the Apollo docs use these names as a convention. Instead of parent, it's also common to use the parent type's name or source.

ArgumentDescription
parent

The return value of the resolver for this field's parent (i.e., the previous resolver in the resolver chain).

For resolvers of top-level fields with no parent (such as fields of Query), this value is obtained from the rootValue function passed to Apollo Server's constructor.

args

An object that contains all GraphQL arguments provided for this field.

For example, when executing query{ user(id: "4") }, the args object passed to the user resolver is { "id": "4" }.

context

An object shared across all resolvers that are executing for a particular operation. Use this to share per-operation state, including authentication information, dataloader instances, and anything else to track across resolvers.

See The context argument for more information.

info

Contains information about the operation's execution state, including the field name, the path to the field from the root, and more.

Its core fields are listed in the GraphQL.js source code. Apollo Server extends it with a cacheControl field.

The context argument

The context argument is useful for passing things that any resolver might need, like authentication scope, sources for fetching data, database connections, and custom fetch functions. If you're using dataloaders to batch requests across resolvers, you can attach them to the context as well.

Resolvers should never destructively modify the context argument. This ensures consistency across all resolvers and prevents unexpected errors.

To provide an initial context to your resolvers, add an asynchronous context initialization function to your integration function (e.g., expressMiddleware or startStandaloneServer). This function is called with every request, so you can customize the context based on each request's details (such as HTTP headers).

import { GraphQLError } from 'graphql';
const resolvers = {
Query: {
// Example resolver
adminExample: (parent, args, context, info) => {
if (context.authScope !== ADMIN) {
throw new GraphQLError('not admin!', {
extensions: { code: 'UNAUTHENTICATED' }
});
}
},
},
};
interface MyContext { // Context typing
authScope?: String;
}
const server = new ApolloServer<MyContext>({
typeDefs,
resolvers,
});
const { url } = await startStandaloneServer(server, {
// A named context function is required if you are not
// using ApolloServer<BaseContext>
context: async ({req, res}) => ({
authScope: getScope(req.headers.authorization),
})
});
import { GraphQLError } from 'graphql';
const resolvers = {
Query: {
// Example resolver
adminExample: (parent, args, context, info) => {
if (context.authScope !== ADMIN) {
throw new GraphQLError('not admin!', {
extensions: { code: 'UNAUTHENTICATED' },
});
}
},
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
});
const { url } = await startStandaloneServer(server, {
// A named context function is required if you are not
// using ApolloServer<BaseContext>
context: async ({ req, res }) => ({
authScope: getScope(req.headers.authorization),
}),
});

This example assumes you're using either the startStandaloneServer or expressMiddleware, both of which use Express under the hood. The fields of the object passed to your context function might differ if you're using a different integration.

The context function should be asynchronous and return an object, which is then accessible to your server's resolvers and plugins (via the contextValue field).

Because the context initialization function is asynchronous, you can use it to establish database connections and wait for other operations to complete:

context: async () => ({
db: await client.connect(),
})
// Resolver
(parent, args, context, info) => {
return context.db.query('SELECT * FROM table_name');
}

Throwing errors in context

By default, if your context function throws an error, Apollo Server returns that error to the user in a JSON response with an HTTP status code of 500. If the thrown error is not a GraphQLError, the error's message will be prepended with "Context creation failed: ".

You can control the HTTP status code of an error by throwing a GraphQLError with an http extension. For example:

context: async ({ req }) => {
const user = await getUserFromReq(req);
if (!user) {
throw new GraphQLError('User is not authenticated', {
extensions: {
code: 'UNAUTHENTICATED',
http: { status: 401 },
}
});
}
// If this throws a non-GraphQLError, it will be rendered with
// `code: "INTERNAL_SERVER_ERROR"` and HTTP status code 500, and
// with a message starting with "Context creation failed: ".
const db = await getDatabaseConnection();
return { user, db };
},

Return values

A resolver function's return value is treated differently by Apollo Server depending on its type:

TypeDescription
Scalar / object

A resolver can return a single value or an object, as shown in Defining a resolver. This return value is passed down to any nested resolvers via the parent argument.

Array

Return an array if and only if your schema indicates that the resolver's associated field contains a list.

After you return an array, Apollo Server executes nested resolvers for each item in the array.

null / undefined

Indicates that the value for the field could not be found.

If your schema indicates that this resolver's field is nullable, then the operation result has a null value at the field's position.

If this resolver's field is not nullable, Apollo Server sets the field's parent to null. If necessary, this process continues up the resolver chain until it reaches a field that is nullable. This ensures that a response never includes a null value for a non-nullable field. When this happens, the response's errors property will be populated with relevant errors concerning the nullability of that field.

Promise

Resolvers can be asynchronous and perform async actions, such as fetching from a database or back-end API. To support this, a resolver can return a promise that resolves to any other supported return type.

Default resolvers

If you don't define a resolver for a particular schema field, Apollo Server defines a default resolver for it.

The default resolver function uses the following logic:

No
Yes
No
Yes
Does the parent argument have a
property with this resolver's exact name?
Return undefined
Is that property's value a function?
Return the property's value
Call the function and
return its return value

As an example, consider the following schema excerpt:

type Book {
title: String
}
type Author {
books: [Book]
}

If the resolver for the books field returns an array of objects that each contain a title field, then you can use a default resolver for the title field. The default resolver will correctly return parent.title.

Resolving unions and interfaces

There are GraphQL types that enable you to define a field that returns one of multiple possible object types (i.e., unions and interfaces). To resolve a field that can return different object types, you must define a __resolveType function to inform Apollo Server which type of object is being returned.

Resolving federated entities

See Resolving Entities.

Monitoring resolver performance

As with all code, a resolver's performance depends on its logic. It's important to understand which of your schema's fields are computationally expensive or otherwise slow to resolve, so that you can either improve their performance or make sure you only query them when necessary.

Apollo Studio integrates directly with Apollo Server to provide field-level metrics that help you understand the performance of your graph over time. For more information, see Analyzing performance.

Edit on GitHub
Previous
Directives
Next
Fetching from REST