Connecting Mongoose to MongoDB — Local and Atlas

Connecting Mongoose to MongoDB is the first thing your Express server does on startup — before any routes are registered and before any requests are handled. A poorly managed database connection causes silent failures, crashes under load, or an Express server that accepts requests but cannot fulfil them because MongoDB is unreachable. In this lesson you will write a production-quality database connection module, handle all connection lifecycle events correctly, implement graceful shutdown, and structure the connection code so it integrates cleanly into your Express application.

The Connection Flow

app startup
    │
    ▼
mongoose.connect(MONGODB_URI)
    │
    ├─ Connecting...
    │
    ├─ Connected ──────────────────────────────────────────────────────────────┐
    │   │                                                                       │
    │   ▼                                                                       │
    │  app.listen(PORT)  ← start accepting requests only after DB is connected │
    │                                                                           │
    └─ Error ────────────────────────────────────────────────────────────────── ▼
                                                                     log + process.exit(1)
Note: Mongoose maintains an internal connection pool — by default it creates up to 5 concurrent connections to MongoDB. These connections are reused across requests. You call mongoose.connect() once at startup and Mongoose manages the pool automatically. You never need to manually acquire or release connections in your route handlers — just await your Mongoose queries normally.
Tip: Always start your Express server inside the .then() callback of mongoose.connect() — or await it before calling app.listen(). This guarantees your API never accepts HTTP requests before the database connection is established. An Express server that starts without a database connection will accept requests but crash when the first DB query runs.
Warning: Never hardcode a MongoDB connection string in your source code — not even for development. Use process.env.MONGODB_URI loaded from a .env file via dotenv. Connection strings contain credentials (Atlas username, password, cluster hostname) that must never appear in Git history. If a connection string is committed to a public repository, rotate the Atlas credentials immediately.

The Database Connection Module

// server/src/config/db.js
const mongoose = require('mongoose');

const connectDB = async () => {
  try {
    const conn = await mongoose.connect(process.env.MONGODB_URI, {
      // These options are the defaults in Mongoose 6+ — listed for clarity
      // maxPoolSize: 10,   // default connection pool size
      // serverSelectionTimeoutMS: 5000,  // fail fast if no server found
    });

    console.log(`MongoDB connected: ${conn.connection.host}`);

  } catch (err) {
    console.error(`MongoDB connection failed: ${err.message}`);
    process.exit(1); // exit the Node process — cannot run without a database
  }
};

module.exports = connectDB;

Connection Events

// server/src/config/db.js — with connection event listeners
const mongoose = require('mongoose');

const connectDB = async () => {
  // ── Event listeners — set up before connecting ────────────────────────────
  mongoose.connection.on('connected', () => {
    console.log(`MongoDB connected: ${mongoose.connection.host}`);
  });

  mongoose.connection.on('error', (err) => {
    console.error(`MongoDB connection error: ${err.message}`);
  });

  mongoose.connection.on('disconnected', () => {
    console.warn('MongoDB disconnected');
  });

  mongoose.connection.on('reconnected', () => {
    console.log('MongoDB reconnected');
  });

  // ── Graceful shutdown on SIGINT (Ctrl+C) ──────────────────────────────────
  process.on('SIGINT', async () => {
    await mongoose.connection.close();
    console.log('MongoDB connection closed — process exiting');
    process.exit(0);
  });

  // ── Connect ───────────────────────────────────────────────────────────────
  try {
    await mongoose.connect(process.env.MONGODB_URI);
  } catch (err) {
    console.error(`Initial MongoDB connection failed: ${err.message}`);
    process.exit(1);
  }
};

module.exports = connectDB;

Integration in index.js

// server/index.js
require('dotenv').config();
const express   = require('express');
const connectDB = require('./src/config/db');

const app  = express();
const PORT = process.env.PORT || 5000;

// ── Middleware ─────────────────────────────────────────────────────────────────
app.use(express.json());

// ── Routes ─────────────────────────────────────────────────────────────────────
app.use('/api/posts', require('./src/routes/posts'));
app.use('/api/auth',  require('./src/routes/auth'));

// ── Start — connect to DB first, then start the HTTP server ───────────────────
const start = async () => {
  await connectDB();           // 1. establish database connection
  app.listen(PORT, () => {     // 2. only then start accepting HTTP requests
    console.log(`Server running → http://localhost:${PORT}`);
  });
};

start().catch(err => {
  console.error('Failed to start server:', err.message);
  process.exit(1);
});

Connecting to Local MongoDB vs Atlas

// server/.env — local MongoDB
MONGODB_URI=mongodb://localhost:27017/blogdb

// server/.env — MongoDB Atlas
MONGODB_URI=mongodb+srv://mern_user:password@cluster0.xxxxx.mongodb.net/blogdb?retryWrites=true&w=majority

// The connection code is IDENTICAL — only the URI in .env changes
// This is why you should never hardcode the URI — one env file change
// switches between local and Atlas seamlessly

Mongoose Connection States

State Number State Name Meaning
0 disconnected Not connected — initial state or after close()
1 connected Active connection — queries will execute
2 connecting Attempting to connect
3 disconnecting In the process of closing the connection
// Check connection state at any time
console.log(mongoose.connection.readyState);
// 0 = disconnected, 1 = connected, 2 = connecting, 3 = disconnecting

// Check in a health endpoint
app.get('/api/health', (req, res) => {
  res.json({
    status:   'ok',
    database: mongoose.connection.readyState === 1 ? 'connected' : 'disconnected',
  });
});

Common Mistakes

Mistake 1 — Starting the server before connecting to MongoDB

❌ Wrong — calling app.listen() before mongoose.connect() completes:

mongoose.connect(process.env.MONGODB_URI); // async — not awaited
app.listen(5000); // starts immediately — DB not connected yet
// First request to a DB-dependent route → MongoNotConnectedError

✅ Correct — await the connection before starting the server:

await mongoose.connect(process.env.MONGODB_URI);
app.listen(5000); // only starts after DB is connected ✓

Mistake 2 — Not handling the initial connection failure

❌ Wrong — no catch on connect() — process hangs silently if Atlas is unreachable:

mongoose.connect(process.env.MONGODB_URI); // unhandled rejection

✅ Correct — always catch and exit on initial connection failure:

try {
  await mongoose.connect(process.env.MONGODB_URI);
} catch (err) {
  console.error(err.message);
  process.exit(1); // cannot operate without database ✓
}

Mistake 3 — Calling mongoose.connect() in every model file

❌ Wrong — each model file calls connect() creating multiple connections:

// models/Post.js
mongoose.connect(process.env.MONGODB_URI); // creates a NEW connection
// models/User.js
mongoose.connect(process.env.MONGODB_URI); // another NEW connection
// Result: multiple connections, potential pool exhaustion

✅ Correct — connect once in index.js or db.js and share the singleton.

Quick Reference

Task Code
Connect await mongoose.connect(process.env.MONGODB_URI)
Get host mongoose.connection.host
Check state mongoose.connection.readyState === 1
Close connection await mongoose.connection.close()
Listen for errors mongoose.connection.on('error', handler)
Graceful shutdown process.on('SIGINT', async () => { await mongoose.connection.close() })

🧠 Test Yourself

Your Express server starts successfully and begins accepting requests. Two seconds later all Mongoose queries start throwing MongoNotConnectedError. What is the most likely cause?