Modern Promise-Based Middleware
middleware-io is a lightweight, zero-dependency library for composing promise-based middleware in JavaScript and TypeScript applications. It provides a `compose` function similar to `koa-compose` and a `Composer` class for building middleware chains with various utility 'snippets' like lazy loading, forking, and concurrency control. The library is written in TypeScript, ensuring type safety and a robust development experience. Currently at version 2.8.1, its release cadence has been irregular since its active development phase around 2019-2021, with the latest significant changes related to `exports` in `package.json` for modern module resolution. Key differentiators include its self-sufficiency (zero dependencies), native ESM support, and a rich set of built-in middleware snippets.
Common errors
-
Error [ERR_REQUIRE_ESM]: require() of ES Module ... not supported.
cause Attempting to `require()` the package in a CommonJS environment, but the package is primarily designed for ESM with its `exports` field in `package.json`.fixEnsure your project is set up for ES Modules (`"type": "module"` in `package.json`) and use `import { Symbol } from 'middleware-io'` syntax. Alternatively, use dynamic `import()`: `const { Symbol } = await import('middleware-io')`. -
TypeError: (0 , middleware_io_1.compose) is not a function
cause This typically occurs in transpiled environments (like some Babel or TypeScript configurations targeting CommonJS) where a named ESM import (`import { compose } from 'middleware-io'`) is incorrectly converted, or if `compose` was previously a default export.fixVerify your `tsconfig.json` `module` setting (e.g., `"ESNext"` or `"NodeNext"`) and `moduleResolution`. Ensure you are using named imports: `import { compose } from 'middleware-io'`. -
Argument of type '(...args: any[]) => Promise<any>' is not assignable to parameter of type 'Middleware<object>'.
cause The generic type for `Composer` or `compose` is not correctly inferred or provided, leading to type mismatch errors with the `context` object in your middleware functions.fixExplicitly provide a generic type to `compose` or `Composer` that matches your context object, e.g., `compose<MyContext>(...)` or `new Composer<MyContext>()`.
Warnings
- breaking Node.js 10 and 8 support was dropped in versions 2.8.0 and 2.2.0 respectively. The library now requires Node.js >=12.0.0.
- breaking Version 2.0.0 changed the main export to `compose` by default and removed `MiddlewareStatus`. Ensure your imports are updated to use named imports for `compose`.
- breaking In v2.4.0, the order of arguments for `getBeforeMiddleware` and `getEnforceMiddleware` snippets was reversed. Additionally, the `Composer` class now requires a generic type with an `object` restriction.
- gotcha Since v2.7.0, the library uses the `exports` field in `package.json` for module resolution. This can lead to issues with older tooling or environments that do not correctly support `exports`, particularly for CommonJS `require()` statements trying to access deep paths.
- gotcha In v2.5.0, `Composer` gained an inherited generic, and `compose` was aligned to correspond with `koa-compose` behavior. This might subtly change how middleware functions are expected to behave, especially concerning multiple `next` calls.
Install
-
npm install middleware-io -
yarn add middleware-io -
pnpm add middleware-io
Imports
- compose
const compose = require('middleware-io')import { compose } from 'middleware-io' - Composer
const Composer = require('middleware-io').Composerimport { Composer } from 'middleware-io' - Middleware
import type { Middleware } from 'middleware-io'
Quickstart
import { compose } from 'middleware-io';
interface MyContext {
now?: number;
customData?: string;
}
const composedMiddleware = compose<MyContext>([
async (context, next) => {
// This is the first middleware in the chain.
console.log('Step 1: Before next() in first middleware');
// Pass control to the next middleware in the stack.
await next();
// Control returns here after subsequent middleware complete.
console.log('Step 4: After next() in first middleware');
// Access data modified by later middleware.
console.log(`Context.now from next middleware: ${context.now}`);
},
async (context, next) => {
// This is the second middleware.
console.log('Step 2: Before next() in second middleware');
// Modify the context object, which is shared across the chain.
context.now = Date.now();
context.customData = 'Hello from second middleware!';
// Pass control to the next middleware (or the final handler if no more middleware).
await next();
// Control returns here after the next middleware (or final handler) completes.
console.log('Step 3: After next() in second middleware');
}
]);
// Execute the composed middleware with an initial context and a final handler.
composedMiddleware({}, () => {
console.log('Final handler reached (no more middleware).');
return Promise.resolve(); // Ensure the final handler also returns a Promise
})
.then(() => {
console.log('Middleware chain finished work successfully.');
})
.catch(console.error);