What is Middleware in Express and How It Works
Also onpreet-jain.hashnode.dev/what-is-middleware-in-expressHere's a mental model for Express: every incoming request travels through a pipeline before reaching your route handler. That pipeline is middleware. It's the checkpoint system between a user hitting your API and your code deciding what to send back.
Without middleware, you'd cram everything into your route handlers. Logging, authentication, validation, error handling — all of it mixed together in every route. Middleware lets you separate concerns and keep your route handlers focused on what they're actually supposed to do.
The Request Pipeline
Think of middleware like a series of security checkpoints at an airport. A request passes through each checkpoint one by one. Each checkpoint can inspect the request, modify it, or decide the request isn't allowed to proceed.
In Express terms:
Request → Middleware 1 → Middleware 2 → Route Handler → ResponseIf a middleware calls next(), the request moves to the next step. If it sends a response directly (like returning a 401 for unauthorized users), the pipeline stops there.
Application-Level Middleware
This is middleware that runs on every request to your app, regardless of the route.
const express = require('express');
const app = express();
// This runs on every single request
app.use((req, res, next) => {
console.log(`\({req.method} request to \){req.path}`);
next();
});app.use() attaches middleware to your application. It fires on every request that matches its path (or all paths if no path is specified).
Router-Level Middleware
Router-level middleware works the same way but is scoped to a specific router instance. Useful when you want certain middleware to only apply to a group of routes.
const express = require('express');
const router = express.Router();
router.use((req, res, next) => {
console.log('Admin route accessed');
next();
});
router.get('/dashboard', (req, res) => {
res.send('Admin Dashboard');
});
module.exports = router;Then mount it in your main app:
const adminRouter = require('./routes/admin');
app.use('/admin', adminRouter);Now the router-level middleware only runs for requests starting with /admin.
Built-in Middleware
Express ships with a few built-in middleware functions:
const express = require('express');
const app = express();
// Parse JSON request bodies
app.use(express.json());
// Serve static files from a folder
app.use(express.static('public'));express.json() parses incoming requests with a Content-Type of application/json. express.static() serves files from a directory without needing route handlers for each file.
Execution Order Matters
This is where developers get tripped up. Middleware executes top to bottom, and first match wins for route-level matching.
app.use(middlewareA);
app.use(middlewareB);
app.get('/route', handler);
// Order of execution for GET /route:
// middlewareA → middlewareB → handlerIf you swap the order:
app.get('/route', handler);
app.use(middlewareA);
app.use(middlewareB);
// Order of execution for GET /route:
// handler only (middleware runs AFTER the route was already matched)The route matches first, then middleware attached after doesn't run for that route. Middleware placement matters.
The next() Function
Every middleware function receives three arguments: req, res, and next. Calling next() tells Express to move to the next middleware in the chain. Not calling it? The request hangs — the client waits forever.
app.use((req, res, next) => {
if (!req.headers.authorization) {
return res.status(401).send('Unauthorized');
}
next();
});In this example, unauthorized requests never reach next(). The response gets sent immediately and the pipeline stops.
Real-World Middleware Examples
Logging middleware — Capture request details for debugging:
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`\({req.method} \){req.path} - \({res.statusCode} (\){duration}ms)`);
});
next();
});Authentication middleware — Block unauthenticated users:
function authenticate(req, res, next) {
const token = req.headers.authorization;
if (verifyToken(token)) {
req.user = getUserFromToken(token);
next();
} else {
res.status(401).json({ error: 'Invalid token' });
}
}
app.get('/profile', authenticate, (req, res) => {
res.json({ user: req.user });
});Notice how the route handler doesn't check authentication itself. The middleware handles it. The handler just assumes req.user exists.
Request validation — Make sure incoming data looks right:
app.use((req, res, next) => {
if (req.method === 'POST' && !req.body.email) {
return res.status(400).json({ error: 'Email is required' });
}
next();
});Wrapping Up
Middleware is Express's way of organizing the logic that happens before your route handlers run. It lets you extract cross-cutting concerns — logging, auth, validation — into reusable pieces that plug into the request pipeline.
The key concepts to internalize: middleware functions take req, res, and next; order of declaration determines order of execution; and calling next() moves the request forward. Once those click, building and debugging Express apps becomes much more intuitive.