Express.js Connecting MongoDB
In-memory arrays disappear when your server restarts. A real application needs a database to store data permanently. MongoDB is a popular NoSQL database that stores data as JSON-like documents, making it a natural fit for Express.js. This topic covers connecting Express to MongoDB using Mongoose, the most widely used library for working with MongoDB in Node.js.
MongoDB vs SQL Databases
SQL databases (like MySQL and PostgreSQL) store data in rigid tables with rows and columns, like a spreadsheet. MongoDB stores data as flexible documents in collections, like a filing cabinet of JSON files. Each document can have different fields, making MongoDB great for data that does not always fit the same shape.
SQL Database (table structure):
┌────┬──────────┬────────────────────┬─────┐
│ id │ name │ email │ age │
├────┼──────────┼────────────────────┼─────┤
│ 1 │ Alice │ alice@example.com │ 28 │
│ 2 │ Bob │ bob@example.com │ 34 │
└────┴──────────┴────────────────────┴─────┘
MongoDB (document structure):
{ _id: "abc1", name: "Alice", email: "alice@example.com", age: 28 }
{ _id: "abc2", name: "Bob", email: "bob@example.com", hobbies: ["reading"] }
Key Terms
┌─────────────────────────────────────────────────────────────┐ │ MongoDB Terminology │ ├──────────────────┬──────────────────────────────────────────┤ │ Database │ Contains all your collections │ │ Collection │ Like a table — holds related documents │ │ Document │ One record (like a row), stored as BSON │ │ Field │ A key-value pair inside a document │ │ _id │ Auto-generated unique ID for each doc │ └──────────────────┴──────────────────────────────────────────┘
Install Required Packages
Install Mongoose, which provides a clean API for connecting to MongoDB and defining data models:
npm install mongoose
Set Up a MongoDB Database
Use MongoDB Atlas, which is MongoDB's free cloud service, to create a database without installing anything locally:
1. Visit mongodb.com/cloud/atlas 2. Create a free account 3. Create a free cluster (M0 tier) 4. Add a database user with a username and password 5. Whitelist your IP address (or allow all IPs with 0.0.0.0/0 for development) 6. Click "Connect" → "Connect your application" 7. Copy the connection string — it looks like: mongodb+srv://username:password@cluster.mongodb.net/myDatabase
Connect Express to MongoDB
Create your app.js file with the database connection:
const express = require('express');
const mongoose = require('mongoose');
const app = express();
app.use(express.json());
// Replace with your actual connection string
const MONGODB_URI = 'mongodb+srv://user:pass@cluster.mongodb.net/myapp';
mongoose.connect(MONGODB_URI)
.then(() => {
console.log('Connected to MongoDB');
app.listen(3000, () => {
console.log('Server running on port 3000');
});
})
.catch((err) => {
console.error('MongoDB connection error:', err);
process.exit(1); // Stop server if DB connection fails
});
The server starts only after the database connection succeeds. This prevents requests from arriving before the database is ready.
Define a Mongoose Schema and Model
A Schema defines the shape and rules for documents in a collection. A Model provides the functions to create, read, update, and delete documents.
const mongoose = require('mongoose');
const taskSchema = new mongoose.Schema({
title: {
type: String,
required: [true, 'Title is required'],
trim: true,
maxlength: [200, 'Title cannot exceed 200 characters']
},
done: {
type: Boolean,
default: false
},
priority: {
type: String,
enum: ['low', 'medium', 'high'],
default: 'medium'
},
createdAt: {
type: Date,
default: Date.now
}
});
const Task = mongoose.model('Task', taskSchema);
module.exports = Task;
Mongoose Schema to MongoDB Document Flow
Schema defines the rules: MongoDB stores:
───────────────────────── ──────────────────────────────
title: String, required {
done: Boolean, default false _id: ObjectId('64a1b2c3...'),
priority: 'low'|'medium'|'high' title: 'Buy groceries',
createdAt: Date, auto done: false,
priority: 'medium',
createdAt: 2024-01-15T10:00:00Z
}
CRUD Operations with Mongoose
CREATE — Save a New Document
app.post('/api/tasks', async (req, res) => {
try {
const task = new Task(req.body);
const savedTask = await task.save();
res.status(201).json({ success: true, data: savedTask });
} catch (err) {
res.status(400).json({ success: false, error: err.message });
}
});
READ — Get All Documents
app.get('/api/tasks', async (req, res) => {
try {
const tasks = await Task.find();
res.status(200).json({ success: true, count: tasks.length, data: tasks });
} catch (err) {
res.status(500).json({ success: false, error: err.message });
}
});
READ — Get One Document
app.get('/api/tasks/:id', async (req, res) => {
try {
const task = await Task.findById(req.params.id);
if (!task) {
return res.status(404).json({ success: false, error: 'Task not found' });
}
res.status(200).json({ success: true, data: task });
} catch (err) {
res.status(400).json({ success: false, error: 'Invalid ID format' });
}
});
UPDATE — Modify a Document
app.patch('/api/tasks/:id', async (req, res) => {
try {
const task = await Task.findByIdAndUpdate(
req.params.id,
req.body,
{ new: true, runValidators: true } // Return updated doc, run schema rules
);
if (!task) {
return res.status(404).json({ success: false, error: 'Task not found' });
}
res.status(200).json({ success: true, data: task });
} catch (err) {
res.status(400).json({ success: false, error: err.message });
}
});
DELETE — Remove a Document
app.delete('/api/tasks/:id', async (req, res) => {
try {
const task = await Task.findByIdAndDelete(req.params.id);
if (!task) {
return res.status(404).json({ success: false, error: 'Task not found' });
}
res.status(200).json({ success: true, message: 'Task deleted' });
} catch (err) {
res.status(500).json({ success: false, error: err.message });
}
});
Querying with Filters
Mongoose provides powerful query methods to filter, sort, and limit documents:
// Find only incomplete tasks, sorted by newest first
const tasks = await Task
.find({ done: false })
.sort({ createdAt: -1 })
.limit(10);
// Find tasks with 'grocery' in the title (case-insensitive)
const tasks = await Task.find({
title: { $regex: 'grocery', $options: 'i' }
});
// Find high-priority incomplete tasks
const tasks = await Task.find({ priority: 'high', done: false });
Using Environment Variables for the Connection String
Never put your database password directly in your code. Store it in an environment variable instead:
# .env file (add to .gitignore!) MONGODB_URI=mongodb+srv://user:pass@cluster.mongodb.net/myapp PORT=3000
// In app.js
require('dotenv').config(); // npm install dotenv
mongoose.connect(process.env.MONGODB_URI);
Summary
MongoDB stores data as flexible JSON-like documents in collections. Mongoose is the Node.js library that connects Express to MongoDB, provides schemas for data structure and validation, and supplies models for database operations. Connect using mongoose.connect() inside a .then() chain so the server starts only after the database is ready. Define schemas with field types, validations, and defaults, then use Task.find(), Task.save(), Task.findByIdAndUpdate(), and Task.findByIdAndDelete() for CRUD operations. Always store your connection string in an environment variable and never commit it to version control.
