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.

Leave a Comment

Your email address will not be published. Required fields are marked *