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:
1type Query {
2 numberSix: Int! # Should always return the number 6 when queried
3 numberSeven: Int! # Should always return 7
4}
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:
1const resolvers = {
2 Query: {
3 numberSix() {
4 return 6;
5 },
6 numberSeven() {
7 return 7;
8 }
9 }
10};
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:
1type User {
2 id: ID!
3 name: String
4}
5
6type Query {
7 user(id: ID!): User
8}
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:
1const users = [
2 {
3 id: '1',
4 name: 'Elizabeth Bennet'
5 },
6 {
7 id: '2',
8 name: 'Fitzwilliam Darcy'
9 }
10];
To learn how to fetch data from an external source (like a database or REST API), see Data sources .
Now we can define a resolver for the user
field, like so:
1const resolvers = {
2 Query: {
3 user(parent, args, context, info) {
4 return users.find(user => user.id === args.id);
5 }
6 }
7}
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
andname
). 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 theuser
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.
Expand example
1const { ApolloServer, gql } = require('apollo-server');
2const {
3 ApolloServerPluginLandingPageLocalDefault,
4} = require('apollo-server-core');
5
6// Hardcoded data store
7const books = [
8 {
9 title: 'The Awakening',
10 author: 'Kate Chopin',
11 },
12 {
13 title: 'City of Glass',
14 author: 'Paul Auster',
15 },
16];
17
18// Schema definition
19const typeDefs = gql`
20 type Book {
21 title: String
22 author: String
23 }
24
25 type Query {
26 books: [Book]
27 }
28`;
29
30// Resolver map
31const resolvers = {
32 Query: {
33 books() {
34 return books;
35 }
36 },
37};
38
39// Pass schema definition and resolvers to the
40// ApolloServer constructor
41const server = new ApolloServer({
42 typeDefs,
43 resolvers,
44 csrfPrevention: true,
45 cache: 'bounded',
46 plugins: [
47 ApolloServerPluginLandingPageLocalDefault({ embed: true }),
48 ],
49});
50
51// Launch the server
52server.listen().then(({ url }) => {
53 console.log(`🚀 Server ready at ${url}`);
54});
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.
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":
1type Product {
2 id: ID!
3 name: String
4 variants: [String!]
5 availability: Availability!
6}
7
8enum Availability {
9 AVAILABLE
10 DISCONTINUED
11}
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:
1# A library has a branch and books
2type Library {
3 branch: String!
4 books: [Book!]
5}
6
7# A book has a title and author
8type Book {
9 title: String!
10 author: Author!
11}
12
13# An author has a name
14type Author {
15 name: String!
16}
17
18type Query {
19 libraries: [Library]
20}
Here's a valid query against that schema:
1query GetBooksByLibrary {
2 libraries {
3 books {
4 author {
5 name
6 }
7 }
8 }
9}
The resulting resolver chain for this query matches the hierarchical structure of the query itself:
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:
Expand example
1const { ApolloServer, gql } = require('apollo-server');
2const {
3 ApolloServerPluginLandingPageLocalDefault,
4} = require('apollo-server-core');
5
6const libraries = [
7 {
8 branch: 'downtown'
9 },
10 {
11 branch: 'riverside'
12 },
13];
14
15// The branch field of a book indicates which library has it in stock
16const books = [
17 {
18 title: 'The Awakening',
19 author: 'Kate Chopin',
20 branch: 'riverside'
21 },
22 {
23 title: 'City of Glass',
24 author: 'Paul Auster',
25 branch: 'downtown'
26 },
27];
28
29// Schema definition
30const typeDefs = gql`
31
32# A library has a branch and books
33 type Library {
34 branch: String!
35 books: [Book!]
36 }
37
38 # A book has a title and author
39 type Book {
40 title: String!
41 author: Author!
42 }
43
44 # An author has a name
45 type Author {
46 name: String!
47 }
48
49 # Queries can fetch a list of libraries
50 type Query {
51 libraries: [Library]
52 }
53`;
54
55// Resolver map
56const resolvers = {
57 Query: {
58 libraries() {
59
60 // Return our hardcoded array of libraries
61 return libraries;
62 }
63 },
64 Library: {
65 books(parent) {
66
67 // Filter the hardcoded array of books to only include
68 // books that are located at the correct branch
69 return books.filter(book => book.branch === parent.branch);
70 }
71 },
72 Book: {
73
74 // The parent resolver (Library.books) returns an object with the
75 // author's name in the "author" field. Return a JSON object containing
76 // the name, because this field expects an object.
77 author(parent) {
78 return {
79 name: parent.author
80 };
81 }
82 }
83
84 // Because Book.author returns an object with a "name" field,
85 // Apollo Server's default resolver for Author.name will work.
86 // We don't need to define one.
87};
88
89// Pass schema definition and resolvers to the
90// ApolloServer constructor
91const server = new ApolloServer({
92 typeDefs,
93 resolvers,
94 csrfPrevention: true,
95 cache: 'bounded',
96 plugins: [
97 ApolloServerPluginLandingPageLocalDefault({ embed: true }),
98 ],
99});
100
101// Launch the server
102server.listen().then(({ url }) => {
103 console.log(`🚀 Server ready at ${url}`);
104});
If we now update our query to also ask for each book's title
:
1query GetBooksByLibrary {
2 libraries {
3 books {
4 title
5 author {
6 name
7 }
8 }
9 }
10}
Then the resolver chain looks like this:
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 orsource
.
Argument | Description |
---|---|
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 |
args | An object that contains all GraphQL arguments provided for this field. For example, when executing |
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 |
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 |
The context
argument
The context
argument is useful for passing things that any resolver might need, like authentication scope , 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 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).
1
2// Constructor
3const server = new ApolloServer({
4 typeDefs,
5 resolvers,
6 csrfPrevention: true,
7 cache: 'bounded',
8 context: ({ req }) => ({
9 authScope: getScope(req.headers.authorization)
10 }),
11 plugins: [
12 ApolloServerPluginLandingPageLocalDefault({ embed: true }),
13 ],
14});
15
16// Example resolver
17(parent, args, context, info) => {
18 if(context.authScope !== ADMIN) throw new AuthenticationError('not admin');
19 // Proceed
20}
This example assumes you're using either the
apollo-server
orapollo-server-express
package, both of which use Express. The fields of the object passed to yourcontext
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 operations to complete:
1context: async () => ({
2 db: await client.connect(),
3})
4
5// Resolver
6(parent, args, context, info) => {
7 return context.db.query('SELECT * FROM table_name');
8}
Return values
A resolver function's return value is treated differently by Apollo Server depending on its type:
Type | Description |
---|---|
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 |
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 If this resolver's field is not nullable, Apollo Server sets the field's parent to |
Promise | Resolvers 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 resolver for a particular schema field, Apollo Server defines a default resolver for it.
The default resolver function uses the following logic:
As an example, consider the following schema excerpt:
1type Book {
2 title: String
3}
4
5type Author {
6 books: [Book]
7}
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 .