Serverless Compose (Lambda Middleware)
raw JSON →serverless-compose is a lightweight, functional middleware framework specifically designed for AWS Lambda functions, enabling the creation of composable, "onion-style" middleware stacks. Currently at version 2.4.0, its release cadence is not explicitly stated in the provided documentation, but it appears actively maintained. A key differentiator is its zero-dependency philosophy, ensuring a minimal bundle size and avoiding dependency conflicts. Unlike more opinionated frameworks, serverless-compose focuses on providing a thin `compose` function and flexible patterns like `recoveryMiddleware` and `timingLogMiddleware` without enforcing specific architectural designs, allowing developers to define their middleware stack with explicit, functional wrappers around their Lambda handlers. It aims to prevent the "fossilization" of bad middleware habits by promoting a clear, functional approach for event processing.
Common errors
error SyntaxError: Cannot use import statement outside a module ↓
"type": "module" to your package.json, or rename your file to .mjs, or ensure your build process (e.g., Babel, TypeScript compiler, bundler) transpiles ESM to CJS if your environment is CJS-only. error ReferenceError: require is not defined in ES module scope, you can use import instead ↓
require() calls with import statements for serverless-compose and other dependencies. If you need to load CJS modules from an ESM context, use await import('your-cjs-module'). error 502 Internal Server Error (from API Gateway) with errorMessage: "Unhandled Promise Rejection" in CloudWatch logs ↓
recoveryMiddleware is included in your compose stack and its error handling function is correctly implemented to return a structured API Gateway proxy response object (e.g., { statusCode: 500, body: JSON.stringify({ message: 'Error' }) }). Place recoveryMiddleware towards the 'right' in your compose arguments to wrap all preceding middleware and your handler. Warnings
gotcha Middleware order is crucial in `serverless-compose`'s onion-style architecture. Middleware functions are applied from right to left (outermost to innermost) in the `compose` function, but execute in an 'onion' fashion (outer wraps inner). Misordering can lead to unexpected behavior or missed error handling. ↓
gotcha When migrating older CommonJS (CJS) Lambda functions or integrating CJS dependencies, you might encounter issues with `serverless-compose` as it primarily uses ES Modules (ESM). AWS Lambda environments need proper configuration (`"type": "module"` in `package.json` or `.mjs` file extensions) to handle ESM correctly. ↓
gotcha Unhandled promise rejections or uncaught exceptions within your Lambda handler or custom middleware can lead to generic `502 Internal Server Error` responses from API Gateway without informative bodies. `recoveryMiddleware` is designed to mitigate this, but must be configured correctly. ↓
gotcha While the library itself is functional, complex Serverless Framework deployments using shared source directories or monorepos can suffer from path resolution and bundling issues, especially when coupled with plugins like `serverless-esbuild` or `serverless-plugin-typescript`. ↓
Install
npm install serverless-compose yarn add serverless-compose pnpm add serverless-compose Imports
- compose wrong
const { compose } = require('serverless-compose')correctimport { compose } from 'serverless-compose' - recoveryMiddleware wrong
const recoveryMiddleware = require('serverless-compose').recoveryMiddlewarecorrectimport { recoveryMiddleware } from 'serverless-compose' - timingLogMiddleware wrong
import timingLogMiddleware from 'serverless-compose/timingLogMiddleware'correctimport { timingLogMiddleware } from 'serverless-compose'
Quickstart
import { compose, recoveryMiddleware, timingLogMiddleware } from 'serverless-compose';
// Suppose this is a client that fetches the weather from some external API
class WeatherClient {
static async askBadAPIForWeather() {
// Simulate a failing API call 50% of the time
if (Math.random() > 0.5) {
throw new Error('Weather API is down!');
}
return { temperature: 25, unit: 'C' };
}
}
// Your actual handler code
async function getWeather(request, context) {
const response = await WeatherClient.askBadAPIForWeather();
return {
statusCode: 200,
body: JSON.stringify(response),
};
}
// Your own custom logging function
async function logDuration(duration) {
console.log(`It took: ${duration}ms to return the weather`);
}
// Your own custom error handler
async function sendError(error) {
console.error('Handler Error:', error);
return {
statusCode: 500,
body: JSON.stringify({
error: `${error.message}`,
message: 'The darn weather API failed me again!',
}),
};
}
const TimingMiddleware = timingLogMiddleware(logDuration);
const RecoveryMiddleware = recoveryMiddleware(sendError);
const MyMiddlewareStack = compose(
TimingMiddleware,
RecoveryMiddleware,
);
export const lambdaHandler = MyMiddlewareStack(getWeather);