Routing and Middleware
Routing and Middleware
As your Express application grows, keeping all routes in one index.js file becomes unmanageable. Express provides a Router that lets you split routes into separate files. And middleware is the mechanism that makes it possible to run shared logic — like logging, authentication, and validation — across multiple routes without duplicating code.
What is Middleware?
Middleware is any function that has access to the request (req), response (res), and the next function in Express's request-response cycle. Middleware functions execute in sequence, and each one either ends the response or calls next() to pass control to the next function.
// A middleware function signature
function myMiddleware(req, res, next) {
// Do something with the request or response
console.log('Request received:', req.method, req.url);
next(); // Pass control to the next middleware or route handler
}
Middleware can:
- Execute any code
- Modify the
reqandresobjects - End the request-response cycle
- Call the next middleware function
Types of Middleware
Application-Level Middleware
Applied to all routes or a subset of routes using app.use():
const express = require('express');
const app = express();
// Applied to ALL routes
app.use(express.json()); // Built-in middleware to parse JSON bodies
// Custom logger — runs on every request
app.use((req, res, next) => {
const timestamp = new Date().toISOString();
console.log(`[${timestamp}] ${req.method} ${req.url}`);
next();
});
// Applied only to routes starting with /api
app.use('/api', (req, res, next) => {
console.log('API request detected');
next();
});
Route-Level Middleware
Applied to specific routes only:
// Authentication middleware
function requireAuth(req, res, next) {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({ error: 'Unauthorised — no token provided' });
}
// In a real app, you would verify the token here
next();
}
// Only the /profile route requires authentication
app.get('/profile', requireAuth, (req, res) => {
res.json({ message: 'Your private profile' });
});
// This route is public
app.get('/about', (req, res) => {
res.json({ message: 'About us page' });
});
Express Router for Modular Routes
// routes/users.js
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => {
res.json({ users: [] });
});
router.get('/:id', (req, res) => {
res.json({ user: { id: req.params.id } });
});
router.post('/', (req, res) => {
const { name, email } = req.body;
res.status(201).json({ user: { id: Date.now(), name, email } });
});
router.put('/:id', (req, res) => {
res.json({ message: `User ${req.params.id} updated` });
});
router.delete('/:id', (req, res) => {
res.json({ message: `User ${req.params.id} deleted` });
});
module.exports = router;
// index.js — mount the router
const express = require('express');
const userRoutes = require('./routes/users');
const app = express();
app.use(express.json());
// Mount users router at /api/users
// All routes in userRoutes are now prefixed with /api/users
app.use('/api/users', userRoutes);
app.listen(3000);
// GET /api/users -> users router '/'
// GET /api/users/42 -> users router '/:id'
// POST /api/users -> users router '/'
Error-Handling Middleware
Error-handling middleware takes four parameters: err, req, res, next. It must be defined after all other middleware and routes.
// Centralised error handler — place this LAST in your middleware stack
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(err.status || 500).json({
error: err.message || 'Internal Server Error'
});
});
// Triggering the error handler
app.get('/fail', (req, res, next) => {
const error = new Error('Something went wrong');
error.status = 500;
next(error); // Pass the error to the error handler
});
Third-Party Middleware
npm install cors morgan helmet
const cors = require('cors'); // Cross-Origin Resource Sharing
const morgan = require('morgan'); // HTTP request logger
const helmet = require('helmet'); // Secure HTTP headers
app.use(helmet()); // Set security headers
app.use(cors()); // Allow cross-origin requests
app.use(morgan('dev')); // Log: GET /api/users 200 5.234 ms - 87
Key Takeaways
- Middleware functions run in sequence and must call
next()to pass control to the next function. - Use
app.use()to apply middleware globally and pass it as a second argument to specific routes. - Express Router enables modular, organised route files — one file per resource.
- Error-handling middleware takes four parameters and must be placed last in the stack.
- Third-party middleware like
cors,morgan, andhelmethandle common concerns out of the box.
Practice Exercise
- Create a
routes/directory and move your user routes intoroutes/users.js. - Create
routes/products.jswith at least GET all and GET by ID routes. - Mount both routers in
index.jsat/api/usersand/api/products. - Add a request logger middleware that logs the method, URL, and response time for every request.
- Add a catch-all 404 handler: if no route matches, return
{ error: "Not found" }with status 404.
Try it yourself
Key Takeaways
- Middleware functions execute in sequence and must call next() to continue the pipeline or end the response.
- Application-level middleware applies globally; route-level middleware applies to specific endpoints.
- Express Router enables modular route files — group routes by resource and mount them with a prefix.
- Error-handling middleware takes four parameters (err, req, res, next) and must be placed last.
- Third-party packages like helmet, cors, and morgan handle common cross-cutting concerns with a single line.
Quick Quiz
1.What must a middleware function call to pass control to the next middleware in the stack?
2.How many parameters does error-handling middleware in Express take?
3.What does Express Router allow you to do?
4.Which package adds security-related HTTP headers to your Express app?
Ready to go further?
CareerEx gives you structured 12-week training, live classes every Saturday and Sunday, real tutor feedback, and a certificate. Join the next cohort.
Join CareerEx