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;
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.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.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 |