Connecting MongoDB in an Express Application

Wiring Mongoose into an Express application is straightforward โ€” but doing it correctly, with proper lifecycle management and environment-based configuration, separates a prototype from a production-ready server. In this lesson you will connect the database module to your Express entry point, verify the connection with a health endpoint, configure environment variables for both local and Atlas MongoDB, and test the full path from Postman through Express through Mongoose to MongoDB and back.

The Complete db.js Module

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

const connectDB = async () => {
  try {
    // Connection options (Mongoose 6+ defaults are sensible โ€” very few needed)
    const conn = await mongoose.connect(process.env.MONGODB_URI);
    console.log(`โœ“ MongoDB connected: ${conn.connection.host}`);
  } catch (err) {
    console.error(`โœ— MongoDB connection failed: ${err.message}`);
    process.exit(1); // Cannot run the API without a database
  }
};

// Connection lifecycle events
mongoose.connection.on('disconnected', () => {
  console.warn('MongoDB disconnected โ€” attempting reconnect...');
});

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

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

// Graceful shutdown
['SIGINT', 'SIGTERM'].forEach(signal => {
  process.on(signal, async () => {
    await mongoose.connection.close();
    console.log('MongoDB connection closed โ€” process exiting');
    process.exit(0);
  });
});

module.exports = connectDB;
Note: process.exit(1) in the catch block is intentional. An Express API that starts without a database connection will accept HTTP requests but fail on every single route that touches the database. It is better to crash immediately with a clear error message than to start successfully and then fail silently or unpredictably on the first real request. Deployment platforms like Render will restart the process automatically after a crash.
Tip: Add MONGODB_URI to your .env.example file with a placeholder value like mongodb://localhost:27017/blogdb. Anyone who clones your repository will immediately see what environment variable they need to set, and the placeholder reminds them to use their own database name โ€” not your production database.
Warning: MongoDB Atlas connection strings contain your username and password. Even if you use environment variables correctly in your code, a console.log(process.env.MONGODB_URI) anywhere in your codebase will print the full string including credentials to your server logs. Use mongoose.connection.host to log just the hostname after connecting, as shown in the example above.

Environment Configuration

// server/.env โ€” development (never commit)
PORT=5000
NODE_ENV=development
MONGODB_URI=mongodb://localhost:27017/blogdb
JWT_SECRET=dev_secret_change_in_production_to_64_char_random_string
JWT_EXPIRES_IN=7d
CLIENT_URL=http://localhost:5173
EMAIL_USER=your_email@gmail.com
EMAIL_PASS=your_gmail_app_password

// server/.env.example โ€” commit this file
PORT=5000
NODE_ENV=development
MONGODB_URI=mongodb://localhost:27017/your_db_name
JWT_SECRET=replace_with_64_char_random_string
JWT_EXPIRES_IN=7d
CLIENT_URL=http://localhost:5173
EMAIL_USER=your_email@provider.com
EMAIL_PASS=your_email_app_password

Verifying the Connection โ€” Health Endpoint

// server/index.js โ€” enhanced health check endpoint
app.get('/api/health', (req, res) => {
  const dbState = ['disconnected', 'connected', 'connecting', 'disconnecting'];
  res.json({
    status:      'ok',
    environment: process.env.NODE_ENV,
    database: {
      state:   dbState[mongoose.connection.readyState],
      host:    mongoose.connection.host || 'not connected',
    },
    timestamp:   new Date().toISOString(),
    uptime:      process.uptime(),
  });
});
Testing the health endpoint:
  GET http://localhost:5000/api/health

  Expected (connected):
  {
    "status": "ok",
    "environment": "development",
    "database": {
      "state": "connected",
      "host":  "localhost"
    },
    "timestamp": "2026-02-25T10:00:00.000Z",
    "uptime": 3.245
  }

  Expected (not connected โ€” server started before DB was ready):
  {
    "database": { "state": "disconnected", "host": "not connected" }
  }

Testing the Full DB Path with Postman

Step-by-step verification after connecting:

1. GET /api/health
   โ†’ Confirms Express is running and MongoDB is connected

2. POST /api/auth/register
   Body: { "name": "Test User", "email": "test@example.com", "password": "Test@1234" }
   โ†’ Confirms User.create() works, password hashing hook runs
   Expected: 201 { success: true, token: "eyJ...", data: { _id, name, email, role } }

3. POST /api/auth/login
   Body: { "email": "test@example.com", "password": "Test@1234" }
   โ†’ Confirms User.findOne(), comparePassword(), token generation
   Expected: 200 { success: true, token: "eyJ...", data: { ... } }

4. GET /api/posts
   โ†’ Confirms Post.find() with the soft-delete filter hook
   Expected: 200 { success: true, count: 0, data: [], total: 0 }

5. POST /api/posts (with Authorization: Bearer <token>)
   Body: { "title": "My First Post", "body": "Content here" }
   โ†’ Confirms auth middleware, Post.create(), slug hook
   Expected: 201 { success: true, data: { _id, title, slug, author, ... } }

6. GET /api/posts/<post_id>
   โ†’ Confirms Post.findById(), populate('author')
   Expected: 200 { success: true, data: { title, author: { name, avatar }, ... } }

Common Connection Errors and Fixes

Error Cause Fix
ECONNREFUSED 127.0.0.1:27017 Local MongoDB service not running brew services start mongodb-community or sudo systemctl start mongod
MongoServerSelectionError: timeout Atlas cluster unreachable or IP not allowlisted Check Atlas Network Access โ€” add your current IP
Authentication failed Wrong username/password in Atlas URI Check .env MONGODB_URI โ€” re-copy from Atlas with correct password
MongoParseError: Invalid connection string Malformed URI or special chars in password URL-encode special characters in password (@ = %40, # = %23)
MongoNotConnectedError Server started before connect() resolved Ensure await connectDB() before app.listen()

Common Mistakes

Mistake 1 โ€” Logging the full connection string (exposes credentials)

โŒ Wrong โ€” printing the URI to logs:

console.log('Connecting to:', process.env.MONGODB_URI);
// mongodb+srv://admin:mypassword@cluster.mongodb.net/db โ€” credentials in logs!

โœ… Correct โ€” log only the host after connecting:

const conn = await mongoose.connect(process.env.MONGODB_URI);
console.log(`Connected to: ${conn.connection.host}`); // cluster.mongodb.net only โœ“

Mistake 2 โ€” Using the same database for test, development, and production

โŒ Wrong โ€” all environments point to the same database:

MONGODB_URI=mongodb://localhost:27017/blogdb // used in all environments

โœ… Correct โ€” separate databases per environment:

// development .env
MONGODB_URI=mongodb://localhost:27017/blogdb_dev
// test .env
MONGODB_URI=mongodb://localhost:27017/blogdb_test
// production (Atlas)
MONGODB_URI=mongodb+srv://...mongodb.net/blogdb_prod

Mistake 3 โ€” Not passing the database name in the connection string

โŒ Wrong โ€” no database name in the URI:

MONGODB_URI=mongodb://localhost:27017
// Mongoose uses the 'test' database by default โ€” not your intended database

โœ… Correct โ€” always include the database name:

MONGODB_URI=mongodb://localhost:27017/blogdb // โœ“ explicitly specifies blogdb

Quick Reference

Task Code
Connect await mongoose.connect(process.env.MONGODB_URI)
Check state mongoose.connection.readyState === 1
Get host mongoose.connection.host
Graceful shutdown await mongoose.connection.close()
Start server after connect await connectDB(); app.listen(PORT)
Health check URL GET /api/health

🧠 Test Yourself

Your Express server starts successfully and logs “Server โ†’ http://localhost:5000” but every Mongoose query throws MongoNotConnectedError. What is the cause?