What is Middleware in Express and How It Works

Middleware sits between incoming requests and your route handlers. It intercepts requests, does something with them, and either passes them along or stops them. Understanding middleware is key to building Express applications.
What Is Middleware?
Middleware is a function that has access to the request and response objects. It can modify them, perform checks, or stop the request.
Think of middleware as checkpoints in an airport:
Security check (middleware validates the request)
Baggage scan (middleware processes data)
Gate (final destination/route handler)
Each checkpoint can allow you through to the next, or stop you.
In Express, middleware functions have this signature:
function middleware(req, res, next) {
// Do something
next(); // Pass to next middleware
}
The next() function passes control to the next middleware or route handler.
How Middleware Works
When a request arrives, Express processes it through middleware in order:
Request comes in
|
v
Middleware 1 (if next() called, continue)
|
v
Middleware 2 (if next() called, continue)
|
v
Route Handler (your actual route)
|
v
Response sent
Each middleware either calls next() to continue or sends a response to stop.
Using Express-Provided Middleware
Express provides built-in middleware for common tasks:
import express from 'express';
const app = express();
// Parse JSON requests
app.use(express.json());
// Parse URL-encoded form data
app.use(express.urlencoded({ extended: true }));
// Serve static files
app.use(express.static('public'));
app.post('/data', (req, res) => {
// Thanks to express.json() middleware, req.body is parsed
console.log(req.body);
res.send('Data received');
});
app.listen(3000);
express.json() middleware automatically parses incoming JSON and populates req.body.
Creating Custom Middleware
Write your own middleware for custom logic:
import express from 'express';
const app = express();
// Custom logging middleware
function logger(req, res, next) {
console.log(`\({req.method} \){req.url}`);
next(); // Pass to next middleware
}
app.use(logger);
app.get('/', (req, res) => {
res.send('Home page');
});
app.listen(3000);
Every request is logged, then passed to the route handler.
Output for GET /:
GET /
Middleware Execution Order
Middleware runs in the order you define it:
import express from 'express';
const app = express();
app.use((req, res, next) => {
console.log('1: First middleware');
next();
});
app.use((req, res, next) => {
console.log('2: Second middleware');
next();
});
app.get('/', (req, res) => {
console.log('3: Route handler');
res.send('Done');
});
app.listen(3000);
For a GET / request, output is:
1: First middleware
2: Second middleware
3: Route handler
Order matters. Middleware defined first runs first.
The next() Function
Calling next() passes control to the next function in the chain. Not calling it stops execution:
app.use((req, res, next) => {
console.log('Middleware 1');
next(); // Continue
});
app.use((req, res, next) => {
console.log('Middleware 2');
// Not calling next() - stops here
});
app.get('/', (req, res) => {
console.log('Route'); // Never reached
res.send('Hi');
});
This stops at "Middleware 2". The route handler is never called.
Types of Middleware
Application-Level Middleware
Runs for all routes or specific routes:
import express from 'express';
const app = express();
// Runs for ALL routes
app.use((req, res, next) => {
console.log('Global middleware');
next();
});
// Runs only for /admin routes
app.use('/admin', (req, res, next) => {
console.log('Admin middleware');
next();
});
app.get('/', (req, res) => res.send('Home'));
app.get('/admin/users', (req, res) => res.send('Admin page'));
app.listen(3000);
Router-Level Middleware
Middleware on a specific router:
import express from 'express';
const app = express();
const router = express.Router();
router.use((req, res, next) => {
console.log('Router middleware');
next();
});
router.get('/users', (req, res) => res.send('Users'));
app.use('/api', router);
app.listen(3000);
Built-In Middleware
Express provides standard middleware:
app.use(express.json());
app.use(express.static('public'));
app.use(express.urlencoded({ extended: true }));
Third-Party Middleware
Use packages from npm:
npm install cors
import cors from 'cors';
app.use(cors()); // Enable CORS
Real-World Middleware Examples
Authentication Middleware
function authenticateToken(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).send('No token');
}
// Verify token (simplified)
if (token === 'valid-token') {
req.user = { id: 1 };
next();
} else {
res.status(403).send('Invalid token');
}
}
app.get('/protected', authenticateToken, (req, res) => {
res.send(`Hello user ${req.user.id}`);
});
Error Handling Middleware
app.get('/data', (req, res, next) => {
try {
throw new Error('Something went wrong');
} catch (error) {
next(error); // Pass to error handler
}
});
// Error handling middleware (must have 4 parameters)
app.use((err, req, res, next) => {
console.error(err.message);
res.status(500).send('Server error');
});
Request Timing Middleware
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`\({req.method} \){req.url} - ${duration}ms`);
});
next();
});
Middleware with Parameters
Middleware factories return middleware functions:
function logMessages(format) {
return (req, res, next) => {
if (format === 'simple') {
console.log(`\({req.method} \){req.url}`);
} else if (format === 'detailed') {
console.log(`\({req.method} \){req.url} - ${req.headers['user-agent']}`);
}
next();
};
}
app.use(logMessages('simple'));
// or
app.use(logMessages('detailed'));
Complete Example with Multiple Middleware
import express from 'express';
const app = express();
// Built-in middleware
app.use(express.json());
// Custom logging middleware
app.use((req, res, next) => {
console.log(`[\({new Date().toISOString()}] \){req.method} ${req.url}`);
next();
});
// Authentication middleware
function authenticate(req, res, next) {
const token = req.query.token;
if (token === 'secret') {
req.user = { id: 1, name: 'John' };
next();
} else {
res.status(401).send('Unauthorized');
}
}
// Request validation middleware
function validateRequest(req, res, next) {
if (!req.body.email) {
return res.status(400).send('Email required');
}
next();
}
// Public route
app.get('/home', (req, res) => {
res.send('Welcome');
});
// Protected route
app.get('/profile', authenticate, (req, res) => {
res.send(`Hello ${req.user.name}`);
});
// Route with validation
app.post('/register', validateRequest, (req, res) => {
res.send(`Registered ${req.body.email}`);
});
// Error handling middleware (must be last)
app.use((err, req, res, next) => {
console.error(err);
res.status(500).json({ error: 'Server error' });
});
app.listen(3000, () => {
console.log('Server running');
});
Common Mistakes
Mistake 1: Forgetting to call next()
// Wrong - middleware stops here
app.use((req, res, next) => {
console.log('Middleware');
// No next() call - route never executes
});
app.get('/', (req, res) => {
res.send('Hello'); // Never reached
});
// Right
app.use((req, res, next) => {
console.log('Middleware');
next(); // Passes to next handler
});
Mistake 2: Wrong middleware order
// Wrong - middleware after route can't help
app.get('/', (req, res) => {
res.send(req.body); // undefined - JSON not parsed yet
});
app.use(express.json()); // Too late!
// Right - middleware before routes
app.use(express.json());
app.get('/', (req, res) => {
res.send(req.body); // Properly parsed
});
Mistake 3: Error handler must have 4 parameters
// Wrong - Express won't recognize as error handler
app.use((err, req, res) => {
res.send('Error');
});
// Right - all 4 parameters required
app.use((err, req, res, next) => {
res.send('Error');
});
Key Takeaways
Middleware intercepts requests and can modify them or stop them
Middleware runs in the order it's defined
Call
next()to pass to the next middlewareExpress provides built-in middleware like
express.json()You can create custom middleware for any task
Application-level middleware runs for all/specific routes
Error handlers are middleware with 4 parameters
Order matters: define middleware before routes that need it
Middleware is the backbone of Express applications. Master it, and you can build clean, modular, and powerful web applications.




