Idempotency
The idempotency middleware ensures that duplicate requests with the same key return the same response without re-executing the handler. This is critical for payment processing, order creation, and any operation that must not be repeated.
Quick Start
Section titled “Quick Start”import { handler, idempotencyMiddleware, createDefaultIdempotencyStore } from '@venturekit/runtime';
const store = await createDefaultIdempotencyStore();
export const main = handler({ middleware: [idempotencyMiddleware({ store })], fn: async (body) => { const order = await createOrder(body); return { data: order }; },});Clients include an Idempotency-Key header:
curl -X POST https://api.example.com/orders \ -H "Idempotency-Key: order-abc-123" \ -d '{"item": "widget", "qty": 2}'The first request executes normally. Any subsequent request with the same key returns the cached response without calling the handler again.
How It Works
Section titled “How It Works”- Extract idempotency key from the request (default:
Idempotency-Keyheader) - Check the store for an existing record:
- Completed: return the cached response immediately
- Pending: return
409 Conflict(another request is in flight) - Not found: proceed to step 3
- Save a
pendingrecord - Execute the handler
- Update the record to
completedwith the response - On handler error: delete the record (allows retry)
Distributed Store (Production)
Section titled “Distributed Store (Production)”DynamoDB Store
Section titled “DynamoDB Store”import { idempotencyMiddleware, createDynamoDBIdempotencyStore } from '@venturekit/runtime';
const store = await createDynamoDBIdempotencyStore({ tableName: 'venturekit-idempotency', // default});
const idempotency = idempotencyMiddleware({ store, ttlSeconds: 86400 });Auto-Selecting Store
Section titled “Auto-Selecting Store”createDefaultIdempotencyStore() picks the right backend automatically:
- Lambda: DynamoDB (distributed, correct with multiple instances)
- Local dev: In-memory
import { createDefaultIdempotencyStore } from '@venturekit/runtime';
const store = await createDefaultIdempotencyStore();Configuration
Section titled “Configuration”idempotencyMiddleware({ store, // Custom key extractor (default: Idempotency-Key header) keyExtractor: headerKeyExtractor('X-Request-Token'), // TTL in seconds (default: 86400 = 24 hours) ttlSeconds: 3600, // Require idempotency key on all requests (default: false) required: true,});Key Extractors
Section titled “Key Extractors”import { headerKeyExtractor, bodyFieldKeyExtractor } from '@venturekit/runtime';
// From a header (default)headerKeyExtractor('Idempotency-Key')
// From a body fieldbodyFieldKeyExtractor('requestId')Required Mode
Section titled “Required Mode”When required: true, requests without an idempotency key receive a 400 Bad Request:
{ "error": { "code": "MISSING_IDEMPOTENCY_KEY", "message": "Idempotency-Key header is required" }}With Saga Pattern
Section titled “With Saga Pattern”The saga pattern has built-in idempotency via explicit IDs and step tracking. When combined with the idempotency middleware, you get end-to-end protection:
import { handler, idempotencyMiddleware, saga, createDefaultIdempotencyStore, createDefaultSagaStore } from '@venturekit/runtime';
const idempotencyStore = await createDefaultIdempotencyStore();const sagaStore = await createDefaultSagaStore();
const createOrderSaga = saga('create-order', steps, { store: sagaStore });
export const main = handler({ middleware: [idempotencyMiddleware({ store: idempotencyStore, required: true })], fn: async (body, ctx) => { const result = await createOrderSaga.run(body, { id: ctx.rawEvent.headers?.['idempotency-key'], // reuse as saga ID traceId: ctx.trace?.traceId, }); return { data: result.context }; },});DynamoDB Table Schema
Section titled “DynamoDB Table Schema”The idempotency store table needs:
- Partition key:
pk(String) - TTL attribute:
ttl(Number)
aws dynamodb create-table \ --table-name venturekit-idempotency \ --attribute-definitions AttributeName=pk,AttributeType=S \ --key-schema AttributeName=pk,KeyType=HASH \ --billing-mode PAY_PER_REQUEST
aws dynamodb update-time-to-live \ --table-name venturekit-idempotency \ --time-to-live-specification "Enabled=true, AttributeName=ttl"Related
Section titled “Related”- Saga Pattern — distributed transactions with compensation
- Lambda Invocation — calling other functions
- Middleware — rate limiting, error handling