GraphQL - Resolver
GraphQL - Resolvers
Section titled “GraphQL - Resolvers”Resolvers are the heart of a GraphQL server’s execution logic. They are functions responsible for fetching the data for a specific field in your schema. When a GraphQL query arrives, the server traverses the query field by field, executing the corresponding resolver function for each field to produce the final result.
Think of your schema as defining the structure and types of your API, and resolvers as providing the implementation – the code that actually gets or computes the data.
Resolver Function Signature
Section titled “Resolver Function Signature”Every resolver function receives four arguments:
fieldName(parent, args, context, info) { /* ... return data ... */ }Let’s break down these arguments:
| Argument | Description | Common Usage |
|---|---|---|
parent (or root) | The result object returned from the resolver of the parent field. For top-level fields in a Query or Mutation, this argument is often undefined or the root value configured in the server. | Accessing data from the parent object to resolve nested fields (e.g., getting userId from a post object to resolve the author field). |
args | An object containing the arguments passed to the field in the GraphQL query. If a field takes no arguments, this object will be empty. | Fetching specific data based on ID (args.id), filtering lists (args.filter), providing input data for mutations (args.input). |
context | An object shared across all resolvers for a single GraphQL operation (query/mutation). It’s created once per request and is useful for passing request-scoped information. | Accessing authentication data (e.g., context.user), database connections or models (context.db), data loaders for batching (context.loaders). |
info | An object containing information about the execution state of the query, including the field name, return type, path to the field from the root, and the query’s Abstract Syntax Tree (AST). | Advanced use cases like implementing look-ahead to optimize database queries based on requested fields, or for complex authorization logic. |
Often, you won’t need all four arguments. It’s common practice to use underscores (_) for arguments you don’t intend to use, e.g., greeting(_, __, context) if you only need the context.
Resolver Map Structure
Section titled “Resolver Map Structure”Resolvers are typically organized in a JavaScript object (often called a ‘resolver map’) whose structure mirrors the GraphQL schema.
// Example Schema Snippettype Query { greeting: String students: [Student] studentById(id: ID!): Student}
type Student { id: ID! firstName: String lastName: String # Field resolved by a specific Student resolver fullName: String}
// Corresponding Resolver Map Structureconst resolvers = { Query: { // Resolver for Query.greeting greeting: (parent, args, context, info) => { /* return greeting string */ },
// Resolver for Query.students students: (parent, args, context, info) => { /* return list of students */ },
// Resolver for Query.studentById studentById: (parent, args, context, info) => { /* find and return student by args.id */ }, },
Student: { // Resolver for Student.fullName // This runs for each Student object being returned fullName: (parent, args, context, info) => { // 'parent' here is the individual student object return `${parent.firstName} ${parent.lastName}`; }, // id, firstName, lastName often don't need resolvers if the parent object // returned by the Query resolver already has these properties with matching names. // GraphQL provides default resolvers for simple property access. },
// ... resolvers for Mutation, Subscription types if defined};Default Resolvers: If a field resolver is not explicitly provided, GraphQL often uses a default resolver. This default resolver typically looks for a property with the same name on the parent object or calls a function with the same name on the parent object.
Resolver Return Values
Section titled “Resolver Return Values”Resolvers can return:
- Scalar Values: Strings, Numbers, Booleans matching the schema type.
- Objects: Plain JavaScript objects whose properties correspond to the fields of an Object type in the schema.
- Arrays: Arrays of values or objects, if the schema field type is a List (
[]). - Promises: Resolvers often perform asynchronous operations (like database calls). Returning a Promise allows GraphQL to wait for the operation to complete before continuing.
- Null: If the data cannot be found or an error occurs (and the field is nullable in the schema). If a resolver for a non-nullable field returns null, GraphQL will trigger an error.
- Errors: Throwing an error (especially
GraphQLError) signals an issue during resolution.
Illustration: Student Resolvers
Section titled “Illustration: Student Resolvers”Let’s create resolvers for a schema that fetches student data, including resolving a specific student by ID.
Step 1: Server & Data Setup
Section titled “Step 1: Server & Data Setup”Assume a basic Apollo Server setup and a mock data source (db.js).
// db.js (Mock Data - Example)const students = [ { id: 'S1001', firstName: 'Mohtashim', lastName: 'Mohammad', collegeId: 'col-102' }, { id: 'S1002', firstName: 'Kannan', lastName: 'Sudhakaran', collegeId: 'col-101' },];
export const db = { students: { list: () => students, get: (id) => students.find(s => s.id === id), }};Step 2: Define Schema (schema.graphql or in server.js)
Section titled “Step 2: Define Schema (schema.graphql or in server.js)”#graphqltype Query { greeting: String students: [Student] # Returns a list of Student objects studentById(id: ID!): Student # Takes a non-null ID, returns a single Student}
type Student { id: ID! firstName: String lastName: String collegeId: String # Assuming this is just data for now}Step 3: Create Resolvers (resolvers.js)
Section titled “Step 3: Create Resolvers (resolvers.js)”Implement the resolver functions corresponding to the schema.
// resolvers.jsimport { db } from './db.js'; // Assuming db.js uses export
const resolvers = { Query: { // Resolver for Query.greeting // No arguments needed here beyond the implicit ones greeting: () => 'Hello from TutorialsPoint!',
// Resolver for Query.students // Fetches the list from our mock DB students: () => { console.log('Resolving: Query.students'); return db.students.list(); },
// Resolver for Query.studentById // Uses the 'args' parameter to get the ID studentById: (parent, args, context, info) => { console.log(`Resolving: Query.studentById with id: ${args.id}`); // The 'id' argument is available in args.id const student = db.students.get(args.id); if (!student) { // Optional: Throw an error if student not found, or return null // throw new GraphQLError('Student not found', { extensions: { code: 'NOT_FOUND' } }); return null; // If schema allows null } return student; }, },
// No 'Student' resolvers needed here yet, as 'id', 'firstName', 'lastName', 'collegeId' // directly match properties on the objects returned by Query resolvers. // GraphQL's default resolver handles this.};
export default resolvers;Key points:
- The
greetingresolver simply returns a static string. - The
studentsresolver callsdb.students.list()to get the array. - The
studentByIdresolver accesses theidpassed in the query viaargs.idand uses it to calldb.students.get(). It handles the case where a student might not be found. - We didn’t need to define resolvers for
Student.id,Student.firstName, etc., because the objects returned bystudents()andstudentById()already have properties with these exact names. GraphQL applies default resolvers in these cases.
Step 4: Run and Test
Section titled “Step 4: Run and Test”Integrate the schema and resolvers with your Apollo Server setup (server.js) and run the server (npm start).
Use Apollo Sandbox (http://localhost:9000/graphql) to test:
# Test Query 1: Get greeting and all studentsquery GetGreetingAndStudents { greeting students { id firstName }}
# Test Query 2: Get specific studentquery GetSpecificStudent { studentById(id: "S1001") { id firstName lastName collegeId }}
# Test Query 3: Get non-existent studentquery GetMissingStudent { studentById(id: "S9999") { id firstName }}Observe the responses and the console logs from the resolvers to see when they are executed.
Resolver Chain
Section titled “Resolver Chain”For nested queries, resolvers execute in a chain. The parent argument connects this chain. For example, if you queried studentById { college { name } }, the Query.studentById resolver would run first, returning a student object. Then, the Student.college resolver would run, receiving that student object as its parent argument.