Node.js MongoDB and Mongoose
Most real-world applications need to store data permanently — in a database. MongoDB is a popular NoSQL database that stores data as flexible JSON-like documents, making it a natural fit for Node.js applications. Mongoose is an Object Data Modeling (ODM) library for MongoDB and Node.js. It provides a structured way to define data models, validate data, and interact with MongoDB using clean, readable JavaScript code.
What Is MongoDB?
MongoDB stores data in collections (similar to tables in SQL) made up of documents (similar to rows). Each document is a JSON-like object that can have its own structure. Unlike SQL databases, MongoDB does not require a fixed schema — different documents in the same collection can have different fields.
What Is Mongoose?
Mongoose adds structure on top of MongoDB by allowing developers to define schemas — blueprints that describe what fields a document should have, their types, default values, and validation rules. This brings consistency to the data stored in MongoDB.
Setting Up MongoDB
MongoDB can be run locally by installing it from https://www.mongodb.com, or it can be used through the cloud service MongoDB Atlas (free tier available) without any local installation.
For this topic, MongoDB Atlas is recommended for simplicity:
- Go to https://cloud.mongodb.com and create a free account.
- Create a free cluster.
- Create a database user with a username and password.
- Get the connection string (it looks like:
mongodb+srv://username:password@cluster.mongodb.net/mydb).
Installing Mongoose
npm install mongoose
Connecting to MongoDB
// db.js
const mongoose = require('mongoose');
const MONGO_URI = 'mongodb+srv://username:password@cluster.mongodb.net/shopdb';
mongoose.connect(MONGO_URI)
.then(() => console.log('Connected to MongoDB successfully!'))
.catch((err) => console.log('Connection error:', err.message));
Defining a Schema and Model
A Schema defines the structure of a document. A Model is a JavaScript class built from the schema that provides methods to interact with the database.
// models/Product.js
const mongoose = require('mongoose');
const productSchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'Product name is required'],
trim: true
},
price: {
type: Number,
required: [true, 'Price is required'],
min: [0, 'Price cannot be negative']
},
category: {
type: String,
required: true,
enum: ['Electronics', 'Furniture', 'Kitchen', 'Clothing']
},
inStock: {
type: Boolean,
default: true
},
createdAt: {
type: Date,
default: Date.now
}
});
const Product = mongoose.model('Product', productSchema);
module.exports = Product;
Mongoose automatically creates a MongoDB collection named products (the lowercase, plural form of the model name Product).
CRUD Operations with Mongoose
Creating a Document
const Product = require('./models/Product');
async function createProduct() {
const product = new Product({
name: 'Wireless Keyboard',
price: 79.99,
category: 'Electronics'
});
const saved = await product.save();
console.log('Product saved:', saved);
}
createProduct();
Or using the shorter create() method:
const product = await Product.create({
name: 'Office Chair',
price: 299,
category: 'Furniture'
});
Reading Documents
// Get all products
const allProducts = await Product.find();
// Get products matching a condition
const electronics = await Product.find({ category: 'Electronics' });
// Get products with price less than 100
const affordable = await Product.find({ price: { $lt: 100 } });
// Get a single product by ID
const product = await Product.findById('64a1b2c3d4e5f6a7b8c9d0e1');
// Get the first matching product
const firstMatch = await Product.findOne({ name: 'Wireless Keyboard' });
Updating Documents
// Find by ID and update, return the new document
const updated = await Product.findByIdAndUpdate(
'64a1b2c3d4e5f6a7b8c9d0e1',
{ price: 89.99, inStock: false },
{ new: true, runValidators: true }
);
console.log('Updated product:', updated);
{ new: true } returns the updated document instead of the old one. { runValidators: true } ensures schema validation runs on the update.
Deleting Documents
// Delete by ID
const deleted = await Product.findByIdAndDelete('64a1b2c3d4e5f6a7b8c9d0e1');
console.log('Deleted:', deleted.name);
// Delete all documents matching a condition
await Product.deleteMany({ inStock: false });
Building a Full REST API with Express and Mongoose
// app.js
const express = require('express');
const mongoose = require('mongoose');
const Product = require('./models/Product');
const app = express();
app.use(express.json());
mongoose.connect('mongodb+srv://username:password@cluster.mongodb.net/shopdb')
.then(() => console.log('MongoDB connected'))
.catch(err => console.log(err));
// GET all products
app.get('/api/products', async (req, res) => {
try {
const products = await Product.find();
res.json({ success: true, data: products });
} catch (err) {
res.status(500).json({ success: false, error: err.message });
}
});
// GET single product
app.get('/api/products/:id', async (req, res) => {
try {
const product = await Product.findById(req.params.id);
if (!product) return res.status(404).json({ success: false, error: 'Not found' });
res.json({ success: true, data: product });
} catch (err) {
res.status(500).json({ success: false, error: err.message });
}
});
// POST create product
app.post('/api/products', async (req, res) => {
try {
const product = await Product.create(req.body);
res.status(201).json({ success: true, data: product });
} catch (err) {
res.status(400).json({ success: false, error: err.message });
}
});
// PUT update product
app.put('/api/products/:id', async (req, res) => {
try {
const product = await Product.findByIdAndUpdate(
req.params.id,
req.body,
{ new: true, runValidators: true }
);
if (!product) return res.status(404).json({ success: false, error: 'Not found' });
res.json({ success: true, data: product });
} catch (err) {
res.status(400).json({ success: false, error: err.message });
}
});
// DELETE product
app.delete('/api/products/:id', async (req, res) => {
try {
const product = await Product.findByIdAndDelete(req.params.id);
if (!product) return res.status(404).json({ success: false, error: 'Not found' });
res.json({ success: true, message: 'Product deleted' });
} catch (err) {
res.status(500).json({ success: false, error: err.message });
}
});
app.listen(3000, () => console.log('Server running on port 3000'));
Query Helpers – Sorting, Limiting, Selecting Fields
// Sort by price ascending
const sorted = await Product.find().sort({ price: 1 });
// Get only the 5 cheapest products
const top5 = await Product.find().sort({ price: 1 }).limit(5);
// Return only name and price fields (exclude _id)
const names = await Product.find().select('name price -_id');
// Pagination (page 2, 10 items per page)
const page = 2;
const limit = 10;
const paginated = await Product.find()
.skip((page - 1) * limit)
.limit(limit);
Key Points
- MongoDB stores data as JSON-like documents in collections. Mongoose adds schemas and models on top of it.
- Connect to MongoDB using
mongoose.connect(uri). The URI includes the host, username, password, and database name. - A Schema defines the structure, types, defaults, and validation rules for documents.
- A Model is created from a Schema with
mongoose.model('Name', schema)and provides all CRUD methods. - Key CRUD methods:
create(),find(),findById(),findByIdAndUpdate(),findByIdAndDelete(). - Always use
async/awaitwithtry/catchblocks for Mongoose operations in Express routes. - Use
sort(),limit(),skip(), andselect()to control query results.
