website/pages/docs/resolver-anatomy.mdx
In GraphQL.js, a resolver is a function that returns the value for a specific field in a schema. Resolvers connect a GraphQL query to the underlying data or logic needed to fulfill it.
This guide breaks down the anatomy of a resolver, how GraphQL.js uses them during query execution, and best practices for writing them effectively.
A resolver is responsible for returning the value for a specific field in a GraphQL query. During execution, GraphQL.js calls a resolver for each field, either using a custom function you provide or falling back to a default behavior.
If no resolver is provided, GraphQL.js tries to retrieve a property from the parent object that matches the field name. If the property is a function, it calls the function and uses the result. Otherwise, it returns the property value directly.
You can think of a resolver as a translator between the schema and the actual data. The schema defines what can be queried, while resolvers determine how to fetch or compute the data at runtime.
When GraphQL.js executes a resolver, it calls the resolver function with four arguments:
function resolve(source, args, context, info) { ... }
Each argument provides information that can help the resolver return the correct value:
source: The result from the parent field's resolver. In nested fields,
source contains the value returned by the parent object (after resolving any
lists). For root fields, it is the rootValue passed to GraphQL, which is often
left undefined.args: An object containing the arguments passed to the field in the
query. For example, if a field is defined to accept an id argument, you can
access it as args.id.context: A shared object available to every resolver in an operation.
It is commonly used to store per-request state like authentication
information, database connections, or caching utilities.info: Information about the current execution state, including
the field name, path to the field from the root, the return type, the parent
type, and the full schema. It is mainly useful for advanced scenarios such
as query optimization or logging.Resolvers can use any combination of these arguments, depending on the needs of the field they are resolving.
If you do not provide a resolver for a field, GraphQL.js uses a built-in
default resolver called defaultFieldResolver.
The default behavior is simple:
source object that matches the name of
the field.This default resolution makes it easy to build simple schemas without
writing custom resolvers for every field. For example, if your source object
already contains fields with matching names, GraphQL.js can resolve them
automatically.
You can override the default behavior by specifying a resolve function when
defining a field in the schema. This is necessary when the field’s value
needs to be computed dynamically, fetched from an external service, or
otherwise requires custom logic.
A custom resolver is a function you define to control exactly how a field's
value is fetched or computed. You can add a resolver by specifying a resolve
function when defining a field in your schema:
const UserType = new GraphQLObjectType({
name: 'User',
fields: {
fullName: {
type: GraphQLString,
resolve(source) {
return `${source.firstName} ${source.lastName}`;
},
},
},
});
Resolvers can be synchronous or asynchronous. If a resolver returns a Promise, GraphQL.js automatically waits for the Promise to resolve before continuing execution:
resolve(source, args, context) {
return database.getUserById(args.id);
}
Custom resolvers are often used to implement patterns such as batching, caching, or delegation. For example, a resolver might use a batching utility like DataLoader to fetch multiple related records efficiently, or delegate part of the query to another API or service.
When writing resolvers, it's important to keep them focused and maintainable:
null values
when fields are nullable. Avoid letting unhandled errors crash the server.context object rather
than passing it manually between resolvers.Following these practices helps keep your GraphQL server reliable, efficient, and easy to maintain as your schema grows.