Ever wondered what actually happens between your request leaving the client and the server sending something back? That hidden “in-between” magic is middleware.
Middleware is one of those things in Express that sounds complicated at first… but once it clicks, everything in backend development suddenly makes more sense.
Day 25 was all about understanding what middleware is and how we use it
🍽️ Middleware (Restaurant analogy)
Think of a restaurant:
- You = the client (making a request)
- The kitchen = the server (sending a response)
- The waiter = middleware
The waiter:
- Takes your order
- Passes it to the kitchen
- Brings food back
- AND can do extra checks (Are you allowed here? Is your order valid?)
That’s exactly what middleware does.
Middleware is simply a function that sits between the request and the response.
It can:
- ▶ Authenticate users (“Is this user logged in?”)
- ▶ Log incoming requests
- ▶ Catch errors
- ▶ Modify or validate data
- ▶ Handle permissions
- ▶ Serve static files
- ▶ Parse request bodies
🤷♂️ Why Do We Need Middleware?
Without middleware, you'd repeat the same logic in every route.
Middleware helps you:
👉 Reuse logic
Write it once → apply everywhere.
👉 Keep code clean
Your route handlers stay focused on actual business logic.
👉 Create pipelines
Middleware stacks like:
Check auth → validate data → run controller → format response
What Does Middleware Look Like?
A middleware function always receives three arguments:
(req, res, next) => {}
- req → what the client sent
- res → what you will send back
- next → passes control to the next middleware
Example:
app.use((req, res, next) => {
console.log("Someone made a request!");
next(); // moves to the next middleware
});
Naming doesn’t matter (you can call them a, b, c),
but the order matters:
first = request, second = response, last = next.
🧱 Types of Middleware in Express
Below are all major types of middleware you’ll use.
1. Application-Level Middleware
Runs for every request OR for specific paths.
app.use((req, res, next) => next()); // global
app.use('/admin', checkAuth); // route-specific
2. Route-Level Middleware
Attached directly to specific routes:
app.get('/dashboard', checkAuth, (req, res) => {
res.send('Dashboard');
});
3. Built-in Middleware (Comes With Express)
express.json()
Parses JSON request bodies.
app.use(express.json());
express.urlencoded()
Parses form data (from HTML forms).
app.use(express.urlencoded({ extended: true }));
express.static()
Serves static assets (HTML, CSS, JS, images).
app.use(express.static('public'));
express.raw()
Parses raw binary data.
app.use(express.raw());
express.text()
Parses plain text requests.
app.use(express.text());
4. Third-Party Middleware
⭐ Morgan → HTTP Logging
npm install morgan
app.use(require('morgan')('dev'));
⭐ CORS → Cross-Origin Requests
npm install cors
app.use(require('cors')());
⭐ Helmet → Security Headers
npm install helmet
app.use(require('helmet')());
⭐ express-rate-limit → Rate Limiting
app.use(rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }));
⭐ cookie-parser → Cookie Parsing
app.use(cookieParser());
console.log(req.cookies);
How Express Processes Middleware (Order Matters!)
Express runs middleware top to bottom in the order they appear in your code.
Incoming Request
↓
Middleware 1 → next()
↓
Middleware 2 → next()
↓
Middleware 3 → res.send()
↓
Response Out
Example:
app.use(logger); // 1st
app.use(express.json()); // 2nd
app.use(checkAuth); // 3rd
app.get('/dashboard', (req, res) => res.send('hi')); // 4th
If you place
checkAuthbeforeexpress.json(), the body won’t be parsed yet.
This is why middleware order is CRUCIAL.
❌ Error Handling Middleware (Special Type)
Error handlers are detected by Express because they have four arguments:
app.use((err, req, res, next) => {
res.status(500).send("Something broke!");
});
🤔 When does it run?
Whenever you call:
next(error)
Example:
app.get('/user', (req, res, next) => {
try {
throw new Error('User not found');
} catch (err) {
next(err);
}
});
✨ Best Practices
- Always put error-handling middleware at the end
- Always include all 4 arguments
- Use
next(err)to trigger it
Example with custom message + status:
app.use((err, req, res, next) => {
res.status(err.status || 500).json({
success: false,
message: err.message || 'Internal Server Error'
});
});
And a custom error:
app.get('/user/:id', (req, res, next) => {
const error = new Error('User not found');
error.status = 404;
next(error);
});
Full Flow:
Route throws error → next(err) → skip normal middleware → Error handler → Response
🏁 Conclusion
- Middleware = function between request and response
-
next()= is what moves the chain forward - Order matters a lot
- Built-in middlewares handle parsing + static files
- Third-party middlewares add security, logging, rate limits, etc.
- Error handlers use four parameters, always last in the file
Happy coding!
Top comments (0)