惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

H
Help Net Security
宝玉的分享
宝玉的分享
The Cloudflare Blog
Apple Machine Learning Research
Apple Machine Learning Research
V
Visual Studio Blog
Last Week in AI
Last Week in AI
Hugging Face - Blog
Hugging Face - Blog
博客园 - 司徒正美
博客园 - 三生石上(FineUI控件)
A
About on SuperTechFans
MyScale Blog
MyScale Blog
aimingoo的专栏
aimingoo的专栏
Microsoft Security Blog
Microsoft Security Blog
D
Docker
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Recent Announcements
Recent Announcements
大猫的无限游戏
大猫的无限游戏
IT之家
IT之家
P
Proofpoint News Feed
L
LangChain Blog
Blog — PlanetScale
Blog — PlanetScale
The GitHub Blog
The GitHub Blog
博客园 - 【当耐特】
Martin Fowler
Martin Fowler

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
What is Middleware in Express and How It Works
Akash Kumar · 2026-06-25 · via DEV Community

"Middleware is the invisible assembly line your request travels through before it ever reaches your route."


Introduction

You've set up an Express server. You have routes. Things work. But then real-world requirements show up — you need to log every incoming request, verify that users are authenticated, validate request bodies, handle errors gracefully. Where does all that logic go?

The answer is middleware — one of the most powerful and fundamental concepts in Express.js. Once you understand middleware, you stop writing tangled route handlers and start building clean, composable server applications.

This guide walks through what middleware is, how it fits into the request lifecycle, the different types available, and how to use it in practice.


1. What Is Middleware?

In Express, middleware is a function that has access to the request object (req), the response object (res), and a special function called next().

Every middleware function can do one of three things:

  • Execute any code — logging, validation, authentication checks
  • Modify req or res — attach data, set headers, parse a body
  • End the request-response cycle — send back a response and stop
  • Call next() — pass control to the next middleware in line

Think of middleware as a series of checkpoints a request must pass through before it reaches its final destination — your route handler.

// The shape of every middleware function
function myMiddleware(req, res, next) {
  // Do something with the request or response
  console.log("Checkpoint reached!");

  // Then either end the cycle...
  // res.send("Stopped here");

  // ...or pass control to the next middleware
  next();
}


2. Where Middleware Sits in the Request Lifecycle

Every time a client sends a request to your Express server, that request doesn't jump straight to your route handler. It travels through a pipeline — a chain of middleware functions executed in order, one after another.

CLIENT REQUEST
     │
     ▼
┌─────────────────────────────────────────────────────────────┐
│                   EXPRESS APPLICATION                       │
│                                                             │
│  ┌──────────────┐   ┌──────────────┐   ┌────────────────┐  │
│  │  Middleware  │   │  Middleware  │   │  Middleware    │  │
│  │      1       │──►│      2       │──►│       3        │  │
│  │   (logger)   │   │   (auth)     │   │  (validator)   │  │
│  └──────────────┘   └──────────────┘   └────────────────┘  │
│         │                 │                    │            │
│     next() called     next() called        next() called   │
│                                                 │           │
│                                        ┌────────────────┐  │
│                                        │  Route Handler │  │
│                                        │  GET /profile  │  │
│                                        └────────────────┘  │
│                                                 │           │
└─────────────────────────────────────────────────┼───────────┘
                                                  │
                                                  ▼
                                           CLIENT RESPONSE

Each middleware in the chain either passes the baton (calls next()) or ends the race (sends a response). If a middleware doesn't call next() and doesn't send a response, the request just... hangs. That's a bug worth memorizing.

The pipeline analogy: Imagine your request is water flowing through pipes. Each pipe fitting (middleware) can filter it, add something to it, or block it entirely. Your route handler is the tap at the end — it only receives water that made it through every fitting before it.


3. Types of Middleware in Express

Express organizes middleware into four categories. Understanding which type to reach for is key to keeping your code organized.

Application-Level Middleware

This middleware is bound directly to your app instance using app.use() or app.METHOD(). It runs for every request that matches its path (or all requests, if no path is given).

const express = require("express");
const app = express();

// Runs on EVERY incoming request (no path specified)
app.use((req, res, next) => {
  console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
  next();
});

// Runs only for requests to /dashboard and its sub-paths
app.use("/dashboard", (req, res, next) => {
  console.log("Dashboard section accessed");
  next();
});

When to use it: Global concerns — logging, parsing request bodies, setting security headers — anything that should run across all (or most) routes.


Router-Level Middleware

Router-level middleware works identically to application-level middleware, but it's bound to an Express Router instance (express.Router()). This lets you group related routes and their middleware together into modular units.

const express = require("express");
const router = express.Router();

// Middleware scoped only to this router
router.use((req, res, next) => {
  console.log("User router middleware running");
  next();
});

router.get("/profile", (req, res) => {
  res.send("User profile page");
});

router.get("/settings", (req, res) => {
  res.send("User settings page");
});

// Mount the router on the app
app.use("/users", router);

When to use it: Feature-specific concerns — auth checks that only apply to /admin routes, validation that only applies to /api routes. Keeps your app.js clean and each feature self-contained.


Built-in Middleware

Express ships with a small set of built-in middleware functions that handle common tasks out of the box.

// Parse incoming JSON request bodies
// Attaches parsed data to req.body
app.use(express.json());

// Parse URL-encoded form data (from HTML forms)
app.use(express.urlencoded({ extended: true }));

// Serve static files (HTML, CSS, images) from a directory
app.use(express.static("public"));

These three cover the vast majority of what you need for a standard web application or REST API. Always add express.json() before any route that expects a JSON body — otherwise req.body will be undefined.

When to use it: Body parsing and static file serving. These almost always appear at the top of every Express application.


Error-Handling Middleware

Error-handling middleware has a special signature — it takes four parameters: (err, req, res, next). Express recognizes the four-argument signature and only calls this middleware when an error occurs.

// Must be registered LAST, after all other app.use() and routes
app.use((err, req, res, next) => {
  console.error("Error caught:", err.message);
  res.status(err.status || 500).json({
    error: err.message || "Internal Server Error"
  });
});

To trigger it from anywhere in your application, call next(error) and pass an error object:

app.get("/data", (req, res, next) => {
  try {
    // Something that might fail
    const result = riskyOperation();
    res.json(result);
  } catch (err) {
    next(err); // Skips all regular middleware and jumps to error handler
  }
});

When to use it: Centralizing error responses. Without it, you'd write the same res.status(500) block in every route.


4. Execution Order: Order Is Everything

Middleware in Express runs in the exact order it is registered. This is not a minor implementation detail — it's the defining rule of the middleware system.

const app = express();

app.use((req, res, next) => {
  console.log("Step 1: Logger");
  next();
});

app.use((req, res, next) => {
  console.log("Step 2: Auth check");
  next();
});

app.use((req, res, next) => {
  console.log("Step 3: Request validation");
  next();
});

app.get("/profile", (req, res) => {
  console.log("Step 4: Route handler");
  res.send("Profile data");
});

// Console output for GET /profile:
// Step 1: Logger
// Step 2: Auth check
// Step 3: Request validation
// Step 4: Route handler

A common source of bugs is registering middleware after the routes it's meant to protect. If you put your auth check below your route, it simply won't run for that route.

// ❌ WRONG — auth middleware registered too late
app.get("/secret", (req, res) => res.send("Secret data"));
app.use(authMiddleware); // This never runs for /secret

// ✅ CORRECT — auth middleware registered before the route
app.use(authMiddleware);
app.get("/secret", (req, res) => res.send("Secret data"));


5. The Role of next()

next() is the function that keeps the pipeline moving. Without it, your request stalls permanently.

Middleware calls next()   →  Next middleware runs
Middleware sends response →  Pipeline ends (no more middleware runs)
Middleware does neither   →  Request hangs forever (a bug)

Passing an Error with next(err)

Calling next() with no argument moves to the next regular middleware. Calling next(err) with an argument skips all regular middleware and jumps directly to the nearest error-handling middleware.

// Middleware chain execution paths:

//  next()          ──►  Next regular middleware or route handler
//  next(new Error) ──►  Skips everything → error handler
//  res.send()      ──►  Ends the request-response cycle entirely

function checkApiKey(req, res, next) {
  const apiKey = req.headers["x-api-key"];

  if (!apiKey) {
    // Skip remaining middleware, jump to error handler
    return next(new Error("API key required"));
  }

  // All good — continue down the chain
  next();
}

Always return when calling next() early. Without return, the rest of the middleware function keeps executing after next(), which can cause double-response errors (Cannot set headers after they are sent).


6. Real-World Examples

Logging Middleware

Logs every request with its method, URL, and how long the server took to respond.

function requestLogger(req, res, next) {
  const start = Date.now();

  // Hook into the response finish event to capture response time
  res.on("finish", () => {
    const duration = Date.now() - start;
    console.log(
      `${req.method} ${req.url}${res.statusCode} (${duration}ms)`
    );
  });

  next(); // Always call next — logger should never block a request
}

app.use(requestLogger);

// Output example:
// GET /users → 200 (14ms)
// POST /login → 401 (3ms)
// GET /profile → 200 (22ms)


Authentication Middleware

Checks for a valid JWT token in the Authorization header. Blocks the request with a 401 if invalid, or attaches the decoded user to req.user and continues.

const jwt = require("jsonwebtoken");

function authenticate(req, res, next) {
  const authHeader = req.headers.authorization;

  // Check header exists and has the right format
  if (!authHeader || !authHeader.startsWith("Bearer ")) {
    return res.status(401).json({ error: "No token provided" });
  }

  const token = authHeader.split(" ")[1];

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded; // Attach user data for downstream handlers
    next();
  } catch (err) {
    return res.status(401).json({ error: "Invalid or expired token" });
  }
}

// Applied selectively to protected routes only
app.get("/profile", authenticate, (req, res) => {
  res.json({ message: `Welcome, ${req.user.name}!` });
});

app.get("/settings", authenticate, (req, res) => {
  res.json({ settings: getUserSettings(req.user.id) });
});


Request Validation Middleware

Validates that a request body contains the required fields before it reaches the route handler. Returns a descriptive 400 error if anything is missing.

function validateRegistration(req, res, next) {
  const { name, email, password } = req.body;
  const errors = [];

  if (!name || name.trim().length < 2) {
    errors.push("Name must be at least 2 characters");
  }

  if (!email || !email.includes("@")) {
    errors.push("A valid email address is required");
  }

  if (!password || password.length < 8) {
    errors.push("Password must be at least 8 characters");
  }

  if (errors.length > 0) {
    // Stop here — don't let invalid data reach the route handler
    return res.status(400).json({ errors });
  }

  next(); // Validation passed — continue
}

app.post("/register", validateRegistration, (req, res) => {
  // If we're here, req.body is guaranteed to be valid
  const user = createUser(req.body);
  res.status(201).json(user);
});


Putting It All Together

Here's how a realistic Express application composes all of these middleware types in the correct order:

const express = require("express");
const app = express();

// ── 1. Built-in middleware (always first) ──────────────────────
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// ── 2. Application-level middleware (global) ───────────────────
app.use(requestLogger);

// ── 3. Public routes (no auth required) ───────────────────────
app.post("/register", validateRegistration, registerUser);
app.post("/login", loginUser);

// ── 4. Protected routes (auth required) ───────────────────────
app.use("/api", authenticate);       // All /api/* routes are protected
app.get("/api/profile", getProfile);
app.put("/api/settings", updateSettings);

// ── 5. Error handler (always last) ────────────────────────────
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(err.status || 500).json({ error: err.message });
});

app.listen(3000);


Quick Reference

Middleware Type Bound To Typical Use
Application-level app.use() Logging, body parsing, global auth
Router-level router.use() Feature-scoped logic (admin, API)
Built-in app.use() JSON parsing, static files
Error-handling app.use(err, req, res, next) Centralized error responses
next() Call What Happens
next() Move to the next middleware or route handler
next(err) Skip to the nearest error-handling middleware
res.send() / res.json() End the request-response cycle
Nothing called Request hangs — always a bug

Key Takeaways

  • Middleware functions run in registration order — sequence matters
  • Every middleware must either call next() or send a response — never neither
  • Use return next() to prevent code from continuing after passing control
  • Use next(err) to trigger your centralized error handler
  • Built-in middleware (express.json()) should be registered before any routes that need it
  • Error-handling middleware (err, req, res, next) must always be last

What's Next?

Now that you have a solid foundation in Express middleware, great next steps include:

  • Third-party middlewarehelmet for security headers, cors for cross-origin requests, morgan for production-grade logging
  • express-validator — a dedicated library for request validation with a clean, chainable API
  • Rate limiting — using express-rate-limit to protect routes from abuse
  • Async middleware — wrapping async functions cleanly so errors are always caught

Middleware is the architecture that separates a prototype from a production application. Master the pipeline, and your Express code will be cleaner, safer, and far easier to maintain.


If this clicked for you, the next read is async/await error handling in Express — because try/catch in every route handler is its own kind of callback hell. 🚀