Skip to content

GraphQL - Resolver

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.

Every resolver function receives four arguments:

fieldName(parent, args, context, info) { /* ... return data ... */ }

Let’s break down these arguments:

ArgumentDescriptionCommon 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).
argsAn 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).
contextAn 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).
infoAn 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.

Resolvers are typically organized in a JavaScript object (often called a ‘resolver map’) whose structure mirrors the GraphQL schema.

// Example Schema Snippet
type 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 Structure
const 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.

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.

Let’s create resolvers for a schema that fetches student data, including resolving a specific student by ID.

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)”
#graphql
type 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
}

Implement the resolver functions corresponding to the schema.

// resolvers.js
import { 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 greeting resolver simply returns a static string.
  • The students resolver calls db.students.list() to get the array.
  • The studentById resolver accesses the id passed in the query via args.id and uses it to call db.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 by students() and studentById() already have properties with these exact names. GraphQL applies default resolvers in these cases.

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 students
query GetGreetingAndStudents {
greeting
students {
id
firstName
}
}
# Test Query 2: Get specific student
query GetSpecificStudent {
studentById(id: "S1001") {
id
firstName
lastName
collegeId
}
}
# Test Query 3: Get non-existent student
query GetMissingStudent {
studentById(id: "S9999") {
id
firstName
}
}

Observe the responses and the console logs from the resolvers to see when they are executed.

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.