JavaScript Middleware Pattern Implementation

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

js-middleware is a versatile JavaScript library designed to implement a powerful middleware pattern, allowing developers to inject custom logic into the execution flow of any object's methods. It aims to bring the scalability and maintainability benefits seen in frameworks like ReduxJS and ExpressJS to general-purpose JavaScript objects. The package is currently at version 0.3.1, with a recent patch 0.3.2, indicating it's actively maintained, though possibly with a slower release cadence. Its primary differentiator is the ability to apply middleware to arbitrary methods of any class or object instance, providing a flexible mechanism to modify arguments, perform side effects, or control method execution. This approach fosters highly modular and testable code by separating cross-cutting concerns from core business logic, making it suitable for extending existing objects without direct modification.

error TypeError: next is not a function
cause The middleware function did not correctly return a function that accepts `next`, or `next` was not properly called.
fix
Ensure your middleware follows the target => next => (...args) => { /* ... */ return next(...args); } signature. Double-check that next is correctly passed and invoked.
error Target function 'myMethod' not called / Process hangs indefinitely
cause A middleware in the chain failed to call `next()`, thereby preventing subsequent middleware and the original target function from executing.
fix
Review all middleware functions applied to the method and ensure that next(...args) is called in each middleware unless an explicit termination of the chain is intended (e.g., for short-circuiting).
error Middleware not applying to 'my_internal_method'
cause The target method's name starts or ends with an underscore (`_`), which makes it exempt from middleware application by default.
fix
Rename the method to remove leading/trailing underscores, or explicitly define the method as a middleware target using the middlewareMethods property in a middleware object/class passed to use().
gotcha Failing to call `next()` within a middleware function will halt the middleware chain and prevent the original target function from executing. This can lead to silent failures or processes hanging indefinitely.
fix Always ensure `next(...args)` is called at some point within your middleware function, unless you explicitly intend to terminate the chain early and handle the response yourself.
gotcha By default, methods on the target object that start or end with an underscore (`_method` or `method_`) are excluded from middleware application. This is a design choice to prevent unintended interception of internal methods.
fix Rename your methods to not use leading/trailing underscores if you intend for middleware to apply to them. Alternatively, if applying middleware via an object, use the `middlewareMethods` property in the middleware class/object to explicitly define which methods should be targeted.
gotcha Middleware functions can directly mutate the arguments passed to them, which can lead to unexpected side effects if not handled carefully. The pattern allows for modification, but consumers should be aware of this potential for state changes.
fix If immutability is desired for arguments, create a copy of the arguments before modification within the middleware, e.g., `const newArgs = [...args];`.
gotcha The current implementation examples primarily demonstrate synchronous middleware. While it might be possible to integrate asynchronous operations, the provided `next()` mechanism is synchronous. Expecting `await next()` without specific async handling by the library could lead to issues.
fix For asynchronous operations within middleware, ensure that the asynchronous task is fully completed before `next()` is called, or manage promises explicitly. For simple cases, `async/await` directly within the middleware function body should work, but `next()` itself is not `awaitable` without custom wrapping.
gotcha When using the library in a browser environment by including `dist/middleware.min.js`, the `MiddlewareManager` constructor is exposed as `window.MiddlewareManager`. This global variable could potentially clash with other libraries that also define `MiddlewareManager` globally.
fix To avoid global conflicts, use the npm package and a bundler (like Webpack or Rollup) to scope the import, or encapsulate the usage within an IIFE (Immediately Invoked Function Expression) in browser environments.
npm install js-middleware
yarn add js-middleware
pnpm add js-middleware

Demonstrates how to initialize MiddlewareManager, apply multiple middleware functions to a target object's method, and observe their effects on method arguments and execution flow.

class Person {
  walk(step) {
    this.step = step;
    console.log(`Person walked ${step} steps.`);
  }
  speak(word) {
    this.word = word;
    console.log(`Person spoke: ${word}`);
  }
}

const logger = target => next => (...args) => {
  console.log(`[Logger Middleware] Calling ${target.constructor.name}'s method with args: ${JSON.stringify(args)}.`);
  const result = next(...args);
  console.log(`[Logger Middleware] Method finished.`);
  return result;
};

const argModifier = target => next => (step) => {
    if (typeof step === 'number') {
        step = step * 2; // Double the steps
        console.log(`[ArgModifier Middleware] Doubling steps to: ${step}`);
    }
    return next(step);
};

const p = new Person();
const middlewareManager = new MiddlewareManager(p);

middlewareManager.use('walk', logger);
middlewareManager.use('walk', argModifier);

console.log('--- Calling walk (with middleware) ---');
p.walk(5); // Will log 'walk start, steps: 5.', 'Doubling steps to: 10', 'Person walked 10 steps.', 'walk end.'

middlewareManager.use('speak', logger);
console.log('\n--- Calling speak (with middleware) ---');
p.speak('Hello World');