bhylu.com

Advertisements

Node.js Express Middleware

Middleware is a sequence of functions that can inspect a request, add bounded context, produce a response or call the next function. Order is part of its behaviour.

How it works

Place shared request metadata before the routes that use it, and keep error middleware after the handlers whose failures it must observe. Calling next is not the same as returning a successful response, and calling it twice can create confusing behaviour. This fixed local fixture records two middleware steps and returns them through one controlled endpoint. It teaches ordering without claiming a complete production tracing or authorization layer.

Run the code

Run this example locally with Node.js. The exact sources are reviewed on Node 22.15.1 and Node 24.22.1 unless a specialised lab states otherwise. Save the complete source as middleware.mjs in a separate empty exercises folder and run node middleware.mjs. There is no Node.js browser runner on Bhylu; the plain JavaScript playground does not provide these filesystem, server or process APIs. In that separate project, install the reviewed packages with npm install --save-exact express@5.1.0. These are teaching dependencies, not changes to the Bhylu website package tree.

Download the complete source. Ordinary Node scripts need no Apache, PHP or MySQL service. File examples own their temporary folders; network fixtures use loopback and available temporary ports.

Example

middleware.mjs

import express from 'express';
import http from 'node:http';
import { once } from 'node:events';
const app = express();
app.use((request, response, next) => {
  request.steps = ['received'];
  next();
});
app.use((request, response, next) => {
  request.steps.push('checked');
  next();
});
app.get('/status', (request, response) => response.json({ steps: request.steps }));
const server = http.createServer(app);
server.listen(0, '127.0.0.1');
await once(server, 'listening');
const base = 'http://127.0.0.1:' + server.address().port;
try {
  const response = await fetch(base + '/status');
  console.log((await response.json()).steps.join(' -> '));
} finally {
  server.closeAllConnections();
  await new Promise((resolve, reject) =>
    server.close((error) => (error ? reject(error) : resolve())),
  );
}

Expected result

received -> checked

The route sees context built by earlier middleware. A real application should choose namespaced or typed context fields and avoid placing sensitive records on a request object merely because later handlers can read it.

Use the Node.js API reference for the precise version contract.