Your server can now talk HTTP. Next it must talk to MongoDB. You could use the official driver, which sends raw commands, but most MERN projects use Mongoose, a library on top of the driver. Mongoose adds three things you will want: schemas (rules for what a document must look like), models (objects that have methods like find and create), and helpful validation and error messages. This lesson only makes the connection. The next three use it.
The connection string
# Copy this file to .env and fill it in. Never commit .env to Git.
PORT=5000
CLIENT_ORIGIN=http://localhost:5173
# Atlas: mongodb+srv://USER:PASSWORD@cluster0.xxxxx.mongodb.net/job_tracker
# Local: mongodb://127.0.0.1:27017/job_tracker
MONGODB_URI=mongodb://127.0.0.1:27017/job_tracker
A connection string is an address plus credentials plus the database name. Atlas gives you one starting with mongodb+srv://. A local one looks like mongodb://127.0.0.1:27017/job_tracker. The last part, after the final slash, is the database name; if it does not exist MongoDB creates it on first write. Put the real value in server/.env, and restart the server, because .env is not watched.
Connecting
import mongoose from 'mongoose';
export async function connectDB(uri = process.env.MONGODB_URI) {
if (!uri) throw new Error('MONGODB_URI is missing. Copy .env.example to .env and set it.');
mongoose.connection.on('disconnected', () => console.warn('MongoDB disconnected'));
// serverSelectionTimeoutMS: fail in 8 seconds with a clear error instead of hanging for 30
await mongoose.connect(uri, { serverSelectionTimeoutMS: 8000 });
console.log(`MongoDB connected: ${mongoose.connection.name}`);
}
mongoose.connect(uri, options) returns a promise. We wrap it in a function so the main file stays short and so tests (lesson 24) can pass a different address. The option serverSelectionTimeoutMS: 8000 is a kindness to yourself: without it, a wrong address or a blocked IP makes the program hang for about 30 seconds before failing. With it you see the real error in 8 seconds. The disconnected listener logs when the connection drops, for example when your Wi-Fi does.
Start listening only when the database is ready
// Lesson 15: connect to MongoDB with Mongoose before we start listening.
import 'dotenv/config';
import express from 'express';
import cors from 'cors';
import mongoose from 'mongoose';
import { connectDB } from './config/db.js';
import { logger } from './middleware/logger.js';
import { notFound, errorHandler } from './middleware/error.js';
import jobsRouter from './routes/jobs.js';
const app = express();
const port = Number(process.env.PORT) || 5000;
app.use(logger);
app.use(cors({ origin: process.env.CLIENT_ORIGIN || 'http://localhost:5173' }));
app.use(express.json({ limit: '100kb' }));
// readyState 1 means connected
app.get('/api/health', (req, res) => res.json({ ok: true, db: mongoose.connection.readyState === 1 ? 'connected' : 'not connected' }));
app.use('/api/jobs', jobsRouter);
app.use(notFound);
app.use(errorHandler);
try {
await connectDB();
app.listen(port, () => console.log(`API listening on http://localhost:${port}`));
} catch (err) {
console.error('Could not start:', err.message);
process.exit(1);
}
The server used to start with app.listen straight away. Now it first awaits connectDB() and only then listens, inside a try/catch. If the database is not available, the program prints a clear message and exits with process.exit(1), instead of accepting requests it cannot serve. This fail fast habit is much better than a server that is “up” but returns errors on every call. (Top-level await works here because the project uses ES modules.)
The health route now reports mongoose.connection.readyState. The value 1 means connected; 0 disconnected, 2 connecting, 3 disconnecting. Monitoring tools can call this route to know that the whole app, not only the web server, is healthy. In lesson 37 it will answer 503 when the database is down.
Common connection problems
- “bad auth” or “Authentication failed”: the user name or password is wrong, or the password contains special characters that must be URL-encoded (
@becomes%40). - Timeout or “ECONNREFUSED”: for Atlas, your IP is not in Network Access; for local MongoDB, the service is not running.
- It connects but you see no data in Atlas: you are looking at a different database name than the one in the string.
Your task
- Run
npm installinserver(it adds Mongoose) and put your connection string inserver/.envasMONGODB_URI. - Start the server and call
/api/health. You should seedb: connected. - Break the string on purpose (wrong password). How long does it take to fail, and what does it print?
- Stop your local MongoDB (or remove your IP from Atlas) while the server runs and watch the log.
How to check it worked
- The terminal prints “MongoDB connected: job_tracker”.
/api/healthanswersdb: connected.- A wrong string makes the program exit within about 8 seconds with a clear message.
Summary
- Mongoose adds schemas, models and validation on top of the MongoDB driver.
- The connection string lives in
.env; the part after the last slash is the database name. - Connect first, listen second, and exit with a clear message if the database is down.