Skip to content

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 or res
  • 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

MiddlewarePurpose
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:

  1. Application-level – applies to all routes
  2. Route-level – applies only to specific routes
  3. Error-handling – catches and handles errors
  4. Built-in or third-party – like cors, body-parser, morgan

1.4 🧵 Example Flow

app.use(express.json()); // 1: parse JSON
app.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.

  1. Global Middleware

    • Middleware registered with app.use() or specific routes will run in the order they are defined in your code.
  2. Route-Specific Middleware

    • Middleware attached to specific routes (like app.get(), app.post(), etc.) runs in the order it is defined for that route.
  3. 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).

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.
  • 2. Call next() to move forward: After each middleware runs, it needs to call next() 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 middleware
app.use((req, res, next) => {
console.log('First middleware');
next(); // Pass to next middleware
});
// Second middleware
app.use((req, res, next) => {
console.log('Second middleware');
next(); // Pass to next middleware
});
// Route handler
app.get('/', (req, res) => {
res.send('Hello World');
});
// Error handling middleware
app.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?

  1. First Middleware → Logs: First middleware
  2. Second Middleware → Logs: Second middleware
  3. Route Handler (GET /) → Sends: Hello World
  4. 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?

  1. Global Middleware → Logs: Global Middleware
  2. Route-Specific Middleware for /home → Logs: Route-Specific Middleware for /home
  3. 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 middleware
app.use((err, req, res, next) => {
console.error('Caught an error:', err.message);
res.status(500).send('Internal Server Error');
});

What Happens?

  1. Middleware or route will pass an error via next().
  2. The error-handling middleware will catch the error and send an appropriate response.

2.4 Summary

  1. Global Middleware: Executed in the order they’re defined with app.use().
  2. Route-Specific Middleware: Executed in the order they are defined for specific routes (e.g., app.get(), app.post()).
  3. 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 middleware
app.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 middleware
router.use((req, res, next) => {
console.log('Router-level middleware');
next();
});
// Routes inside the router
router.get('/hello', (req, res) => {
res.send('Hello from router!');
});
// Mount the router at /api
app.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 TypeScopeDefined UsingApplied To
Application-wideWhole appapp.use()All routes (or filtered by path)
Router-levelSpecific routerrouter.use()Only routes handled by that router