Middleware Factories

Configurable Middleware Factories

Nearly every middleware package you install is not a middleware function but a function that returns one: express.json({ limit: '32kb' }), cors 6,195 ({ origin: 'https://app.example.com' }), morgan 8,204 ('combined'). The pattern keeps expensive setup — compiling a pattern, building a Set, opening a store — at startup rather than in the request path, and lets the same package be mounted twice with different settings.

An API-key factory with a skip predicateShell
export function requireApiKey(options = {}) {
  const { header = 'x-api-key', keys = [], skip = () => false } = options;
  const allowed = new Set(keys);                     // built once, at startup
  return function requireApiKeyMiddleware(req, res, next) {
    if (skip(req)) return next();
    const key = req.get(header);
    if (!allowed.has(key)) {
      return next(Object.assign(new Error('Invalid API key'), { status: 401 }));
    }
    req.apiKey = key;
    next();
  };
}
app.use(requireApiKey({ keys: ['k-live-1'], skip: (req) => req.path === '/health' }));
Output
  -> 200 {"status":"ok"}
  -> 401 {"error":"Invalid API key"}
  -> 200 [{"id":1,"title":"Dune"}]

Four conventions make a factory feel like a first-party one. Accept a single options object, so callers never have to remember positional order. Destructure with defaults at the top, which documents the full option set in one place. Validate options and throw immediately — a misspelled option should crash the process at boot, not fail silently at 3 a.m. And name the returned function; requireApiKeyMiddleware beats anonymous in a stack trace.

Build in a skip predicate from the start. Health checks, metrics endpoints and static assets almost always need an exemption from authentication, logging or rate limiting, and a predicate keeps that decision at the call site.