The makeHandlerIdempotent function creates Middy middleware that makes Lambda handlers idempotent. It integrates seamlessly with the Middy middleware framework.
Creates Middy middleware to make Lambda handlers idempotent by tracking executions in a persistence store.
/**
* Creates Middy middleware to make Lambda handlers idempotent.
*
* The middleware runs before the handler executes to check for existing records,
* after the handler completes to save the result, and on errors to clean up.
*
* @param options - Configuration options for idempotency behavior
* @returns Middy middleware object with before, after, and onError hooks
*/
function makeHandlerIdempotent(
options: IdempotencyLambdaHandlerOptions
): MiddlewareLikeObj;
interface IdempotencyLambdaHandlerOptions {
/** Persistence layer to store idempotency records */
persistenceStore: BasePersistenceLayer;
/** Optional configuration for idempotency behavior */
config?: IdempotencyConfig;
/** Optional custom prefix for idempotency keys */
keyPrefix?: string;
}
interface MiddlewareLikeObj {
/** Runs before the handler executes */
before: (request: MiddyLikeRequest) => unknown;
/** Runs after successful handler execution */
after: (request: MiddyLikeRequest) => Promise<void>;
/** Runs when an error occurs in the handler */
onError: (request: MiddyLikeRequest) => Promise<void>;
}Usage Examples:
Basic Middy Middleware Usage
import { makeHandlerIdempotent } from '@aws-lambda-powertools/idempotency/middleware';
import { DynamoDBPersistenceLayer } from '@aws-lambda-powertools/idempotency/dynamodb';
import middy from '@middy/core';
import type { Context, APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
const persistenceStore = new DynamoDBPersistenceLayer({
tableName: 'idempotencyTable',
});
const lambdaHandler = async (
event: APIGatewayProxyEvent,
context: Context
): Promise<APIGatewayProxyResult> => {
// Your business logic
return {
statusCode: 200,
body: JSON.stringify({ message: 'Success' }),
};
};
export const handler = middy(lambdaHandler).use(
makeHandlerIdempotent({ persistenceStore })
);With Configuration Options
import { makeHandlerIdempotent } from '@aws-lambda-powertools/idempotency/middleware';
import { IdempotencyConfig } from '@aws-lambda-powertools/idempotency';
import { DynamoDBPersistenceLayer } from '@aws-lambda-powertools/idempotency/dynamodb';
import middy from '@middy/core';
import type { Context, APIGatewayProxyEvent } from 'aws-lambda';
const persistenceStore = new DynamoDBPersistenceLayer({
tableName: 'idempotencyTable',
});
const config = new IdempotencyConfig({
hashFunction: 'md5',
useLocalCache: true,
expiresAfterSeconds: 3600,
throwOnNoIdempotencyKey: false,
eventKeyJmesPath: 'body',
});
export const handler = middy(
async (event: APIGatewayProxyEvent, context: Context): Promise<void> => {
// Your logic
}
).use(makeHandlerIdempotent({ config, persistenceStore }));Using JMESPath for Idempotency Key
import { makeHandlerIdempotent } from '@aws-lambda-powertools/idempotency/middleware';
import { IdempotencyConfig } from '@aws-lambda-powertools/idempotency';
import { DynamoDBPersistenceLayer } from '@aws-lambda-powertools/idempotency/dynamodb';
import middy from '@middy/core';
import type { Context, APIGatewayProxyEvent } from 'aws-lambda';
const persistenceStore = new DynamoDBPersistenceLayer({
tableName: 'idempotencyTable',
});
const config = new IdempotencyConfig({
// Use a specific header as the idempotency key
eventKeyJmesPath: 'headers."idempotency-key"',
});
export const handler = middy(
async (event: APIGatewayProxyEvent, context: Context): Promise<void> => {
// Your logic
}
).use(makeHandlerIdempotent({ config, persistenceStore }));With Multiple Middleware
import { makeHandlerIdempotent } from '@aws-lambda-powertools/idempotency/middleware';
import { DynamoDBPersistenceLayer } from '@aws-lambda-powertools/idempotency/dynamodb';
import middy from '@middy/core';
import httpErrorHandler from '@middy/http-error-handler';
import httpJsonBodyParser from '@middy/http-json-body-parser';
import type { Context, APIGatewayProxyEvent } from 'aws-lambda';
const persistenceStore = new DynamoDBPersistenceLayer({
tableName: 'idempotencyTable',
});
const lambdaHandler = async (
event: APIGatewayProxyEvent,
context: Context
): Promise<void> => {
// Your logic - body is already parsed by httpJsonBodyParser
console.log(event.body);
};
export const handler = middy(lambdaHandler)
.use(httpJsonBodyParser())
.use(makeHandlerIdempotent({ persistenceStore }))
.use(httpErrorHandler());With Custom Key Prefix
import { makeHandlerIdempotent } from '@aws-lambda-powertools/idempotency/middleware';
import { DynamoDBPersistenceLayer } from '@aws-lambda-powertools/idempotency/dynamodb';
import middy from '@middy/core';
import type { Context, APIGatewayProxyEvent } from 'aws-lambda';
const persistenceStore = new DynamoDBPersistenceLayer({
tableName: 'idempotencyTable',
});
export const handler = middy(
async (event: APIGatewayProxyEvent, context: Context): Promise<void> => {
// Your logic
}
).use(
makeHandlerIdempotent({
persistenceStore,
keyPrefix: 'payment-service',
})
);With Cache Persistence
import { makeHandlerIdempotent } from '@aws-lambda-powertools/idempotency/middleware';
import { CachePersistenceLayer } from '@aws-lambda-powertools/idempotency/cache';
import { IdempotencyConfig } from '@aws-lambda-powertools/idempotency';
import { createClient } from '@redis/client';
import middy from '@middy/core';
import type { Context, APIGatewayProxyEvent } from 'aws-lambda';
// Create and connect Redis client
const redisClient = await createClient({
url: `rediss://${process.env.REDIS_ENDPOINT}:${process.env.REDIS_PORT}`,
}).connect();
const persistenceStore = new CachePersistenceLayer({
client: redisClient,
});
const config = new IdempotencyConfig({
expiresAfterSeconds: 300, // 5 minutes
useLocalCache: true,
});
export const handler = middy(
async (event: APIGatewayProxyEvent, context: Context): Promise<void> => {
// Your logic
}
).use(makeHandlerIdempotent({ persistenceStore, config }));The middleware operates in three phases:
Before Hook:
After Hook:
OnError Hook:
undefined. This is a known limitation of the early return feature in Middy.jsundefined, use the makeIdempotent() function wrapper insteadmakeHandlerIdempotent after input parsing middleware (like httpJsonBodyParser) but before error handling middlewareeventKeyJmesPath to use only part of the eventThe middleware may throw various idempotency errors:
IdempotencyKeyError - When no idempotency key can be extractedIdempotencyItemAlreadyExistsError - When a duplicate request is detectedIdempotencyAlreadyInProgressError - When a request is already being processedIdempotencyValidationError - When payload validation failsWhen errors occur, the middleware's onError hook cleans up the in-progress record to allow retries.