GraphQL resolvers are the bridge between your GraphQL schema and your data source, in this case, MongoDB via Mongoose.

Let’s see how this actually looks. Imagine a simple GraphQL schema for a Book type:

type Book {
  id: ID!
  title: String!
  author: String!
  publishedYear: Int
}

type Query {
  books: [Book!]!
  book(id: ID!): Book
}

type Mutation {
  addBook(title: String!, author: String!, publishedYear: Int): Book!
}

Now, let’s define the Mongoose model for this Book:

const mongoose = require('mongoose');

const bookSchema = new mongoose.Schema({
  title: { type: String, required: true },
  author: { type: String, required: true },
  publishedYear: Number
});

const BookModel = mongoose.model('Book', bookSchema);

module.exports = BookModel;

The core of connecting them lies in the GraphQL resolver functions. These functions are responsible for fetching the data that matches the fields defined in your GraphQL schema.

Here’s how you’d write the resolvers for the Query and Mutation types above, using BookModel:

const BookModel = require('./models/Book'); // Assuming your model is in ./models/Book.js

const resolvers = {
  Query: {
    // Resolver for the 'books' query
    books: async () => {
      try {
        const allBooks = await BookModel.find({});
        return allBooks;
      } catch (error) {
        console.error("Error fetching all books:", error);
        throw new Error("Could not retrieve books.");
      }
    },
    // Resolver for the 'book' query
    book: async (_, { id }) => {
      try {
        const singleBook = await BookModel.findById(id);
        if (!singleBook) {
          throw new Error(`Book with ID ${id} not found.`);
        }
        return singleBook;
      } catch (error) {
        console.error(`Error fetching book with ID ${id}:`, error);
        throw new Error(`Could not retrieve book with ID ${id}.`);
      }
    }
  },
  Mutation: {
    // Resolver for the 'addBook' mutation
    addBook: async (_, { title, author, publishedYear }) => {
      try {
        const newBook = new BookModel({
          title,
          author,
          publishedYear
        });
        const savedBook = await newBook.save();
        return savedBook;
      } catch (error) {
        console.error("Error adding new book:", error);
        throw new Error("Could not add book.");
      }
    }
  }
};

module.exports = resolvers;

To make this work, you’d then combine your GraphQL schema and resolvers into a GraphQLSchema object, typically using the graphql library or a framework like Apollo Server.

const { GraphQLSchema, buildSchema } = require('graphql');
const resolvers = require('./resolvers'); // Assuming your resolvers are in ./resolvers.js

// Define your schema string (or load from a file)
const typeDefs = `
  type Book {
    id: ID!
    title: String!
    author: String!
    publishedYear: Int
  }

  type Query {
    books: [Book!]!
    book(id: ID!): Book
  }

  type Mutation {
    addBook(title: String!, author: String!, publishedYear: Int): Book!
  }
`;

const schema = buildSchema(typeDefs);

// This is a simplified example. In a real app, you'd likely use Apollo Server or similar
// which handles schema merging and resolver mapping more elegantly.
// For a basic setup, you'd pass schema and resolvers to your GraphQL execution engine.

// Example of how you might execute a query (this is usually handled by the server framework)
/*
const { graphql } = require('graphql');
graphql({ schema, source: '{ books { title } }', rootValue: resolvers }).then((response) => {
  console.log(response);
});
*/

// With Apollo Server, it would look something like this:
/*
const { ApolloServer } = require('apollo-server');
const server = new ApolloServer({ typeDefs, resolvers });
server.listen().then(({ url }) => {
  console.log(`🚀 Server ready at ${url}`);
});
*/

The Query.books resolver simply calls BookModel.find({}) to fetch all documents from the books collection. The Query.book resolver uses BookModel.findById(id) to retrieve a single book by its MongoDB _id. The Mutation.addBook resolver creates a new BookModel instance and saves it to the database. Notice how the arguments passed to the mutation (title, author, publishedYear) are directly used to construct the Mongoose document.

The _ in book: async (_, { id }) is a convention for the first argument of a resolver, which represents the parent object. For top-level queries and mutations, the parent is usually undefined. The second argument, { id }, is args – an object containing the arguments passed to the GraphQL field.

The real power here is that the GraphQL schema acts as a strict contract. Your resolvers must return data that conforms to this contract. Mongoose documents, when returned from resolvers, are automatically mapped to the GraphQL types because Mongoose schemas often align closely with the expected data shapes. The ID! type in GraphQL maps to Mongoose’s ObjectId (which _id is) and is handled seamlessly.

A common pitfall is forgetting to handle errors. In the resolvers above, try...catch blocks are used to wrap asynchronous Mongoose operations. This is crucial for providing meaningful error messages back to the client and for logging issues on the server. Without error handling, a failed database query could crash your server or return opaque, unhelpful errors.

When you define a Book type in your GraphQL schema and have a Mongoose BookModel with corresponding fields, GraphQL and Mongoose work together. If your Book schema has publishedYear: Int and your Mongoose schema has publishedYear: Number, Mongoose will happily convert the Number to an Int for GraphQL. The id: ID! field in GraphQL is implicitly mapped to Mongoose’s _id field by convention, and Mongoose’s ObjectId type is automatically serialized into a string for the ID GraphQL scalar.

The next step in a real-world application would be to integrate these resolvers with a GraphQL server framework like Apollo Server, which provides an HTTP endpoint to receive GraphQL queries and mutations, execute them against your schema and resolvers, and return the results.

Want structured learning?

Take the full Graphql-tools course →