The stylish Node.js middleware engine for AWS Lambda (core package)
npx @tessl/cli install tessl/npm-middy--core@6.4.0Middy Core is the stylish Node.js middleware engine for AWS Lambda. It provides a fluent, plugin-based architecture that enables developers to create composable and reusable middleware patterns for serverless applications. The core package handles before, after, and error middleware execution, supports both traditional and streaming response patterns, includes built-in timeout handling, and provides a clean API for creating maintainable Lambda functions.
npm install @middy/coreimport middy from "@middy/core";For CommonJS (also supported):
const middy = require("@middy/core");import middy from "@middy/core";
// Basic Lambda handler
const lambdaHandler = async (event, context) => {
return { statusCode: 200, body: "Hello World" };
};
// Create middlewared handler
const handler = middy(lambdaHandler)
.use(someMiddleware())
.before(async (request) => {
console.log("Before handler", request.event);
})
.after(async (request) => {
console.log("After handler", request.response);
})
.onError(async (request) => {
console.log("Error occurred", request.error);
});
export { handler };Middy Core is built around several key components:
middy() function creates middlewared Lambda handlersCreates a middlewared Lambda handler with fluent API for attaching middleware.
/**
* Middy factory function. Use it to wrap your existing handler to enable middlewares on it.
* @param handler - Your original AWS Lambda function or PluginObject
* @param plugin - Plugin configuration for lifecycle hooks
* @returns MiddyfiedHandler with middleware methods
*/
function middy<TEvent = unknown, TResult = any, TErr = Error, TContext extends LambdaContext = LambdaContext, TInternal extends Record<string, unknown> = {}>(
handler?: LambdaHandler<TEvent, TResult> | MiddlewareHandler<LambdaHandler<TEvent, TResult>, TContext, TResult, TEvent> | PluginObject,
plugin?: PluginObject
): MiddyfiedHandler<TEvent, TResult, TErr, TContext, TInternal>;The enhanced Lambda handler returned by middy() with middleware attachment methods. It extends both MiddyInputHandler and MiddyInputPromiseHandler to support both callback and Promise-based execution patterns.
interface MiddyfiedHandler<TEvent = any, TResult = any, TErr = Error, TContext extends LambdaContext = LambdaContext, TInternal extends Record<string, unknown> = {}> extends MiddyInputHandler<TEvent, TResult, TContext>, MiddyInputPromiseHandler<TEvent, TResult, TContext> {
/** Attach middleware objects or arrays of middleware objects */
use: (middlewares: MiddlewareObj | MiddlewareObj[]) => MiddyfiedHandler<TEvent, TResult, TErr, TContext, TInternal>;
/** Attach before middleware function */
before: (middleware: MiddlewareFn<TEvent, TResult, TErr, TContext, TInternal>) => MiddyfiedHandler<TEvent, TResult, TErr, TContext, TInternal>;
/** Attach after middleware function */
after: (middleware: MiddlewareFn<TEvent, TResult, TErr, TContext, TInternal>) => MiddyfiedHandler<TEvent, TResult, TErr, TContext, TInternal>;
/** Attach error middleware function */
onError: (middleware: MiddlewareFn<TEvent, TResult, TErr, TContext, TInternal>) => MiddyfiedHandler<TEvent, TResult, TErr, TContext, TInternal>;
/** Replace the Lambda handler function */
handler: <TInputHandlerEventProps = TEvent, TInputHandlerResultProps = TResult>(
handler: MiddyInputHandler<TInputHandlerEventProps, TInputHandlerResultProps, TContext>
) => MiddyfiedHandler<TInputHandlerEventProps, TInputHandlerResultProps, TErr, TContext, TInternal>;
}The request object passed to all middleware functions containing event, context, response, and internal state.
interface Request<TEvent = any, TResult = any, TErr = Error, TContext extends LambdaContext = LambdaContext, TInternal extends Record<string, unknown> = {}> {
/** The Lambda event object */
event: TEvent;
/** The Lambda context object */
context: TContext;
/** Handler response (null initially, set by handler or middleware) */
response: TResult | null;
/** Optional early response value for short-circuiting */
earlyResponse?: TResult | null | undefined;
/** Error object (null initially, set if error occurs) */
error: TErr | null;
/** Internal state object for sharing data between middleware */
internal: TInternal;
}Function signature for individual middleware functions.
/**
* Middleware function signature
* @param request - The request object containing event, context, response, and internal state
* @returns Any value (can be Promise)
*/
type MiddlewareFn<TEvent = any, TResult = any, TErr = Error, TContext extends LambdaContext = LambdaContext, TInternal extends Record<string, unknown> = {}> = (
request: Request<TEvent, TResult, TErr, TContext, TInternal>
) => any;Object structure for defining middleware with before, after, and error hooks.
interface MiddlewareObj<TEvent = unknown, TResult = any, TErr = Error, TContext extends LambdaContext = LambdaContext, TInternal extends Record<string, unknown> = {}> {
/** Optional before middleware function */
before?: MiddlewareFn<TEvent, TResult, TErr, TContext, TInternal>;
/** Optional after middleware function */
after?: MiddlewareFn<TEvent, TResult, TErr, TContext, TInternal>;
/** Optional error middleware function */
onError?: MiddlewareFn<TEvent, TResult, TErr, TContext, TInternal>;
/** Optional middleware name for debugging */
name?: string;
}Configuration object for lifecycle hooks and advanced functionality.
interface PluginObject {
/** Internal state object shared across middleware */
internal?: any;
/** Hook called before prefetch operations */
beforePrefetch?: PluginHook;
/** Hook called at request start */
requestStart?: PluginHook;
/** Hook called before each middleware */
beforeMiddleware?: PluginHookWithMiddlewareName;
/** Hook called after each middleware */
afterMiddleware?: PluginHookWithMiddlewareName;
/** Hook called before handler execution */
beforeHandler?: PluginHook;
/** Timeout in milliseconds before Lambda timeout for early response */
timeoutEarlyInMillis?: number;
/** Function to call when timeout occurs */
timeoutEarlyResponse?: PluginHook;
/** Hook called after handler execution */
afterHandler?: PluginHook;
/** Hook called at request end */
requestEnd?: PluginHookPromise;
/** Enable AWS Lambda streaming response support */
streamifyResponse?: boolean;
}Object passed to Lambda handlers containing abort signal for timeout handling.
interface MiddyHandlerObject {
/**
* An abort signal that will be canceled just before the lambda times out.
* Use this to cancel long-running operations gracefully.
*/
signal: AbortSignal;
}import middy from "@middy/core";
const middleware1 = {
before: async (request) => {
console.log("Middleware 1 before");
},
after: async (request) => {
console.log("Middleware 1 after");
},
name: "middleware1"
};
const middleware2 = {
before: async (request) => {
console.log("Middleware 2 before");
},
onError: async (request) => {
console.log("Middleware 2 error handler");
},
name: "middleware2"
};
const handler = middy(lambdaHandler)
.use([middleware1, middleware2]);import middy from "@middy/core";
const pluginConfig = {
timeoutEarlyInMillis: 1000, // Timeout 1 second before Lambda timeout
timeoutEarlyResponse: () => {
throw new Error("Function timed out");
},
beforeMiddleware: (name) => console.log(`Starting ${name}`),
afterMiddleware: (name) => console.log(`Completed ${name}`),
internal: { startTime: Date.now() }
};
const handler = middy(lambdaHandler, pluginConfig);import middy from "@middy/core";
// Create handler without initial function
const handler = middy()
.use(someMiddleware())
.handler(async (event, context, { signal }) => {
// Use abort signal for cancellation
const controller = new AbortController();
signal.addEventListener('abort', () => controller.abort());
return await fetch('https://api.example.com', {
signal: controller.signal
});
});
// Or replace an existing handler
const existingHandler = middy(lambdaHandler);
existingHandler.handler(async (event, context, { signal }) => {
// Completely replace the original handler logic
return { statusCode: 200, body: "New handler implementation" };
});import middy from "@middy/core";
const pluginConfig = {
streamifyResponse: true
};
const handler = middy(async (event, responseStream, context) => {
responseStream.write("Streaming data chunk 1\n");
responseStream.write("Streaming data chunk 2\n");
responseStream.end();
}, pluginConfig);import middy from "@middy/core";
const authMiddleware = {
before: async (request) => {
if (!request.event.headers.authorization) {
// Short-circuit the execution chain
request.earlyResponse = {
statusCode: 401,
body: JSON.stringify({ error: "Unauthorized" })
};
return;
}
}
};
const handler = middy(lambdaHandler)
.use(authMiddleware);import middy from "@middy/core";
const errorHandlerMiddleware = {
onError: async (request) => {
console.error("Error occurred:", request.error);
// Modify response based on error
request.response = {
statusCode: 500,
body: JSON.stringify({
error: "Internal Server Error",
requestId: request.context.awsRequestId
})
};
}
};
const handler = middy(lambdaHandler)
.use(errorHandlerMiddleware);Package constants available for reference:
/** Default no-op Lambda handler */
declare const defaultLambdaHandler: () => void;
/** Default plugin configuration object */
declare const defaultPluginConfig: PluginObject;
/** Chunk size for string iteration in streaming responses */
declare const stringIteratorSize: number; // 16384The defaultPluginConfig object contains these default values:
const defaultPluginConfig = {
timeoutEarlyInMillis: 5,
timeoutEarlyResponse: () => {
const err = new Error("[AbortError]: The operation was aborted.", {
cause: { package: "@middy/core" }
});
err.name = "TimeoutError";
throw err;
},
streamifyResponse: false
};All TypeScript types and interfaces are exported in the middy namespace:
declare namespace middy {
export type {
Request,
PluginHook,
PluginHookWithMiddlewareName,
PluginObject,
MiddlewareFn,
MiddlewareObj,
MiddyfiedHandler,
};
}
/** Plugin hook function signature */
type PluginHook = () => void;
/** Plugin hook function with middleware name parameter */
type PluginHookWithMiddlewareName = (middlewareName: string) => void;
/** Plugin hook function that can return a promise */
type PluginHookPromise = (request: Request) => Promise<unknown> | unknown;
/** Handler attachment function signature */
type AttachMiddlewareFn<TEvent = any, TResult = any, TErr = Error, TContext extends LambdaContext = LambdaContext, TInternal extends Record<string, unknown> = {}> = (
middleware: MiddlewareFn<TEvent, TResult, TErr, TContext, TInternal>
) => MiddyfiedHandler<TEvent, TResult, TErr, TContext, TInternal>;
/** Middleware use function signature */
type UseFn<TEvent = any, TResult = any, TErr = Error, TContext extends LambdaContext = LambdaContext, TInternal extends Record<string, unknown> = {}> = <TMiddleware extends MiddlewareObj<any, any, Error, any, any>>(
middlewares: TMiddleware | TMiddleware[]
) => MiddyfiedHandler<TEvent, TResult, TErr, TContext, TInternal>;
/** Handler function with optional MiddyHandlerObject parameter */
type MiddyInputHandler<TEvent, TResult, TContext extends LambdaContext = LambdaContext> = (
event: TEvent,
context: TContext,
opts: MiddyHandlerObject
) => undefined | Promise<TResult> | TResult;
/** Standard Promise-based handler function */
type MiddyInputPromiseHandler<TEvent, TResult, TContext extends LambdaContext = LambdaContext> = (
event: TEvent,
context: TContext
) => Promise<TResult>;