Express-style Middleware Executor

raw JSON →
1.0.3 verified Thu Apr 23 auth: no javascript

execute-middleware is a JavaScript utility designed for the standalone execution of Express-style middleware logic, outside of a full Express application. It processes both standard middleware (expecting `(req, res, next)`) and error-handling middleware (expecting `(err, req, res, next)`), automatically differentiating between them by the number of arguments. The library supports complex nesting of middleware arrays and correctly handles error propagation: when `next(err)` is called, subsequent normal middleware is skipped until an error handler is encountered. It offers both serial and concurrent execution modes for middleware chains. The current stable version is 1.0.3. While no explicit release cadence is stated, it appears to be a mature, stable utility providing a focused solution for running Express middleware patterns in a more controlled or isolated environment.

error TypeError: (intermediate value).serial is not a function
cause Attempting to call `.serial` or `.concurrent` on an `executeMW` object that was not correctly imported or is `undefined`. This often happens with incorrect CommonJS `require` syntax in an ESM project or vice-versa.
fix
Verify your import statement. For CommonJS, use const executeMW = require('execute-middleware');. For ESM, use import executeMW from 'execute-middleware'; or import { serial } from 'execute-middleware';.
error TypeError: next is not a function
cause The `next` argument was not provided when invoking `executeMW.serial` or `executeMW.concurrent`, or a non-function value was passed in its place. The `execute-middleware` library expects `req`, `res`, and `next` arguments after the middleware array.
fix
Ensure you are passing valid req, res, and next mock objects/functions as the second, third, and fourth arguments respectively to executeMW.serial() or executeMW.concurrent().
gotcha Middleware functions are distinguished by argument count: 3 arguments `(req, res, next)` for normal middleware, and 4 arguments `(err, req, res, next)` for error-handling middleware. Incorrect argument counts will lead to unexpected behavior (e.g., an error handler being treated as normal middleware).
fix Ensure your middleware functions match the expected `(req, res, next)` or `(err, req, res, next)` signatures based on their intended purpose.
gotcha Calling `next(someValue)` where `someValue` is not an `Error` object will still cause `execute-middleware` to treat it as an error and trigger the error handling flow, skipping subsequent normal middleware. This mirrors Express's behavior.
fix Only pass an `Error` object (or an object that evaluates to true) to `next()` if you intend to trigger the error handling chain. For normal continuation, call `next()` without arguments.
gotcha Asynchronous operations within middleware (e.g., database calls, API requests) must explicitly call `next()` or `next(err)` upon completion. If `next()` is not called, the middleware chain will hang indefinitely, never progressing to the next step.
fix Always ensure that `next()` or `next(err)` is called exactly once after all asynchronous operations within a middleware function have completed, even if in a `finally` block or an error handler.
npm install execute-middleware
yarn add execute-middleware
pnpm add execute-middleware

Demonstrates serial execution of Express-style middleware, including nested arrays and error handling. It shows how `req`, `res`, and `next` are passed as arguments to `executeMW.serial` and how errors propagate, skipping normal middleware to reach an error handler.

import executeMW from 'execute-middleware';

// Mock Express-like objects for demonstration
const req = {
  url: '/',
  method: 'GET',
  params: {},
  query: {},
  body: {},
  data: {} // For middleware to attach data
};
const res = {
  statusCode: 200,
  _body: null,
  _headers: {},
  send: function(data) {
    this._body = data;
    console.log('Response sent:', data);
  },
  status: function(code) {
    this.statusCode = code;
    return this;
  },
  json: function(data) {
    this.send(JSON.stringify(data));
  },
  set: function(name, value) {
    this._headers[name.toLowerCase()] = value;
    return this;
  }
};

// The final `next` function for the entire chain
const finalNext = (err) => {
  if (err) {
    console.error('Final next caught an error:', err.message);
    if (!res._body) { // Only send if no middleware already sent one
      res.status(500).json({ error: err.message || 'Internal Server Error' });
    }
  } else {
    console.log('All middleware in chain completed successfully.');
    if (!res._body) { // If no middleware sent a response
      res.status(200).send('No response sent by middleware, but chain completed.');
    }
  }
};

// Some middleware
const SOME_MIDDLEWARE = [
  (req, res, next) => {
    console.log('Middleware 1: Initializing request data.');
    req.data.message = 'Hello from MW1';
    next();
  },
  [ // Nested middleware array
    (req, res, next) => {
      console.log('Middleware 2.1: Processing:', req.data.message);
      req.data.processed = true;
      next();
    },
    (err, req, res, next) => { // This is an error handler, won't be called here in normal flow
      console.log('Middleware 2.2 (Error Handler): This should be skipped in normal flow.');
      next(err); // Pass error along if called
    }
  ],
  (req, res, next) => {
      console.log('Middleware 3: Final step, received:', req.data);
      // Simulate sending a response
      res.status(200).json({ status: 'success', data: req.data });
      // Do not call next() here if response is sent, typically ends the chain
  }
];

async function runSerialExample() {
  console.log('--- Running Serial Middleware Example (Normal Flow) ---');
  // Pass in the middleware array, then the usual express (req, res, next) values
  await executeMW.serial(SOME_MIDDLEWARE, req, res, finalNext);
  console.log('--- Serial Middleware Finished (Normal Flow) ---\n');

  // Example with error propagation
  const errorReq = { ...req, data: {} }; // Fresh request object
  const errorRes = { ...res, _body: null }; // Fresh response object

  const ERROR_MIDDLEWARE = [
    (req, res, next) => {
      console.log('Error MW 1: About to trigger an error.');
      // Simulate an asynchronous operation that throws an error
      setTimeout(() => {
        next(new Error('Validation failed for input!'));
      }, 50);
    },
    (req, res, next) => {
      console.log('Error MW 2: This middleware should be skipped because of the error.');
      next(); // This next() will not be called as error handler takes over
    },
    (err, req, res, next) => { // This is an error handler middleware
      console.error('Error MW 3 (Error Handler): Caught error:', err.message);
      errorRes.status(400).json({ message: 'Request Error', detail: err.message });
      next(); // Call next to complete the error handling chain, allowing finalNext to run
    }
  ];
  console.log('--- Running Serial Middleware with Error Example ---');
  await executeMW.serial(ERROR_MIDDLEWARE, errorReq, errorRes, finalNext);
  console.log('--- Serial Middleware Finished (Error Flow) ---');
}

runSerialExample();