Idempotency Key Middleware for Express APIs
By (author)Saira Gul
read more
Read Me
Idempotency Key Middleware
Drop-in Express middleware that guarantees exactly-once processing for POST, PUT, and PATCH requests — the same pattern Stripe and GitHub use. Stops duplicate charges, duplicate orders, and duplicate side effects caused by network retries, double-clicks, or client-side retry logic, without you writing any deduplication code by hand.
The Problem This Solves
A client calls POST /charge. The request succeeds, but the response is lost on a flaky connection. The client's retry logic fires again. Without protection, the customer just got charged twice. This happens constantly in real production traffic, not just in edge cases.
How It Works
- The client sends a unique Idempotency-Key header with the request.
- On first receipt, the middleware records the request as "processing."
- Your route handler runs normally and sends a response.
- The middleware captures that response and stores it against the key.
- If the same key arrives again:
- Same request body → the original response is replayed; your route handler never runs again.
- Different body → 422 idempotency_key_conflict (misuse of the key).
- Still processing → 409 request_in_progress.
Install
Run: npm install express
Run: npm install mongoose (only needed if you use MongoStore)
Then drop idempotency-middleware.js into your project.
Quick Start
Import idempotencyMiddleware and MemoryStore from ./idempotency-middleware. Create a store with new MemoryStore(), pass it into idempotencyMiddleware({ store }), and register that as Express middleware before your routes. For production or multi-instance deployments, swap MemoryStore for MongoStore (after connecting mongoose) so idempotency records are shared across every instance of your app.
Options
- store (required): MemoryStore or MongoStore instance
- headerName (default "idempotency-key"): the header the client must send
- methods (default POST, PUT, PATCH): which HTTP methods are guarded
- required (default false): if true, requests missing the header get a 400
- ttlSeconds (default 86400): how long a completed record is remembered
Try the Built-In Demo
Run: node idempotency-middleware.js
Then in another terminal, send a POST request to localhost:3000/charge with an Idempotency-Key header set to any value and a JSON body like {"amount":500}. Send the exact same request again — you'll get the identical response back, and the charge count won't increase.
Files Included
- idempotency-middleware.js — the entire middleware, both store adapters, and a runnable demo, in one file
- This Read Me
Requirements
Node.js 16 or higher, Express 4.x, MongoDB (optional, only if using MongoStore)