Middleware Explained

intermediate
12 min

Middleware Explained

If you understand middleware, you understand Express. Nearly every feature of the framework, from body parsing to authentication to error handling, is implemented as middleware. This lesson demystifies the concept, shows you how to write your own, and explains the ordering rules that trip up almost every newcomer.

What Is Middleware?

Middleware is simply a function that sits between the incoming request and your final route handler. It has access to the request object, the response object, and a third argument called next:

js
function myMiddleware(req, res, next) { // do something with req or res next(); // pass control to the next function }

Think of a request as travelling down a pipeline. Each middleware can inspect the request, modify it, respond immediately, or call next() to hand it further along. If a middleware neither responds nor calls next, the request hangs forever, which is the single most common middleware bug.

Application-Level Middleware

Register middleware for every request with app.use. A classic first example is a request logger:

js
const express = require('express'); const app = express(); app.use((req, res, next) => { console.log(`${new Date().toISOString()} ${req.method} ${req.url}`); next(); }); app.get('/', (req, res) => res.send('Home'));

Every request now prints a timestamped line before reaching any route. Because app.use has no path here, it applies to all paths and all methods.

Order Is Everything

Express runs middleware and routes in registration order. Middleware registered after a route will never run for that route. Compare:

js
app.get('/first', (req, res) => res.send('No logging for me')); app.use(logger); // registered too late for /first app.get('/second', (req, res) => res.send('I get logged'));

Rule of thumb: register general middleware (logging, parsers, static files) at the top, routes in the middle, and 404 plus error handlers at the very bottom.

Attaching Data to req

Middleware often enriches the request for later handlers. A common pattern is attaching a request ID or a user object:

js
app.use((req, res, next) => { req.requestTime = Date.now(); next(); }); app.get('/time', (req, res) => { res.json({ receivedAt: req.requestTime }); });

This works because the same req object flows through the whole pipeline.

Route-Level and Path-Scoped Middleware

You can scope middleware to a path prefix or attach it to a single route as an extra argument:

js
app.use('/admin', requireAuth); // everything under /admin app.get('/dashboard', requireAuth, (req, res) => { res.send('Secret dashboard'); }); function requireAuth(req, res, next) { if (req.headers['x-api-key'] === 'secret123') return next(); res.status(401).json({ error: 'Unauthorized' }); }

Notice how requireAuth either calls next() on success or ends the cycle with a 401. Ending early is a feature: the route handler never runs for unauthorized users.

Built-In and Third-Party Middleware

Express ships with express.json(), express.urlencoded(), and express.static(). The npm ecosystem adds thousands more: morgan for logging, cors for cross-origin requests, helmet for security headers, and compression for gzip. Using them is one line each:

js
const morgan = require('morgan'); app.use(morgan('dev'));

Key Takeaways

  • Middleware is a function with (req, res, next) that runs in the request pipeline.
  • Call next() to continue, or send a response to end the cycle; doing neither hangs the request.
  • Registration order decides execution order; parsers and loggers go first, error handlers last.
  • Middleware can attach data to req for downstream handlers.
  • Scope middleware globally, by path prefix, or per route.

Next lesson: Serving Static Files, our first look at built-in middleware in action.

Middleware Explained - Express.js | CodeYourCraft | CodeYourCraft