Imagine you're running a sushi restaurant. Each order starts at the counter, gets prepared by the chef, checked by the quality inspector, and finally plated by the server before reaching the customer. At each station, someone can modify the order, reject it, or pass it forward. Express middlewares work exactly like this assembly line.
What exactly is a middlewareWhat is middleware?A function that runs between receiving a request and sending a response. It can check authentication, log data, or modify the request before your main code sees it.?
Simply put, a middleware is a function that sits between the incoming request and your final route handlerWhat is route handler?A Next.js file named route.js inside the app/ directory that handles HTTP requests directly - the App Router equivalent of API routes.. It has access to three things:
req: the request object (what the client sent)res: the response object (what you'll send back)next: a function to pass control to the next middleware
Here's what it looks like:
function myMiddleware(req, res, next) {
// Do something with the request
console.log('Request received:', req.method, req.url);
// Pass to the next middleware in the chain
next();
}
// Or with arrow functions (more common)
const logger = (req, res, next) => {
console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
next();
};next(), your request will hang indefinitely. The client will wait... and wait... until it times out. Express won't automatically move to the next middleware, you're in control.Three types of middlewares
Not all middlewares are created equal. Express gives you three ways to use them, depending on how broadly you want to apply them.
Application-level middlewares
These apply to your entire Express app. They're the most common and get registered with app.use().
// This logger runs on EVERY request to your app
app.use(logger);
// This auth middleware only runs on routes starting with /api
app.use('/api', authMiddleware);Think of application-level middlewares as your restaurant's house rules, they apply to everyone walking through the door, or to specific sections of the restaurant.
Router-level middlewares
Sometimes you want middlewares that only apply to a specific group of routes. That's where Express routers come in.
const userRouter = express.Router();
// This validation only applies to user routes
userRouter.use(validateUserData);
userRouter.get('/', getAllUsers);
userRouter.post('/', createUser);
userRouter.put('/:id', updateUser);
// Mount the router
app.use('/users', userRouter);It's like having a special prep station just for vegetarian dishes, the validation rules only apply there, not to the entire kitchen.
Route-specific middlewares
The most granular option: middlewares attached to a single route.
// These middlewares only run for GET /admin
app.get('/admin', requireAuth, requireAdmin, (req, res) => {
res.json({ message: 'Welcome to the admin area' });
});In our restaurant analogy, this is like requiring a manager's approval specifically for refunds, not for every transactionWhat is transaction?A group of database operations that either all succeed together or all fail together, preventing partial updates., just this specific action.
The execution order matters
Here's where things get interesting. Middlewares don't just run, they run in order. The sequence in which you declare them determines the sequence in which they execute.
// First middleware
app.use((req, res, next) => {
console.log('1. Logger middleware');
next();
});
// Second middleware (only for /admin)
app.use('/admin', (req, res, next) => {
console.log('2. Auth middleware');
next();
});
// Route handler
app.get('/admin', (req, res) => {
console.log('3. Route handler');
res.send('Admin page');
});When you make a GET request to /admin, the console will show:
1. Logger middleware
2. Auth middleware
3. Route handlerBut if you request /public, you'll only see:
1. Logger middlewareStopping the chain
Middlewares don't have to pass control forward. They can stop the chain entirely by sending a response. This is how authenticationWhat is authentication?Verifying who a user is, typically through credentials like a password or token. works.
const requireAuth = (req, res, next) => {
const token = req.headers.authorization;
// No token? Stop right here.
if (!token) {
return res.status(401).json({
error: 'Authentication required'
});
}
// Token exists? Continue to the next middleware
next();
};
// Protected route
app.get('/dashboard', requireAuth, (req, res) => {
res.json({ message: 'Secret dashboard data' });
});Notice the return before res.status(401). Without it, your function would continue executing after sending the response, potentially causing errors. Always return when you send an error response in a middlewareWhat is middleware?A function that runs between receiving a request and sending a response. It can check authentication, log data, or modify the request before your main code sees it..
Modifying requests and responses
One of the most powerful features of middlewares is their ability to attach data to the request object. Since the same req object flows through the entire chain, data added by one middlewareWhat is middleware?A function that runs between receiving a request and sending a response. It can check authentication, log data, or modify the request before your main code sees it. is available to all subsequent ones.
// Middleware that adds timing data
app.use((req, res, next) => {
req.timestamp = Date.now();
next();
});
// Middleware that adds user info (from a token)
app.use((req, res, next) => {
const token = req.headers.authorization;
if (token) {
req.user = { id: '123', role: 'admin' };
}
next();
});
// Later in your routes, all this data is available
app.get('/data', (req, res) => {
console.log('Request started at:', req.timestamp);
console.log('User role:', req.user?.role);
res.json({ timestamp: req.timestamp });
});Think of req as a shared clipboard that travels down the assembly line. Each station can write on it, and every station downstream can read what's been written.
Quick reference: middlewareWhat is middleware?A function that runs between receiving a request and sending a response. It can check authentication, log data, or modify the request before your main code sees it. flow
| What happens | Code pattern | Result |
|---|---|---|
| Continue to next middleware | next() | Request proceeds |
| End with success | res.json() or res.send() | Response sent to client |
| End with error | return res.status().json() | Error response, chain stops |
| Modify request | req.customProperty = value | Data available downstream |
| Skip remaining middlewares | Don't call next() or res | Request hangs (bad!) |
Common middlewareWhat is middleware?A function that runs between receiving a request and sending a response. It can check authentication, log data, or modify the request before your main code sees it. patterns
Here's a cheat sheet of what you'll build most often:
// Logging middleware
const logger = (req, res, next) => {
console.log(`${req.method} ${req.url}`);
next();
};
// Timing middleware
const timer = (req, res, next) => {
req.startTime = Date.now();
res.on('finish', () => {
console.log(`Request took ${Date.now() - req.startTime}ms`);
});
next();
};
// Simple auth check
const requireAuth = (req, res, next) => {
if (!req.headers.authorization) {
return res.status(401).json({ error: 'Unauthorized' });
}
next();
};That's the essence of middlewares. They're simple functions with extraordinary power. Once you understand this pattern, you've unlocked the secret to building clean, modular Express applications.