Skip to content

Express Middleware — Complete Guide to Request Processing Pipeline

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Express Middleware. We cover key concepts, practical examples, and best practices to help you master this topic.

Express middleware functions access the request and response objects, execute code, modify request/response, and call the next middleware in the pipeline.

What You'll Learn

By the end of this tutorial, you'll use application-level and router-level middleware, create custom middleware, use built-in middleware (json, static, urlencoded), and handle errors with error middleware.

Why Middleware Matters

Middleware is Express's extension mechanism. Logging, authentication, Parsing, compression, and CORS are all implemented as middleware. Understanding middleware lets you customize the request pipeline.

Real-World Use

A production API uses middleware for: request logging, JWT authentication, request body parsing, Rate Limiting, compression, CORS headers, and error handling — all chained in the correct order.

Express Middleware Learning Path

flowchart LR
  A[Express Routing] --> B[Middleware]
  B --> C[Error Handling]
  C --> D[Sessions]
  D --> E[Security]
  A --> F{You Are Here}
  style F fill:#f90,color:#fff

Middleware Fundamentals

import express from "express";
const app = express();

// Custom middleware
const logger = (req, res, next) => {
  console.log(`${req.method} ${req.url} at ${new Date().toISOString()}`);
  next();  // Pass control to the next middleware
};
app.use(logger);  // Applied to all routes
app.get("/", (req, res) => res.send("Hello"));

Application-Level Middleware

// Applied to every request
app.use((req, res, next) => {
  req.requestTime = Date.now();
  next();
});

// Applied to specific path prefix
app.use("/api", (req, res, next) => {
  console.log("API request received");
  next();
});

Router-Level Middleware

import { Router } from "express";
const router = Router();

const validateId = (req, res, next) => {
  const id = Number(req.params.id);
  if (isNaN(id)) return res.status(400).send("Invalid ID");
  req.validatedId = id;
  next();
};
router.get("/users/:id", validateId, (req, res) => {
  res.send(`User ID: ${req.validatedId}`);
});

Built-in Middleware

import express from "express";
const app = express();

app.use(express.json());       // Parse JSON bodies
app.use(express.urlencoded({ extended: true }));  // Parse form data
app.use(express.static("public"));  // Serve static files

Third-Party Middleware

npm install cors morgan helmet compression
import cors from "cors";
import morgan from "morgan";
import helmet from "helmet";
import compression from "compression";

app.use(helmet());          // Security headers
app.use(cors());            // Cross-origin requests
app.use(morgan("combined"));  // HTTP request logging
app.use(compression());     // Gzip compression

Error-Handling Middleware

app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(err.status || 500).json({
    error: process.env.NODE_ENV === "production"
      ? "Internal Server Error"
      : err.message
  });
});

Error middleware has four parameters. Express identifies it by the arity (number of parameters).

Custom Middleware Pattern

const rateLimiter = (maxRequests, windowMs) => {
  const requests = {};
  return (req, res, next) => {
    const ip = req.ip;
    requests[ip] = (requests[ip] || 0) + 1;
    if (requests[ip] > maxRequests) {
      return res.status(429).send("Too many requests");
    }
    setTimeout(() => requests[ip]--, windowMs);
    next();
  };
};
app.use(rateLimiter(100, 60000));  // 100 requests per minute

Middleware Order Matters

// WRONG: Error handler before routes
app.use(errorHandler);  // Catches nothing
app.get("/test", handler);

// RIGHT: Error handler after routes
app.get("/test", handler);
app.use(errorHandler);

Common Mistakes

1. Forgetting to Call next()

If middleware doesn't call next(), the request hangs until timeout. Always call next() unless sending a response.

2. Sending Response Twice

Calling res.send() or res.json() inside middleware and again in the route handler causes "Cannot set headers after they are sent" error.

3. Wrong Middleware Order

Security middleware should come first. Error middleware should come last. Static files before routes for performance.

4. Not Returning on Error Response

// WRONG: execution continues after send
if (!req.body) res.status(400).send("No body");
// Execution continues here!

// RIGHT: return the send call
if (!req.body) return res.status(400).send("No body");

5. Modifying req/res Objects Without Documentation

If your middleware adds properties to req (like req.user), document it clearly so other developers know what's available.

Practice Questions

1. What is the job of the next() function?

next() passes control to the next middleware in the pipeline. Without it, the request processing stops and hangs.

2. How is error-handling middleware different from regular middleware?

It has four parameters (err, req, res, next) instead of three. Express identifies it by the function's parameter count.

3. What is the order of middleware execution?

Middleware runs in the order it's registered. Request passes through each middleware top-to-bottom until one sends a response.

4. How do you make middleware conditional?

Wrap middleware in a conditional: if (condition) { app.use(middleware); } or use a wrapper function that conditionally calls next().

5. Challenge: Create middleware that adds response time header to every response.

app.use((req, res, next) => {
  const start = Date.now();
  const originalEnd = res.end;
  res.end = function (...args) {
    res.set("X-Response-Time", `${Date.now() - start}ms`);
    originalEnd.apply(res, args);
  };
  next();
});

FAQ

What is the difference between app.use and app.METHOD?

app.use matches any HTTP method and partial paths. app.get/post/put/delete matches specific methods.

Can middleware be asynchronous?

Yes, but unhandled rejections crash the process. Use try/catch and call next(err) for errors.

What happens if middleware throws an error?

Express catches synchronous throws. For async errors, call next(err) to pass to error middleware.

How do I skip middleware conditionally?

Don't call next() — call the route handler function directly or use a conditional wrapper.

Can I mount middleware on a specific route only?

Yes: app.get('/route', middleware, handler). Middleware runs only for that route.

Mini Project: Request Logger Middleware

Build a configurable request logger that logs method, URL, status code, duration, and body size.

function requestLogger(options = {}) {
  return (req, res, next) => {
    const start = Date.now();
    res.on("finish", () => {
      const duration = Date.now() - start;
      const log = {
        method: req.method,
        url: req.originalUrl,
        status: res.statusCode,
        duration: `${duration}ms`,
        bodySize: res.get("Content-Length") || "0"
      };
      console.log(options.format === "json" ? JSON.stringify(log) :
        `${log.method} ${log.url} ${log.status} ${log.duration}`);
    });
    next();
  };
}
app.use(requestLogger({ format: "json" }));

What's Next

Express Error Handling Express Sessions REST API Express

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro