Node.js + MongoDB: Creating a GraphQL API

Not too long ago, I was setting up a basic GraphQL server with Node.js. It worked — queries returned responses, and the structure felt clean. But there was one thing missing: real data. Static arrays and hardcoded responses are great for learning, but eventually, I needed my server to talk to an actual database. That’s when MongoDB came into the picture.
In this post, I’m walking through how I connected a GraphQL API to MongoDB using Mongoose. It’s not complicated — once it clicks, it actually feels surprisingly intuitive. The goal here is to build a small but functional API for managing books. Something simple, but real enough to be useful.
Why MongoDB and GraphQL Make Sense Together
One thing I noticed early on with GraphQL is how well it mirrors the shape of the data you want. And MongoDB? It stores data in a format that already looks a lot like the shape of your queries. That’s a big deal — I didn’t have to fight the database to make GraphQL work. The two fit together naturally.
What We're Building
Here’s what this project covers:
A GraphQL API that can:
Fetch all books
Fetch a single book by ID
Add a new book
Update a book
Delete a book
MongoDB as the backend database
Mongoose to model and work with Mongo documents
By the end, it’ll be a full CRUD API you can query directly in GraphiQL or hook into a frontend app.
Setting Up the Project
I started with a blank Node.js project. After initializing, I installed the usual suspects:
npm init -y
npm install express express-graphql graphql mongoose dotenv
And just like that, the foundation was ready.
Connecting to MongoDB
I chose to use a local MongoDB instance, but MongoDB Atlas works too. I saved my connection string in a .env
file to keep things clean:
MONGO_URI=mongodb://localhost:27017/graphql_books
Then I created a simple connection file using Mongoose:
const mongoose = require('mongoose');
require('dotenv').config();
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGO_URI);
console.log('MongoDB connected');
} catch (error) {
console.error('Connection failed:', error.message);
process.exit(1);
}
};
module.exports = connectDB;
When this ran without error, I knew I was ready to define models.
Creating the Book Model
With the database connected, it was time to define the structure for my book data. Here’s what I added in models/Book.js
:
const mongoose = require('mongoose');
const BookSchema = new mongoose.Schema({
title: String,
author: String,
publishedYear: Number
});
module.exports = mongoose.model('Book', BookSchema);
Setting Up the GraphQL Schema
This is where the magic happens — translating Mongo data into something GraphQL can query and mutate.
I created a file called schema.js
and started with the BookType
, defining the fields my GraphQL API would expose:
const {
GraphQLObjectType,
GraphQLString,
GraphQLInt,
GraphQLID,
GraphQLSchema,
GraphQLList,
GraphQLNonNull
} = require('graphql');
const Book = require('../models/Book');
const BookType = new GraphQLObjectType({
name: 'Book',
fields: () => ({
id: { type: GraphQLID },
title: { type: GraphQLString },
author: { type: GraphQLString },
publishedYear: { type: GraphQLInt }
})
});
Then I added queries for fetching all books or just one by ID, and mutations for adding, updating, and deleting a book:
const RootQuery = new GraphQLObjectType({
name: 'RootQueryType',
fields: {
books: {
type: new GraphQLList(BookType),
resolve() {
return Book.find();
}
},
book: {
type: BookType,
args: { id: { type: GraphQLID } },
resolve(_, args) {
return Book.findById(args.id);
}
}
}
});
const Mutation = new GraphQLObjectType({
name: 'Mutation',
fields: {
addBook: {
type: BookType,
args: {
title: { type: new GraphQLNonNull(GraphQLString) },
author: { type: new GraphQLNonNull(GraphQLString) },
publishedYear: { type: GraphQLInt }
},
resolve(_, args) {
const book = new Book(args);
return book.save();
}
},
deleteBook: {
type: BookType,
args: { id: { type: new GraphQLNonNull(GraphQLID) } },
resolve(_, args) {
return Book.findByIdAndDelete(args.id);
}
},
updateBook: {
type: BookType,
args: {
id: { type: new GraphQLNonNull(GraphQLID) },
title: { type: GraphQLString },
author: { type: GraphQLString },
publishedYear: { type: GraphQLInt }
},
resolve(_, args) {
return Book.findByIdAndUpdate(args.id, args, { new: true });
}
}
}
});
module.exports = new GraphQLSchema({
query: RootQuery,
mutation: Mutation
});
Spinning Up the Server
Everything came together in index.js
:
const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const schema = require('./graphql/schema');
const connectDB = require('./db');
require('dotenv').config();
const app = express();
const PORT = 4000;
connectDB();
app.use('/graphql', graphqlHTTP({
schema,
graphiql: true
}));
app.listen(PORT, () => {
console.log(`Server is running at http://localhost:${PORT}/graphql`);
});
Testing the API
With the server running, I opened GraphiQL and tried adding a book:
mutation {
addBook(title: "GraphQL for Beginners", author: "Jane Doe", publishedYear: 2022) {
id
title
}
}
Then fetched all books:
{
books {
id
title
author
publishedYear
}
}
Seeing real data come through from MongoDB was a rewarding moment — much more satisfying than hardcoded mock data.
Wrap-Up
This simple project took me from a static GraphQL server to a live, data-driven API using MongoDB. What stood out was how natural the flow felt — defining types, connecting them to Mongoose models, and instantly being able to query or mutate that data.
Next up, I’m planning to refine this even more by adding authentication, modularizing the schema and resolvers, and maybe even exploring pagination and filters. But for now, I’ve got a working GraphQL API backed by MongoDB — and that’s a solid foundation to build on.
Subscribe to my newsletter
Read articles from Sharukhan Patan directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
