Skip to content

Resolvers in GraphQL — Complete Guide

DodaTech Updated 2026-06-28 3 min read

In this tutorial, you will learn about Resolvers in GraphQL. We cover key concepts, practical examples, and best practices to help you master this topic.

Resolvers are functions that provide values for schema fields. Each field in a GraphQL schema maps to a resolver function. The resolver receives the parent value, arguments, context, and an info object to compute the field value.

What You'll Learn

  • Resolver function signature (parent, args, context, info)
  • Field-level vs type-level resolve patterns
  • Using context for data sources and authentication
  • Resolver chaining and the default resolver behavior

Why It Matters

Resolvers are the implementation layer that connects your schema to actual data sources. Efficient resolver design determines API performance. Poor resolvers cause N+1 problems and slow response times.

Real-World Use

Apollo Server uses data sources that are added to context for Dependency Injection. Each resolver calls dataSource methods that handle Caching and deduplication internally.

flowchart TD
    Query[Root Query] --> ResolverA[Resolver: books]
    ResolverA --> DataSource[Data Source: BookAPI]
    DataSource --> DB[(Database)]
    ResolverA --> Book{Book type}
    Book --> ResolverB[Default Resolver: title]
    Book --> ResolverC[Resolver: author]
    ResolverC --> DataSource2[Data Source: AuthorAPI]

Teacher Mindset

Every resolver is a micro-function with one job: return the value for its field. Default resolvers automatically look up a property with the same name on the parent object. Only write explicit resolvers when you need custom logic.

Code Examples

// Example 1: Resolver signatures
const resolvers = {
  Query: {
    book: (parent, args, context, info) => {
      return context.dataSources.books.getById(args.id);
    },
    books: (parent, args, context) => {
      return context.dataSources.books.list();
    }
  },
  Book: {
    author: (parent, args, context) => {
      return context.dataSources.authors.getById(parent.authorId);
    }
  }
};
// Example 2: Context with data sources and auth
const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: async ({ req }) => {
    const token = req.headers.authorization || '';
    const user = await verifyToken(token);
    return {
      user,
      dataSources: {
        books: new BookAPI(),
        authors: new AuthorAPI()
      }
    };
  }
});
// Example 3: Batch resolver with DataLoader
const bookResolvers = {
  Query: {
    books: async (_, __, { dataLoaders }) => {
      return dataLoaders.books.loadMany(bookIds);
    }
  }
};

// Default resolver - no explicit function needed for simple fields
// Schema: type Book { id: ID! title: String! }
// The title field automatically reads parent.title

Common Mistakes

  • Writing resolvers that ignore the parent argument for relational data
  • Fetching data inside field resolvers without batching, causing N+1 queries
  • Not using the context argument for shared dependencies
  • Throwing generic errors instead of specific GraphQLError types
  • Forgetting that top-level fields on the Query type receive root as parent

Practice

  1. Write a resolver for Query.books that fetches from a REST API.
  2. Implement a field resolver for Book.author that uses the parent authorId.
  3. Create a context that provides authentication and data sources.
  4. Use args to filter results in a resolver function.
  5. Challenge: Implement a resolver chain three levels deep and trace the parent values at each level.

FAQ

What is the default resolver behavior?

If no resolver is defined for a field, GraphQL uses the default field resolver. It reads a property with the same name from the parent object.

What is the info argument used for?

The info object contains query execution details: field name, return type, parent type, path, and the AST of the query. It is rarely needed in application code.

Can a resolver return a Promise?

Yes. Resolvers can return a value directly, a Promise, or throw an error. GraphQL runtime awaits Promises and propagates errors.

How do I share logic across resolvers?

Add shared services to the context object. Every resolver accesses context to call data sources, utilities, or auth checks.

Should every field have its own resolver?

No. Only write resolvers for fields that need custom logic. Simple property access works via the default resolver.

Mini Project

Implement resolvers for your library schema. Write resolvers for Query.books, Query.book(id), Book.author, Mutation.createBook. Use context to pass a database client or in-memory data store.

What's Next

Next, you will learn about input types for structuring complex mutation arguments in GraphQL.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro