Compose Middleware
compose-middleware is a utility library designed to combine an array of Express- or Connect-style middleware functions into a single, cohesive middleware function. This package is currently stable at version 5.0.1, with recent updates primarily focusing on TypeScript type improvements and stricter function signatures. Historically, major versions have introduced changes to error handling logic and middleware validation. Its key differentiators include built-in support for inline error handling middleware, which can be composed separately using the `errors` export, and robust validation of middleware functions to prevent common pitfalls in middleware chains. It provides a flexible way to structure complex middleware pipelines for web frameworks.
Common errors
-
Error: next() called multiple times
cause A middleware function within the composed chain called `next()` more than once for a single request, which is disallowed since v2.2.0.fixReview the middleware where the error originates and ensure `next()` is called exactly once. Use `return next();` to prevent accidental subsequent execution or wrap asynchronous calls in promises. -
Argument of type '(...)' is not assignable to parameter of type '(...)'
cause This TypeScript error indicates a mismatch in middleware function signatures, often due to the stricter type definitions introduced in v5.0.0, or incorrect argument counts (e.g., passing a 4-arg function where a 3-arg is expected).fixExplicitly define types for `req`, `res`, `next`, and `err` (if applicable) in your middleware functions. Ensure 3-argument middleware is used with `compose` and 4-argument middleware with `errors`, aligning with the library's expected types. -
Error: Middleware must be a function
cause An element passed into the array for `compose` or `errors` was not a JavaScript function. This validation was added in v2.0.0.fixVerify that all items in the array passed to `compose` or `errors` are indeed functions, and not `null`, `undefined`, or other data types.
Warnings
- breaking Version 5.0.0 introduced stricter TypeScript type signatures, removing optional types from function parameters. This may cause type errors in existing TypeScript projects.
- breaking Version 3.0.0 changed the error re-throwing mechanism. Errors thrown synchronously from `done()` or other inconsistent states are now re-thrown higher up the stack, which might alter error handling flow for complex or edge-case error patterns.
- breaking Version 2.2.0 introduced validation that throws an error if `next()` is called multiple times within a single middleware execution. This prevents common pitfalls and ensures predictable middleware execution.
- breaking Version 2.0.0 added validation for all middleware functions and introduced support for 4-argument error handlers. Providing non-function middleware or incorrectly structured error handlers will now result in errors.
- gotcha The `compose` function returns a 3-argument middleware function (req, res, next), suitable for regular request processing. The `errors` function returns a 4-argument error middleware function (err, req, res, next).
Install
-
npm install compose-middleware -
yarn add compose-middleware -
pnpm add compose-middleware
Imports
- compose
const compose = require('compose-middleware').composeimport { compose } from 'compose-middleware' - errors
const errors = require('compose-middleware').errorsimport { errors } from 'compose-middleware' - ComposeMiddleware
import type { ComposeMiddleware } from 'compose-middleware'
Quickstart
import express from 'express';
import { compose, errors } from 'compose-middleware';
const app = express();
// A basic middleware function
const logRequest = (req: express.Request, res: express.Response, next: express.NextFunction) => {
console.log(`Request received: ${req.method} ${req.url}`);
next();
};
// An error-handling middleware
const handleError = (err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => {
console.error(`An error occurred: ${err.message}`);
res.status(500).send('Something broke!');
};
// Composing a sequence of regular middleware
app.use(compose([
logRequest,
(req, res, next) => { req.body = req.body || {}; next(); },
(req, res, next) => { console.log('Processing request...'); next(); }
]));
// Composing a sequence of error-handling middleware
app.use(errors([
(err, req, res, next) => { if (err.message === 'validation error') { console.log('Validation failed'); return next(err); } next(err); },
handleError
]));
// Example route to trigger middleware
app.get('/', (req, res) => {
res.send('Hello World!');
});
// Example route to trigger an error
app.get('/error', (req, res, next) => {
next(new Error('This is an intentional error!'));
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});