5 min read
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, consider:
- 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
• 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...
• 4 min read
In today’s interconnected digital world, APIs (Application Programming Interfaces) are the backbone of communication between different software applications. From mobile apps to cloud services, APIs e...
• 5 min read
In the modern digital ecosystem, APIs (Application Programming Interfaces) serve as the backbone of connectivity. Whether you're building microservices, enabling integrations, or crafting data pipelin...