01How The Internet Works 02Your First Node.js Server 03HTTP Methods and JSON 04Express.js Framework 05Building REST APIs 06MongoDB Fundamentals 07Mongoose ODM 08Data Modeling 09Authentication Basics 10JWT Implementation

Settings

Theme
Sand
Cloud
Midnight
Forest
Sunset
Purple
Ocean
Crimson
Font
Merriweather
Inter
JetBrains
Space Grotesk
Fira Code
Playfair
Font Size
100%
Bookmark
No bookmark set
Day 6

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:

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.

Real World Parallel

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:

MongoDB Document
{
    "_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.

  1. Go to mongodb.com/atlas
  2. Create a free account
  3. Create a new cluster (choose the FREE tier)
  4. Click "Connect" and choose "Connect your application"
  5. 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.

MongoDB Shell Commands
// 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 Operations
// 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 Operations
// 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 Operations
// 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 Operations
// 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()
Warning

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

Sorting, Limiting, and Skipping
// 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.

Terminal
# Create a new project
mkdir mongodb-demo
cd mongodb-demo
npm init -y

# Install MongoDB driver and Express
npm install mongodb express
index.js - Complete MongoDB + Express Example
// 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:

This makes ObjectIds globally unique without coordination between servers.

Working with ObjectId
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

Mini Project

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 post
  • POST /api/posts - Create post (title, content, author, tags array)
  • PUT /api/posts/:id - Update post
  • DELETE /api/posts/:id - Delete post
  • GET /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?

ACollection
BDocument
CField
DDatabase

2. Which operator would you use to find all products with price greater than 100?

A{ price: { $more: 100 } }
B{ price: { $above: 100 } }
C{ price: { $gt: 100 } }
D{ price: > 100 }

3. What does the $set operator do in an update operation?

AModifies specific fields without affecting others
BReplaces the entire document
CDeletes the specified fields
DCreates a new collection

4. What is the purpose of ObjectId in MongoDB?

ATo encrypt document data
BTo compress documents
CTo connect to the database
DTo uniquely identify documents