Introduction
Middleware are functions that run between receiving a request and sending a response. They can modify the request or response, end the request-response cycle, or call the next middleware function.
How to use middleware
We add middleware to our Express app using the app.use() method. This tells Express to run this function for every request.
The Request-Response Cycle
Request → Middleware 1 → Middleware 2 → Route Handler → Response
Basic middleware structure
import express, { Request, Response, NextFunction } from "express";
const app = express(); // Create the Express app
// Add middleware using app.use()
app.use((req: Request, res: Response, next: NextFunction) => {
// Do something with req or res
console.log(`${req.method} ${req.url}`);
// Call next() to continue to the next middleware
next();
});
What is next()?
nextis a function that tells Express to move to the next middleware or route handler- It’s like saying “I’m done with this middleware, go to the next one”
- If you don’t call
next(), the request stops here and never reaches your route handlers - If there’s no next middleware,
next()will call the route handler (likeapp.get('/', ...)orapp.post('/users', ...))
TypeScript note: We import NextFunction as well as Request and Response to demonstrate how we can type these parameters. Going forward, we’ll let TypeScript infer these types to keep our code clean and focused on middleware concepts.
Why do we need it?
- Express processes middleware in order, one after another
- Each middleware must decide whether to continue or stop the request
- Calling
next()passes control to the next middleware/route - Not calling
next()ends the request-response cycle
Example of what happens
Note: We’re not using TypeScript types in this example for brevity, but you should use them in your code.
1. First middleware runs, logs “First middleware”, calls next()
app.use((req, res, next) => {
console.log("First middleware");
next();
});
2. Second middleware runs, logs “Second middleware”, calls next()
app.use((req, res, next) => {
console.log("Second middleware");
next();
});
3. Route handler runs, logs the message and sends JSON response
app.get("/", (req, res) => {
console.log("Route handler - this is where we handle the actual request");
res.json({ message: "Hello!" });
});
Output: “First middleware” → “Second middleware” → “Route handler - this is where we handle the actual request”
Important: Always call next() unless you want to end the request-response cycle.
Simple Logging Example
Create a new Express project for this lesson to focus on middleware concepts. Set up a basic Express server and let’s start with a practical example - logging every request:
// Logging middleware
app.use((req, res, next) => {
const timestamp = new Date().toISOString();
console.log(`Middleware timestamp: [${timestamp}] ${req.method} ${req.url}`);
next();
});
app.get("/", (req, res) => {
res.json({ message: "Hello world!" });
});
Now every request will be logged to the console before it’s processed.
Note: Check the terminal/console in VS Code where you ran your server to see the logged requests.