Lesson 14 of 20

Working with Databases (MongoDB)

MongoDB and Mongoose ODM

MongoDB is a NoSQL document database that stores data as flexible JSON-like documents. Unlike SQL databases with rigid tables, MongoDB collections can contain documents with different structures.

Mongoose is an ODM (Object Data Modeling) library for MongoDB and Node.js. It provides schema validation, type casting, query building, and middleware hooks. Mongoose makes working with MongoDB more structured and predictable.

Example
// npm install mongoose

const mongoose = require('mongoose');

// Connect to MongoDB
mongoose.connect('mongodb://localhost:27017/myapp')
  .then(() => console.log('Connected to MongoDB'))
  .catch(err => console.error('Connection error:', err));

// Define a Schema
const userSchema = new mongoose.Schema({
  name: { type: String, required: true, trim: true },
  email: {
    type: String,
    required: true,
    unique: true,
    lowercase: true
  },
  age: { type: Number, min: 0 },
  role: {
    type: String,
    enum: ['user', 'admin'],
    default: 'user'
  },
  createdAt: { type: Date, default: Date.now }
});

// Create a Model
const User = mongoose.model('User', userSchema);

module.exports = User;
  • mongoose.connect() — Connect to a MongoDB database
  • Schema — Define the structure, types, and validation for documents
  • Model — A constructor compiled from the Schema, used for CRUD operations
  • required — Mark a field as mandatory
  • unique — Ensure no duplicate values for a field
  • default — Set a default value when the field is not provided
Notes
  • Install MongoDB locally or use MongoDB Atlas (free cloud tier) for development. The connection string format is: mongodb://localhost:27017/database-name.

CRUD Operations with Mongoose

Mongoose models provide methods for all CRUD operations. These methods return Promises, so you can use async/await. Common operations include find(), findById(), create(), findByIdAndUpdate(), and findByIdAndDelete().

Here is a complete Express route file using Mongoose for database operations.

Example
const express = require('express');
const User = require('./models/User');
const router = express.Router();

// CREATE — Add a new user
router.post('/', async (req, res) => {
  try {
    const user = await User.create(req.body);
    res.status(201).json(user);
  } catch (err) {
    if (err.code === 11000) {
      return res.status(400).json({ error: 'Email already exists' });
    }
    res.status(400).json({ error: err.message });
  }
});

// READ — Get all users (with filtering)
router.get('/', async (req, res) => {
  try {
    const { role, sort = '-createdAt' } = req.query;
    const filter = role ? { role } : {};
    const users = await User.find(filter).sort(sort).limit(50);
    res.json(users);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// READ — Get a single user
router.get('/:id', async (req, res) => {
  try {
    const user = await User.findById(req.params.id);
    if (!user) return res.status(404).json({ error: 'User not found' });
    res.json(user);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// UPDATE — Modify a user
router.put('/:id', async (req, res) => {
  try {
    const user = await User.findByIdAndUpdate(
      req.params.id,
      req.body,
      { new: true, runValidators: true }
    );
    if (!user) return res.status(404).json({ error: 'User not found' });
    res.json(user);
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

// DELETE — Remove a user
router.delete('/:id', async (req, res) => {
  try {
    const user = await User.findByIdAndDelete(req.params.id);
    if (!user) return res.status(404).json({ error: 'User not found' });
    res.status(204).send();
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

module.exports = router;
Notes
  • Always use { new: true } with findByIdAndUpdate() to return the updated document. By default, it returns the original document before the update.