Docs
Launch GraphOS Studio
Apollo Server 3 is officially deprecated, with end-of-life scheduled for 22 October 2024. Learn more about upgrading to a supported Apollo Server version.

Resolvers

How Apollo Server processes GraphQL operations


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

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 for a particular , 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 for the numberSix and numberSeven of the root Query type so that they always return 6 and 7 when they're queried.

Those definitions look like this:

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

As this example shows:

  • You define all of your server's in a single JavaScript object (named resolvers above). This object is called the resolver map.
  • The map has top-level that correspond to your schema's types (such as Query above).
  • Each function belongs to whichever type its corresponding 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 the user 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'
}
];

To learn how to fetch data from an external source (like a database or REST API), see Data sources.

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

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

As this example shows:

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

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

Passing resolvers to Apollo Server

After you define all of your , 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 map. It then initializes an ApolloServer instance, passing the schema and to it.

Note that you can define your 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.

Resolver chains

Whenever a asks for a that returns an , 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 ). A query always "bottoms out" on that return a scalar, an enum, or a list of these.

For example, all 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 resolves a that returns an , it always then resolves one or more fields of that object. Those subfields might in turn also contain . Depending on your schema, this object- 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 against that schema:

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

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

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

These 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 above with this chain:

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

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

Then the 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

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

You can use any name for each 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 for this 's parent (i.e., the previous resolver in the resolver chain).

For of top-level 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 provided for this .

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

context

An object shared across all that are executing for a particular . 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 's execution state, including the name, the path to the field from the root, and more.

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

The context argument

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

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

To provide an initial context to your , add a context initialization function to the ApolloServer constructor. This function is called with every request, so you can customize the context based on each request's details (such as HTTP headers).

// Constructor
const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true,
cache: 'bounded',
context: ({ req }) => ({
authScope: getScope(req.headers.authorization)
}),
plugins: [
ApolloServerPluginLandingPageLocalDefault({ embed: true }),
],
});
// Example resolver
(parent, args, context, info) => {
if(context.authScope !== ADMIN) throw new AuthenticationError('not admin');
// Proceed
}

This example assumes you're using either the apollo-server or apollo-server-express package, both of which use Express. The of the object passed to your context function might differ if you're using middleware besides Express. See the API reference for details.

For more information on middleware in general, see Choosing an Apollo Server package.

Context initialization can be asynchronous, allowing database connections and other to complete:

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

Return values

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

TypeDescription
Scalar / object

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

Array

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

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

null / undefined

Indicates that the value for the could not be found.

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

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

Promise

often perform asynchronous 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 for a particular schema , defines a default resolver for it.

The default function uses the following logic:

N
Yes
N
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 for the books returns an array of objects that each contain a title , then you can use a default for the title . The default will correctly return parent.title.

Resolving unions and interfaces

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

Resolving federated entities

See Resolving Entities.

Monitoring resolver performance

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

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

Previous
Creating directives
Next
Data sources
Edit on GitHubEditForumsDiscord

© 2024 Apollo Graph Inc.

Privacy Policy

Company