Adding MongoDB to Your Node.js API
This tutorial builds on our previous API project, showing how to replace the in-memory storage with MongoDB. We'll use Mongoose as our ODM (Object Data Modeling) library to interact with MongoDB.
Prerequisites
- Completed Part 1 of the tutorial
- MongoDB installed locally or a MongoDB Atlas account
- Basic understanding of databases and async/await
Project Setup
First, install the required dependencies:
npm install mongoose dotenv
Updated Project Structure
Add these new files to your project:
books-api/
├── src/
│ ├── config/
│ │ └── database.js
│ ├── models/
│ │ └── Book.js
│ ├── routes/
│ │ └── books.js
│ ├── middleware/
│ │ └── errorHandler.js
│ └── server.js
├── package.json
└── .env
Environment Setup
Create a .env
file in your root directory:
MONGODB_URI=mongodb://localhost:27017/books_api
NODE_ENV=development
PORT=3000
Database Configuration
Create src/config/database.js
:
const mongoose = require('mongoose');
const connectDB = async () => {
try {
const conn = await mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true
});
console.log(`MongoDB Connected: ${conn.connection.host}`);
} catch (error) {
console.error(`Error: ${error.message}`);
process.exit(1);
}
};
module.exports = connectDB;
Book Model
Create src/models/Book.js
:
const mongoose = require('mongoose');
const bookSchema = new mongoose.Schema({
title: {
type: String,
required: [true, 'Title is required'],
trim: true
},
author: {
type: String,
required: [true, 'Author is required'],
trim: true
},
publishedYear: {
type: Number,
validate: {
validator: function(value) {
return value >= 1000 && value <= new Date().getFullYear();
},
message: props => `${props.value} is not a valid year!`
}
},
isbn: {
type: String,
unique: true,
sparse: true,
trim: true
}
}, {
timestamps: true
});
// Add index for better search performance
bookSchema.index({ title: 'text', author: 'text' });
module.exports = mongoose.model('Book', bookSchema);
Updated Server File
Update src/server.js
:
require('dotenv').config();
const express = require('express');
const bodyParser = require('body-parser');
const connectDB = require('./config/database');
const booksRouter = require('./routes/books');
const errorHandler = require('./middleware/errorHandler');
// Initialize express app
const app = express();
const PORT = process.env.PORT || 3000;
// Connect to MongoDB
connectDB();
// Middleware
app.use(bodyParser.json());
// Routes
app.use('/api/books', booksRouter);
// Error handling middleware
app.use(errorHandler);
// Start server
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Updated Routes
Update src/routes/books.js
:
const express = require('express');
const router = express.Router();
const Book = require('../models/Book');
// GET all books with pagination and filtering
router.get('/', async (req, res, next) => {
try {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const searchQuery = req.query.search;
let query = {};
// Add search functionality
if (searchQuery) {
query = { $text: { $search: searchQuery } };
}
const books = await Book.find(query)
.skip((page - 1) * limit)
.limit(limit)
.sort({ createdAt: -1 });
const total = await Book.countDocuments(query);
res.json({
books,
currentPage: page,
totalPages: Math.ceil(total / limit),
totalBooks: total
});
} catch (error) {
next(error);
}
});
// GET book by ID
router.get('/:id', async (req, res, next) => {
try {
const book = await Book.findById(req.params.id);
if (!book) {
return res.status(404).json({ message: 'Book not found' });
}
res.json(book);
} catch (error) {
next(error);
}
});
// POST new book
router.post('/', async (req, res, next) => {
try {
const book = new Book(req.body);
const savedBook = await book.save();
res.status(201).json(savedBook);
} catch (error) {
next(error);
}
});
// PUT update book
router.put('/:id', async (req, res, next) => {
try {
const book = await Book.findByIdAndUpdate(
req.params.id,
req.body,
{ new: true, runValidators: true }
);
if (!book) {
return res.status(404).json({ message: 'Book not found' });
}
res.json(book);
} catch (error) {
next(error);
}
});
// DELETE book
router.delete('/:id', async (req, res, next) => {
try {
const book = await Book.findByIdAndDelete(req.params.id);
if (!book) {
return res.status(404).json({ message: 'Book not found' });
}
res.status(204).send();
} catch (error) {
next(error);
}
});
module.exports = router;
Enhanced Error Handler
Update src/middleware/errorHandler.js
:
function errorHandler(err, req, res, next) {
console.error(err.stack);
// Mongoose validation error
if (err.name === 'ValidationError') {
return res.status(400).json({
message: 'Validation Error',
errors: Object.values(err.errors).map(error => ({
field: error.path,
message: error.message
}))
});
}
// Mongoose duplicate key error
if (err.code === 11000) {
return res.status(400).json({
message: 'Duplicate field value entered',
field: Object.keys(err.keyPattern)[0]
});
}
// Mongoose cast error (invalid ID)
if (err.name === 'CastError') {
return res.status(400).json({
message: 'Invalid ID format'
});
}
res.status(500).json({
message: 'Something went wrong!',
error: process.env.NODE_ENV === 'development' ? err.message : undefined
});
}
module.exports = errorHandler;
Testing the MongoDB Integration
- Start MongoDB locally or ensure your Atlas connection is ready
- Start your server:
node src/server.js
Test the enhanced API endpoints:
# Get all books (with pagination)
curl "http://localhost:3000/api/books?page=1&limit=10"
# Search books
curl "http://localhost:3000/api/books?search=Gatsby"
# Create new book
curl -X POST -H "Content-Type: application/json" \
-d '{
"title": "The Great Gatsby",
"author": "F. Scott Fitzgerald",
"publishedYear": 1925,
"isbn": "978-0743273565"
}' \
http://localhost:3000/api/books
# Update book
curl -X PUT -H "Content-Type: application/json" \
-d '{
"publishedYear": 1926
}' \
http://localhost:3000/api/books/[book-id]
# Delete book
curl -X DELETE http://localhost:3000/api/books/[book-id]
New Features Added
MongoDB Integration
- Proper database connection with error handling
- Mongoose schema with validation
- Timestamps for created and updated dates
Enhanced Query Features
- Pagination support
- Text search functionality
- Sorting by creation date
Improved Error Handling
- Mongoose validation errors
- Duplicate key errors
- Invalid ID format errors
Data Validation
- Required fields
- Year validation
- Unique ISBN
Best Practices Implemented
Environment Variables
- Database connection string
- Node environment
- Port configuration
Error Handling
- Centralized error handling middleware
- Specific error types with appropriate status codes
- Development vs production error messages
Database Operations
- Async/await for all database operations
- Proper error catching
- Validation before saving
API Features
- Pagination to handle large datasets
- Search functionality
- Proper HTTP status codes
Next Steps
To further enhance your API:
- Implementing authentication and authorization
- Implementing rate limiting
- Adding API documentation using Swagger
- Setting up automated testing
- Adding pagination for GET requests
- Implementing proper logging
Conclusion
You now have a robust API with MongoDB integration! This implementation includes proper data persistence, validation, error handling, and several advanced features like pagination and search. Remember to:
- Regularly backup your database
- Monitor performance
- Implement proper security measures
- Keep your dependencies updated
The next tutorial will cover adding authentication and authorization to secure your API endpoints.
Related Posts
6 min read
The Model Context Protocol (MCP) has emerged as one of the most significant developments in AI technology in 2025. Launched by Anthropic in November 2024, MCP is an open standard designed to bridge AI...
5 min read
APIs (Application Programming Interfaces) are the backbone of modern digital applications. They allow different software systems to communicate, exchange data, and collaborate seamlessly. As businesse...