# Middleware in Node.js: Understanding Express.js Patterns and Implementation

> Master Node.js middleware with Express.js patterns. Understand how req, res, and next functions create effective request handling pipelines for your applications.

- Repository: [expressjs/express](https://github.com/expressjs/express)
- Tags: deep-dive
- Published: 2026-02-16

---

**Middleware in Node.js functions as a sequential stack of functions that process HTTP requests through the `req`, `res`, and `next` parameters, enabling composable request handling pipelines.**

Middleware in Node.js forms the architectural foundation of the Express.js framework, enabling developers to compose reusable request processing logic. The expressjs/express repository implements this pattern through a sophisticated routing layer in [`lib/application.js`](https://github.com/expressjs/express/blob/main/lib/application.js) that manages how functions are registered, executed, and chained together to handle HTTP traffic.

## What Is Middleware in Node.js?

Express builds on the Connect middleware model, where every incoming request passes through a stack of functions before a response is sent. Each middleware function receives three arguments: the request object (`req`), the response object (`res`), and a `next` callback that passes control to the subsequent middleware in the chain.

When `next()` is invoked without arguments, execution continues to the next function. Calling `next(err)` with an error object immediately skips to the first error-handling middleware, creating a robust exception handling pipeline.

## How Express.js Implements Middleware Internally

### The Core Dispatch Mechanism

In [`lib/application.js`](https://github.com/expressjs/express/blob/main/lib/application.js), the `handle` method (lines 52-78) serves as the entry point for every HTTP request. This method establishes the response pipeline by setting up circular references between the request and response objects, applying default headers, and ultimately delegating to `this.router.handle(req, res, done)`.

The router then manages the middleware stack, determining which functions match the current request path and executing them in registration order.

### Registering Middleware with app.use

The `app.use` method in [`lib/application.js`](https://github.com/expressjs/express/blob/main/lib/application.js) (lines 90-124) handles middleware registration. This implementation normalizes arguments to support multiple patterns: path-specific mounting, array-based middleware chains, and nested arrays.

The method determines the mount path, flattens argument arrays using `flatten(args, 1)`, validates that each item is a function, and delegates to the internal Router for actual storage in the middleware stack.

## Types of Middleware in Express.js

### Application-Level Middleware

Application-level middleware binds to an instance of the Express app using `app.use()` or `app.METHOD()` (where METHOD is an HTTP verb like GET or POST). These functions execute for every request or for specific paths depending on how they are mounted.

### Error-Handling Middleware

Error-handling middleware functions accept four arguments instead of three: `(err, req, res, next)`. Express identifies these handlers by checking function arity (`.length === 4`).

As demonstrated in [`examples/web-service/index.js`](https://github.com/expressjs/express/blob/main/examples/web-service/index.js) (lines 93-100), these handlers catch errors passed via `next(err)` and format appropriate responses:

```javascript
app.use(function errorHandler(err, req, res, next) {
  res.status(err.status || 500);
  res.json({ error: err.message });
});

```

### Built-in Middleware

Express exposes several built-in middleware functions directly from the `express` module. In [`lib/express.js`](https://github.com/expressjs/express/blob/main/lib/express.js) (lines 74-82), these are exported as properties of the main express object:

```javascript
app.use(express.json());          // Parses JSON request bodies
app.use(express.urlencoded({ extended: true })); // Parses URL-encoded bodies
app.use(express.static('public')); // Serves static files

```

These wrappers delegate to the `body-parser` and `serve-static` packages while providing a unified API surface.

### Sub-Application Mounting

An Express application instance can function as middleware for another application. In [`lib/application.js`](https://github.com/expressjs/express/blob/main/lib/application.js) (lines 164-202), the `app.use` method detects when a mounted argument has `.handle` and `.set` properties, treating it as a sub-application.

The implementation wraps the sub-app to ensure that `req.app` references the parent application after the sub-app completes processing:

```javascript
const admin = express();

admin.use(function adminOnly(req, res, next) {
  if (!req.user || !req.user.isAdmin) {
    return next(new Error('Forbidden'));
  }
  next();
});

app.use('/admin', admin);   // Mounts sub-app at /admin path

```

## Common Middleware Patterns in Node.js

The Express source code demonstrates several reusable patterns for structuring middleware logic.

**Request Logging**

Basic logging middleware intercepts every request to record method and URL before continuing:

```javascript
app.use(function logger(req, res, next) {
  console.log(`${req.method} ${req.url}`);
  next();  // Pass control to next middleware
});

```

**Path-Scoped Authentication**

Restricting middleware to specific routes reduces overhead and isolates security logic:

```javascript
app.use('/api', function apiKeyChecker(req, res, next) {
  const key = req.query['api-key'];
  if (!key) return next(new Error('API key required'));
  // Validation logic here
  next();
});

```

This pattern appears in [`examples/web-service/index.js`](https://github.com/expressjs/express/blob/main/examples/web-service/index.js) (lines 30-42), where API-specific validation is scoped to protected endpoints.

**Async Error Handling**

While middleware can use async/await, unhandled promise rejections require explicit error passing:

```javascript
app.use(async function asyncHandler(req, res, next) {
  try {
    const data = await fetchData();
    req.data = data;
    next();
  } catch (err) {
    next(err);  // Pass error to error-handling middleware
  }
});

```

## Summary

- Middleware in Node.js functions as a **stack of functions** that process requests sequentially through the `req`, `res`, and `next` parameters.
- The Express implementation in [`lib/application.js`](https://github.com/expressjs/express/blob/main/lib/application.js) handles registration via `app.use` and dispatches requests through `app.handle` to the internal router.
- **Error-handling middleware** accepts four arguments (`err, req, res, next`) and activates only when `next(err)` is called.
- **Built-in middleware** like `express.json()` and `express.static()` are exported from [`lib/express.js`](https://github.com/expressjs/express/blob/main/lib/express.js) and provide common parsing and serving functionality.
- **Sub-application mounting** allows entire Express instances to function as middleware, with `req.app` context preservation handled internally.

## Frequently Asked Questions

### What is the difference between middleware and route handlers?

Route handlers are specialized middleware that terminate the request-response cycle by sending a response. While standard middleware calls `next()` to pass control forward, route handlers typically call `res.send()`, `res.json()`, or similar methods. In Express, both use the same underlying mechanism in [`lib/application.js`](https://github.com/expressjs/express/blob/main/lib/application.js), but route handlers are usually registered with specific HTTP methods like `app.get()` or `app.post()`.

### How does error-handling middleware differ from regular middleware?

Error-handling middleware functions accept four parameters—`err`, `req`, `res`, and `next`—whereas regular middleware accepts three (`req`, `res`, `next`). Express detects error handlers by checking function arity (`.length === 4`). These handlers are only invoked when `next()` is called with an error argument, as demonstrated in [`examples/web-service/index.js`](https://github.com/expressjs/express/blob/main/examples/web-service/index.js) (lines 93-100), allowing centralized error formatting and logging.

### Can I use async/await in Express middleware?

Yes, you can use async/await in Express middleware, but you must handle promise rejections explicitly. If an async operation throws an error, you must catch it and pass it to `next(err)` to trigger error-handling middleware. Unhandled promise rejections will not automatically invoke Express error handlers and may crash the process or leave requests hanging, depending on your Node.js version and configuration.

### What happens if I forget to call next() in middleware?

If you forget to call `next()` in non-terminating middleware, the request will hang indefinitely because Express waits for the middleware to signal completion. The client will eventually timeout, but no subsequent middleware or route handlers will execute. This is a common debugging issue when middleware performs async operations but fails to call `next()` after completion or error handling.