Express.js is the “E” in MERN and the technology responsible for your application’s server-side logic. It sits between React and MongoDB — receiving HTTP requests from the browser, running middleware for authentication and validation, querying the database, and returning JSON responses. Express is intentionally minimal: it provides only what you need to handle HTTP without imposing an architecture on you. In this lesson you will understand exactly what Express does, why it exists on top of Node.js, and how it will serve as the REST API backbone of your MERN application.
What Problem Does Express Solve?
Node.js ships with a built-in http module that can create a server, but writing an entire API with it is tedious. Express wraps that module and provides clean abstractions for the things every API needs:
| Raw Node.js http | Express equivalent |
|---|---|
Parse URL manually from req.url |
app.get('/api/posts', handler) — routing built in |
| Parse JSON body manually | app.use(express.json()) — one line |
| Set response headers manually | res.json(data) sets Content-Type automatically |
| No middleware concept | app.use(middleware) — composable pipeline |
| No router organisation | express.Router() — modular route files |
next(err) manually. If you are starting a new project, use Express 5: npm install express@5. Existing Express 4 code is largely compatible.express-validator or joi before saving it to the database. Never trust client-supplied data.Your First Express Server
// server/index.js
const express = require('express');
const cors = require('cors');
const app = express();
const PORT = process.env.PORT || 5000;
// ── Middleware ────────────────────────────────────────────────
app.use(cors()); // allow requests from React dev server
app.use(express.json()); // parse JSON request bodies
// ── Routes ────────────────────────────────────────────────────
app.get('/api/health', (req, res) => {
res.json({ status: 'ok', message: 'MERN API is running' });
});
app.get('/api/posts', (req, res) => {
// We will connect this to MongoDB with Mongoose later
res.json({ success: true, data: [] });
});
// ── Start server ──────────────────────────────────────────────
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
Anatomy of an Express Route Handler
app.get('/api/posts/:id', async (req, res) => {
// ▲ ▲ ▲ ▲
// │ │ │ └─ res: send back a response
// │ │ └───────── req: incoming request object
// │ └────────────────────────── route path with :id parameter
// └───────────────────────────────── HTTP method (GET)
const { id } = req.params; // URL parameter → /api/posts/abc123
const search = req.query.q; // Query string → /api/posts?q=mern
const body = req.body; // Request body → JSON payload (POST/PUT)
// After processing:
res.status(200).json({ success: true, data: {} }); // send JSON
// or
res.status(404).json({ success: false, message: 'Not found' });
});
The Middleware Pipeline
Express processes every request through a pipeline of middleware functions. Each middleware receives req, res, and next. Calling next() passes the request to the next middleware in the chain.
Incoming Request
│
▼
[cors middleware] → adds CORS headers
│
▼
[express.json()] → parses request body from JSON string to object
│
▼
[auth middleware] → verifies JWT token, attaches user to req.user
│
▼
[route handler] → business logic + database query
│
▼
[error middleware] → catches any errors thrown above
│
▼
JSON Response sent to browser
HTTP Methods and REST Conventions
| HTTP Method | Express Method | REST Convention | Example Endpoint |
|---|---|---|---|
| GET | app.get() |
Read — retrieve data | GET /api/posts |
| POST | app.post() |
Create — add new data | POST /api/posts |
| PUT | app.put() |
Update — replace entire resource | PUT /api/posts/:id |
| PATCH | app.patch() |
Update — modify partial resource | PATCH /api/posts/:id |
| DELETE | app.delete() |
Delete — remove a resource | DELETE /api/posts/:id |
Express Project Structure for MERN
server/
├── index.js ← entry point — creates app, connects DB, starts server
├── .env ← environment variables (never commit to Git)
├── package.json
└── src/
├── config/
│ └── db.js ← Mongoose connection
├── models/
│ └── Post.js ← Mongoose schema and model
├── routes/
│ └── posts.js ← Express Router for /api/posts
├── controllers/
│ └── postController.js ← route handler functions
└── middleware/
├── auth.js ← JWT verification middleware
└── errorHandler.js ← global error middleware
Common Mistakes
Mistake 1 — Putting all routes in index.js
❌ Wrong — one giant file with all routes and logic mixed together:
// index.js — 500+ lines of mixed routes, DB logic, and middleware
app.get('/api/posts', ...);
app.post('/api/posts', ...);
app.get('/api/users', ...);
app.post('/api/auth/login', ...);
✅ Correct — separate each resource into its own router file and mount it:
// index.js — clean and organised
app.use('/api/posts', require('./src/routes/posts'));
app.use('/api/users', require('./src/routes/users'));
app.use('/api/auth', require('./src/routes/auth'));
Mistake 2 — Forgetting express.json() middleware
❌ Wrong — req.body is undefined because the JSON parser was not added:
app.post('/api/posts', (req, res) => {
console.log(req.body); // undefined — express.json() is missing!
});
✅ Correct — always add express.json() before your route definitions:
app.use(express.json()); // ← must come before routes
app.post('/api/posts', (req, res) => {
console.log(req.body); // { title: '...', body: '...' } ✓
});
Mistake 3 — No error handling middleware
❌ Wrong — unhandled async errors crash the server or return an unhelpful HTML error page to React:
app.get('/api/posts', async (req, res) => {
const posts = await Post.find(); // if this throws — no catch!
res.json(posts);
});
✅ Correct — use try/catch (Express 4) or rely on Express 5’s built-in async error handling, and always add a global error middleware:
// Global error middleware — must have 4 parameters
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ success: false, message: err.message });
});
Quick Reference
| Task | Express Code |
|---|---|
| Create app | const app = express() |
| Parse JSON bodies | app.use(express.json()) |
| Enable CORS | app.use(cors()) |
| Define GET route | app.get('/path', handler) |
| URL parameter | req.params.id |
| Query string | req.query.search |
| Request body | req.body |
| Send JSON response | res.status(200).json(data) |
| Create router | const router = express.Router() |
| Start server | app.listen(PORT, callback) |