2.1 Introduction
Middleware are functions that have access to the request (req
), response (res
), and a special next function in the request-response cycle. Middleware are functions that run before the request reaches the final route handler.
They can:
- Run any code
- Modify
req
orres
- End the request-response cycle
- Call
next()
to pass control to the next middleware
1. Basics of Middleware
1.1 🔧 Basic Syntax
function middleware(req, res, next) { // Do something... next(); // Pass to next middleware or route handler}
Used like:
app.use(middleware);
🧪 Example: Logger Middleware
app.use((req, res, next) => { console.log(`${req.method} ${req.url}`); next(); // Continue to next middleware or route});
1.2 🛠 Common Built-in Middleware
Middleware | Purpose |
---|---|
express.json() | Parses JSON bodies in requests |
express.urlencoded() | Parses URL-encoded form data |
express.static() | Serves static files like HTML, CSS, JS |
1.3 🧭 Where Middleware Runs
Middleware can be:
- Application-level – applies to all routes
- Route-level – applies only to specific routes
- Error-handling – catches and handles errors
- Built-in or third-party – like
cors
,body-parser
,morgan
1.4 🧵 Example Flow
app.use(express.json()); // 1: parse JSONapp.use(myLogger); // 2: log request
app.post('/data', (req, res) => { res.send('Data received');});
1.5 Handling Errors in Middleware
In Express middleware, if there’s an error and you want to stop further processing, you can return a response immediately using res.status(...).send(...)
or pass the error to the next error-handling middleware using next(error)
.
✅ Option 1: Return response directly
app.use((req, res, next) => { if (!req.headers['x-auth']) { return res.status(401).send('Unauthorized'); } next(); // Call next only if no error});
✅ Option 2: Pass error to error-handling middleware
app.use((req, res, next) => { if (!req.headers['x-auth']) { const err = new Error('Unauthorized'); err.status = 401; return next(err); } next();});
// Error-handling middleware (must have 4 args)app.use((err, req, res, next) => { res.status(err.status || 500).send(err.message);});
1.6 “Headers Already Sent” - Issues
“Headers already sent” is a common error in Express (and other Node.js frameworks) that means:
Your code tried to send an HTTP response more than once, or tried to modify headers after the response body was already started.
❌ Example that causes the error:
app.use((req, res, next) => { res.send('Hello'); next(); // ❌ This is called after res.send});
Why this is a problem:
- When you call
res.send()
, Express sends the headers and the body. - HTTP headers must be sent before the body.
- Once the headers are sent, you can’t modify them or send another response.
- If
next()
is called after a response was already sent, the next middleware might try to send another response.
✅ Correct way (use return
):
app.use((req, res, next) => { if (!req.headers['x-auth']) { return res.status(401).send('Unauthorized'); } next();});
✅ Also valid (no res.send()
called yet):
app.use((req, res, next) => { console.log('Still safe to call next()'); next();});
2. Middleware Execution Order
The order in which middleware runs in Express.js is very important, as it follows a top-to-bottom sequence. The order is determined by the way middleware is registered in your application.
-
Global Middleware
- Middleware registered with
app.use()
or specific routes will run in the order they are defined in your code.
- Middleware registered with
-
Route-Specific Middleware
- Middleware attached to specific routes (like
app.get()
,app.post()
, etc.) runs in the order it is defined for that route.
- Middleware attached to specific routes (like
-
Error Handling Middleware
- Middleware that handles errors is called only if an error is passed to
next(err)
within another middleware or route handler. These middleware functions must have 4 arguments:(err, req, res, next)
.
- Middleware that handles errors is called only if an error is passed to
Key Points:
-
1. Order of Definition Matters: Middleware is executed in the order it is defined.
- If you define middleware
app.use()
earlier, it runs before middleware defined later.
- If you define middleware
-
2. Call
next()
to move forward: After each middleware runs, it needs to callnext()
to pass the request to the next middleware or route handler. -
3. If
next()
is not called, the request is “stuck” and will not reach the next middleware or route handler.
2.1 Order with Global Middleware
const express = require('express');const app = express();
// First middlewareapp.use((req, res, next) => { console.log('First middleware'); next(); // Pass to next middleware});
// Second middlewareapp.use((req, res, next) => { console.log('Second middleware'); next(); // Pass to next middleware});
// Route handlerapp.get('/', (req, res) => { res.send('Hello World');});
// Error handling middlewareapp.use((err, req, res, next) => { console.error('Error:', err); res.status(500).send('Something went wrong!');});
app.listen(3000, () => { console.log('Server running on port 3000');});
What Happens?
- First Middleware → Logs:
First middleware
- Second Middleware → Logs:
Second middleware
- Route Handler (
GET /
) → Sends:Hello World
- Error Handling Middleware (if there’s an error) → Catches any errors and responds with a 500 status.
2.2 Order with Route-Specific Middleware
You can also apply middleware to specific routes, affecting the sequence for those routes.
app.use((req, res, next) => { console.log('Global Middleware'); next();});
app.get('/home', (req, res, next) => { console.log('Route-Specific Middleware for /home'); next(); // Pass to the next middleware or route handler}, (req, res) => { res.send('Home Page');});
app.listen(3000, () => { console.log('Server running on port 3000');});
What Happens?
Global Middleware
→ Logs:Global Middleware
Route-Specific Middleware for /home
→ Logs:Route-Specific Middleware for /home
- Route Handler for
/home
→ Sends:Home Page
2.3 Order with Error Handling Middleware
If any route or middleware passes an error to next(err)
, the error handling middleware will run.
app.use((req, res, next) => { // Some logic... next(new Error('Something went wrong!')); // Pass error to error handler});
// Error handling middlewareapp.use((err, req, res, next) => { console.error('Caught an error:', err.message); res.status(500).send('Internal Server Error');});
What Happens?
- Middleware or route will pass an error via
next()
. - The error-handling middleware will catch the error and send an appropriate response.
2.4 Summary
- Global Middleware: Executed in the order they’re defined with
app.use()
. - Route-Specific Middleware: Executed in the order they are defined for specific routes (e.g.,
app.get()
,app.post()
). - Error Handling Middleware: Activated only when an error is passed using
next(err)
. It must have 4 parameters:(err, req, res, next)
.
3. Scope of Middleware
In Express.js, middleware functions are used to handle requests and responses. There are two main types in terms of scope: application-wide middleware and router-level middleware.
3.1 ✅ Application-wide Middleware
These are middleware functions that are applied to the entire app. They run for every incoming request, unless filtered by a path.
💡 How it’s defined:
const express = require('express');const app = express();
// Application-wide middlewareapp.use((req, res, next) => { console.log('This runs for all routes'); next();});
📍 You can also limit it to a specific path:
app.use('/api', (req, res, next) => { console.log('This runs for all routes starting with /api'); next();});
3.2 ✅ Router-level Middleware
This middleware is tied to an instance of express.Router()
. It’s like a mini-app with its own middleware stack, and it’s mounted onto the main app at a certain path.
💡 Example:
const express = require('express');const app = express();const router = express.Router();
// Router-level middlewarerouter.use((req, res, next) => { console.log('Router-level middleware'); next();});
// Routes inside the routerrouter.get('/hello', (req, res) => { res.send('Hello from router!');});
// Mount the router at /apiapp.use('/api', router);
In this example, router.use()
middleware will only apply to routes handled by that router (/api/hello
, etc.).
3.3 🔁 Summary Table
Middleware Type | Scope | Defined Using | Applied To |
---|---|---|---|
Application-wide | Whole app | app.use() | All routes (or filtered by path) |
Router-level | Specific router | router.use() | Only routes handled by that router |