diff --git a/website/pages/docs/_meta.ts b/website/pages/docs/_meta.ts index 39ac3a1486..6a4dbc36ca 100644 --- a/website/pages/docs/_meta.ts +++ b/website/pages/docs/_meta.ts @@ -19,6 +19,7 @@ const meta = { 'constructing-types': '', 'oneof-input-objects': '', 'defer-stream': '', + 'resolver-anatomy': '', '-- 3': { type: 'separator', title: 'FAQ', diff --git a/website/pages/docs/resolver-anatomy.mdx b/website/pages/docs/resolver-anatomy.mdx new file mode 100644 index 0000000000..8b7195790a --- /dev/null +++ b/website/pages/docs/resolver-anatomy.mdx @@ -0,0 +1,138 @@ +--- +title: Anatomy of a Resolver +--- + +# Anatomy of a Resolver + +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. + +## What is a resolver? + +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. + +## Resolver function signature + +When GraphQL.js executes a resolver, it calls the resolver function +with four arguments: + +```js +function resolver(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. For root fields, +it is often `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. + +## Default resolvers + +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: + +- It looks for a property on the `source` object that matches the name of +the field. +- If the property exists and is a function, it calls the function and uses the +result. +- Otherwise, it returns the property value directly. + +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. + +## Writing a custom resolver + +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: + +```js +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: + +```js +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. + +## Best practices + +When writing resolvers, it's important to keep them focused and maintainable: + +- Keep business logic separate. A resolver should focus on fetching or +computing the value for a field, not on implementing business rules or +complex workflows. Move business logic into separate service layers +whenever possible. +- Handle errors carefully. Resolvers should catch and handle errors +appropriately, either by throwing GraphQL errors or returning `null` values +when fields are nullable. Avoid letting unhandled errors crash the server. +- Use context effectively. Store shared per-request information, such as +authentication data or database connections, in the `context` object rather +than passing it manually between resolvers. +- Prefer batching over nested requests. For fields that trigger multiple +database or API calls, use batching strategies to minimize round trips and +improve performance. A common solution for batching in GraphQL is [dataloader](https://github.com/graphql/dataloader). +- Keep resolvers simple. Aim for resolvers to be small, composable functions +that are easy to read, test, and reuse. + +Following these practices helps keep your GraphQL server reliable, efficient, +and easy to maintain as your schema grows. \ No newline at end of file