MongoDB Fundamentals
Your data currently lives in JavaScript arrays and vanishes when the server restarts. Today you learn to persist data permanently using MongoDB, a powerful NoSQL database.
Why Databases Matter
In Day 5, we stored books in a JavaScript array. The moment we stopped the server, everything was lost. Real applications need persistent storage - data that survives server restarts, crashes, and updates.
Databases provide:
- Persistence - Data is written to disk and survives restarts
- Scalability - Handle millions of records efficiently
- Concurrent Access - Multiple users can read and write simultaneously
- Data Integrity - Rules ensure data remains consistent
- Query Power - Search, filter, and aggregate data easily
SQL vs NoSQL
There are two main database paradigms:
SQL (Relational) Databases
MySQL, PostgreSQL, SQLite store data in tables with rows and columns. Data has a fixed structure (schema). Tables relate to each other through foreign keys.
Best for: Financial systems, complex relationships, when data structure is well-defined upfront.
NoSQL (Non-Relational) Databases
MongoDB, Redis, CouchDB store data in flexible formats like documents or key-value pairs. Schema is flexible - different documents can have different fields.
Best for: Rapid development, evolving requirements, JSON-heavy applications, high scalability needs.
SQL is like a spreadsheet - every row must have the same columns. NoSQL is like a filing cabinet - each folder can contain different types of papers with different formats.
What is MongoDB?
MongoDB is a document-oriented NoSQL database. Instead of tables and rows, it uses collections and documents. Documents are JSON-like objects that can have any structure.
┌──────────────────────┐ ┌──────────────────────┐ │ SQL World │ │ MongoDB World │ ├──────────────────────┤ ├──────────────────────┤ │ Database │ ───────► │ Database │ │ Table │ ───────► │ Collection │ │ Row │ ───────► │ Document │ │ Column │ ───────► │ Field │ └──────────────────────┘ └──────────────────────┘
A MongoDB document looks like this:
{
"_id": "507f1f77bcf86cd799439011", // Unique identifier auto-generated by MongoDB
"name": "John Doe", // String field
"email": "john@example.com", // Another string field
"age": 28, // Number field
"isActive": true, // Boolean field
"roles": ["admin", "editor"], // Array field
"address": { // Nested object (embedded document)
"city": "New York",
"zip": "10001"
},
"createdAt": "2024-01-15T10:30:00Z" // Date stored as string or Date object
}
Notice how flexible this is - you can nest objects, include arrays, and mix data types. Each document in a collection can have different fields.
Setting Up MongoDB
You have two options for using MongoDB:
Option 1: MongoDB Atlas (Cloud - Recommended for Beginners)
MongoDB Atlas is a free cloud-hosted database. No installation required.
- Go to mongodb.com/atlas
- Create a free account
- Create a new cluster (choose the FREE tier)
- Click "Connect" and choose "Connect your application"
- Copy the connection string (looks like:
mongodb+srv://user:pass@cluster.mongodb.net/dbname)
Option 2: Local Installation
For offline development, install MongoDB Community Server from mongodb.com/download.
MongoDB Shell Basics
Before connecting from Node.js, let us learn MongoDB commands using the shell. If using Atlas, use MongoDB Compass or the Atlas web interface.
// Show all databases on the server
show dbs
// Switch to a database (creates it if it does not exist)
use myapp
// Show all collections in current database
show collections
// Create a collection by inserting a document
db.users.insertOne({ name: "Alice", email: "alice@example.com" })
// Find all documents in a collection
db.users.find()
// Find with pretty formatting
db.users.find().pretty()
CRUD Operations in MongoDB
CRUD stands for Create, Read, Update, Delete - the four basic operations for any database.
CREATE - Inserting Documents
// Insert a single document into the 'products' collection
// MongoDB automatically creates the collection if it does not exist
db.products.insertOne({
name: "Laptop", // Product name as string
price: 999.99, // Price as number (supports decimals)
category: "Electronics", // Category for filtering
inStock: true, // Boolean for availability
tags: ["computer", "portable"] // Array of tags for search
})
// Insert multiple documents at once
// More efficient than multiple insertOne calls
db.products.insertMany([
{
name: "Mouse",
price: 29.99,
category: "Electronics",
inStock: true,
tags: ["computer", "accessory"]
},
{
name: "Keyboard",
price: 79.99,
category: "Electronics",
inStock: false,
tags: ["computer", "accessory", "mechanical"]
},
{
name: "Headphones",
price: 149.99,
category: "Audio",
inStock: true,
tags: ["audio", "wireless"]
}
])
When you insert a document, MongoDB automatically adds an _id field if you do not provide one. This is a unique identifier called an ObjectId.
READ - Finding Documents
// Find ALL documents in the collection
// Returns a cursor that iterates through results
db.products.find()
// Find documents matching a condition
// This finds all products in the Electronics category
db.products.find({ category: "Electronics" })
// Find ONE document (returns first match)
// Useful when you expect only one result
db.products.findOne({ name: "Laptop" })
// Find with multiple conditions (AND logic)
// Both conditions must be true
db.products.find({
category: "Electronics", // Must be Electronics
inStock: true // AND must be in stock
})
// Comparison operators - find products over $50
// $gt = greater than, $gte = greater than or equal
// $lt = less than, $lte = less than or equal
// $ne = not equal, $eq = equal
db.products.find({ price: { $gt: 50 } })
// Find products priced between $25 and $100
db.products.find({
price: {
$gte: 25, // Greater than or equal to 25
$lte: 100 // Less than or equal to 100
}
})
// OR logic - find Electronics OR Audio products
db.products.find({
$or: [
{ category: "Electronics" },
{ category: "Audio" }
]
})
// Find documents where field is in a list of values
db.products.find({
category: { $in: ["Electronics", "Audio"] }
})
// Projection - return only specific fields
// 1 means include, 0 means exclude
// _id is included by default unless explicitly excluded
db.products.find(
{ category: "Electronics" }, // Filter condition
{ name: 1, price: 1, _id: 0 } // Only return name and price
)
UPDATE - Modifying Documents
// Update ONE document matching the filter
// $set operator changes specific fields without affecting others
db.products.updateOne(
{ name: "Laptop" }, // Filter: find document with this name
{ $set: { price: 899.99 } } // Update: change price to new value
)
// Update multiple fields at once
db.products.updateOne(
{ name: "Keyboard" },
{
$set: {
price: 89.99, // Update price
inStock: true, // Update stock status
updatedAt: new Date() // Add new field with current timestamp
}
}
)
// Update ALL documents matching the filter
// Be careful - this affects multiple documents!
db.products.updateMany(
{ category: "Electronics" }, // All electronics
{ $set: { onSale: true } } // Add onSale field to all
)
// Increment a numeric field
// $inc adds the value (use negative to subtract)
db.products.updateOne(
{ name: "Mouse" },
{ $inc: { price: 5 } } // Increase price by 5
)
// Add item to an array field
db.products.updateOne(
{ name: "Laptop" },
{ $push: { tags: "gaming" } } // Add "gaming" to tags array
)
// Remove item from an array
db.products.updateOne(
{ name: "Laptop" },
{ $pull: { tags: "gaming" } } // Remove "gaming" from tags
)
// Upsert - update if exists, insert if not
// Useful for "create or update" scenarios
db.products.updateOne(
{ name: "Tablet" },
{ $set: { price: 499, category: "Electronics" } },
{ upsert: true } // Create document if no match found
)
DELETE - Removing Documents
// Delete ONE document matching the filter
// Only deletes the first match if multiple exist
db.products.deleteOne({ name: "Tablet" })
// Delete ALL documents matching the filter
// WARNING: This can delete many documents!
db.products.deleteMany({ inStock: false })
// Delete ALL documents in a collection
// DANGER: This empties the entire collection!
db.products.deleteMany({})
// Drop (delete) an entire collection
// DANGER: Collection and all its documents are gone!
db.products.drop()
Delete operations are permanent. Always double-check your filter before running deleteMany(). In production, consider "soft deletes" (marking documents as deleted) instead of actual deletion.
Advanced Queries
// Sort results
// 1 = ascending (A-Z, 0-9), -1 = descending (Z-A, 9-0)
db.products.find().sort({ price: 1 }) // Cheapest first
db.products.find().sort({ price: -1 }) // Most expensive first
db.products.find().sort({ name: 1 }) // Alphabetical by name
// Limit number of results
db.products.find().limit(5) // Return only 5 documents
// Skip results (for pagination)
db.products.find().skip(10) // Skip first 10, return the rest
// Combine for pagination
// Page 1: skip 0, limit 10
// Page 2: skip 10, limit 10
// Page 3: skip 20, limit 10
db.products.find()
.sort({ createdAt: -1 }) // Newest first
.skip(20) // Skip first 20 (page 3)
.limit(10) // Return 10 per page
// Count documents matching a filter
db.products.countDocuments({ category: "Electronics" })
// Check if any documents exist
db.products.findOne({ name: "Laptop" }) !== null
Connecting from Node.js
Now let us connect to MongoDB from our Express application. We will use the official MongoDB driver.
# Create a new project
mkdir mongodb-demo
cd mongodb-demo
npm init -y
# Install MongoDB driver and Express
npm install mongodb express
// Import required modules
const express = require('express');
const { MongoClient, ObjectId } = require('mongodb');
// Create Express application
const app = express();
// Middleware to parse JSON request bodies
app.use(express.json());
// MongoDB connection string
// Replace with your Atlas connection string or use localhost
const uri = 'mongodb://localhost:27017';
// Database and collection names
const dbName = 'shopdb';
const collectionName = 'products';
// Variable to hold database connection
let db;
// Connect to MongoDB when server starts
async function connectToMongo() {
try {
// Create a new MongoClient instance
const client = new MongoClient(uri);
// Connect to the MongoDB server
await client.connect();
// Select the database
db = client.db(dbName);
console.log('Connected to MongoDB successfully!');
} catch (error) {
console.error('Failed to connect to MongoDB:', error);
process.exit(1); // Exit if cannot connect
}
}
// GET all products
app.get('/api/products', async (req, res) => {
try {
// Find all documents and convert cursor to array
const products = await db
.collection(collectionName)
.find()
.toArray();
res.json(products);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch products' });
}
});
// GET single product by ID
app.get('/api/products/:id', async (req, res) => {
try {
// Convert string ID to MongoDB ObjectId
const id = new ObjectId(req.params.id);
// Find document with matching _id
const product = await db
.collection(collectionName)
.findOne({ _id: id });
if (!product) {
return res.status(404).json({ error: 'Product not found' });
}
res.json(product);
} catch (error) {
res.status(400).json({ error: 'Invalid product ID' });
}
});
// POST create new product
app.post('/api/products', async (req, res) => {
try {
// Extract data from request body
const { name, price, category, inStock } = req.body;
// Validate required fields
if (!name || !price) {
return res.status(400).json({ error: 'Name and price are required' });
}
// Create product document
const newProduct = {
name,
price,
category: category || 'Uncategorized',
inStock: inStock !== undefined ? inStock : true,
createdAt: new Date()
};
// Insert into database
const result = await db
.collection(collectionName)
.insertOne(newProduct);
// Return created product with its new _id
res.status(201).json({
_id: result.insertedId,
...newProduct
});
} catch (error) {
res.status(500).json({ error: 'Failed to create product' });
}
});
// PUT update product
app.put('/api/products/:id', async (req, res) => {
try {
const id = new ObjectId(req.params.id);
const updates = req.body;
// Add updatedAt timestamp
updates.updatedAt = new Date();
// Update the document
const result = await db
.collection(collectionName)
.updateOne(
{ _id: id },
{ $set: updates }
);
if (result.matchedCount === 0) {
return res.status(404).json({ error: 'Product not found' });
}
// Fetch and return updated product
const updated = await db
.collection(collectionName)
.findOne({ _id: id });
res.json(updated);
} catch (error) {
res.status(400).json({ error: 'Invalid product ID or data' });
}
});
// DELETE product
app.delete('/api/products/:id', async (req, res) => {
try {
const id = new ObjectId(req.params.id);
// Delete the document
const result = await db
.collection(collectionName)
.deleteOne({ _id: id });
if (result.deletedCount === 0) {
return res.status(404).json({ error: 'Product not found' });
}
res.json({ message: 'Product deleted successfully' });
} catch (error) {
res.status(400).json({ error: 'Invalid product ID' });
}
});
// Start server after connecting to database
const PORT = 3000;
connectToMongo().then(() => {
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
});
Understanding ObjectId
MongoDB uses ObjectId as the default identifier type. It is a 12-byte value that includes:
- 4-byte timestamp (seconds since Unix epoch)
- 5-byte random value (unique per machine/process)
- 3-byte incrementing counter
This makes ObjectIds globally unique without coordination between servers.
const { ObjectId } = require('mongodb');
// Convert string to ObjectId for queries
const id = new ObjectId('507f1f77bcf86cd799439011');
// Check if a string is a valid ObjectId
function isValidObjectId(str) {
try {
return new ObjectId(str).toString() === str;
} catch {
return false;
}
}
// Generate a new ObjectId
const newId = new ObjectId();
// Get timestamp from ObjectId
const timestamp = id.getTimestamp(); // Returns Date object
Video Resources
Blog Posts Database
Create a MongoDB-backed API for blog posts:
GET /api/posts- Get all posts (support ?limit and ?skip for pagination)GET /api/posts/:id- Get single postPOST /api/posts- Create post (title, content, author, tags array)PUT /api/posts/:id- Update postDELETE /api/posts/:id- Delete postGET /api/posts/search?q=keyword- Search posts by title
Knowledge Check: Day 6
1. In MongoDB terminology, what is equivalent to a "row" in SQL databases?
2. Which operator would you use to find all products with price greater than 100?
3. What does the $set operator do in an update operation?
4. What is the purpose of ObjectId in MongoDB?