{"id":17615,"library":"express-idempotency","title":"Idempotency Middleware for Express","description":"express-idempotency is an Express.js middleware designed to add idempotency to API routes, inspired by Stripe's implementation. It ensures that repeated requests with the same idempotency key result in the same outcome, preventing duplicate processing of non-idempotent operations. The current stable version is 2.0.0. The package has seen consistent updates, with recent releases focusing on dependency upgrades and security fixes, indicating an active maintenance cadence rather than rapid feature development. Key differentiators include its high level of customization for data adapters, intent validators, and response validators, allowing developers to integrate it deeply with their existing infrastructure and persistence layers. It also provides helpers to detect idempotency hits and report errors, facilitating robust error handling within route handlers. It requires Node.js >=18.0.0 and npm >=9.0.0, aligning with modern JavaScript ecosystem standards. The library ships with TypeScript types, promoting strong typing in projects.","status":"active","version":"2.0.0","language":"javascript","source_language":"en","source_url":"https://github.com/VilledeMontreal/express-idempotency","tags":["javascript","express","middleware","idempotent","idempotency","typescript"],"install":[{"cmd":"npm install express-idempotency","lang":"bash","label":"npm"},{"cmd":"yarn add express-idempotency","lang":"bash","label":"yarn"},{"cmd":"pnpm add express-idempotency","lang":"bash","label":"pnpm"}],"dependencies":[{"reason":"Required as a peer dependency for any Express.js application.","package":"express","optional":true},{"reason":"Used internally for HTTP status code management.","package":"http-status-codes","optional":false},{"reason":"A runtime dependency to bind methods correctly, fixed as a critical dependency in v1.0.3.","package":"autobind-decorator","optional":false}],"imports":[{"note":"The primary export is a named middleware function. While CommonJS `require` can also be used with destructuring (e.g., `const { idempotency } = require('express-idempotency');`), ESM `import` statements are idiomatic for modern Node.js and TypeScript environments, especially since v2.0.0.","wrong":"const idempotency = require('express-idempotency'); // This tries to call the module directly, not the named export","symbol":"idempotency","correct":"import { idempotency } from 'express-idempotency';"},{"note":"This is a TypeScript interface for defining custom data storage. Use `import type` to clearly indicate it's a type-only import and prevent potential bundling issues or runtime overhead.","wrong":"import { IIdempotencyDataAdapter } from 'express-idempotency'; // Imports as a value instead of a type","symbol":"IIdempotencyDataAdapter","correct":"import type { IIdempotencyDataAdapter } from 'express-idempotency';"},{"note":"This helper function retrieves the singleton `IdempotencyService` instance, which is internally managed by the middleware after initialization. It is typically available in the application context after `idempotency()` middleware has been applied, rather than being a direct named module export.","wrong":"import { getSharedIdempotencyService } from 'express-idempotency'; // This function is not directly exported as a module member","symbol":"getSharedIdempotencyService","correct":"const idempotencyService = getSharedIdempotencyService();"}],"quickstart":{"code":"import express from 'express';\nimport { idempotency, IIdempotencyDataAdapter } from 'express-idempotency';\nimport { v4 as uuidv4 } from 'uuid';\n\n// IMPORTANT: For production, replace this with a persistent storage solution (Redis, MongoDB, etc.)\n// The default in-memory adapter is not suitable for production environments.\nclass InMemoryDataAdapter implements IIdempotencyDataAdapter {\n    private store = new Map<string, { request: any; response: any; status: string }>();\n\n    async get(idempotencyKey: string): Promise<{ request: any; response: any; status: string } | null> {\n        return this.store.get(idempotencyKey) || null;\n    }\n\n    async set(idempotencyKey: string, request: any, response: any, status: string): Promise<void> {\n        this.store.set(idempotencyKey, { request, response, status });\n    }\n\n    async remove(idempotencyKey: string): Promise<void> {\n        this.store.delete(idempotencyKey);\n    }\n}\n\nconst app = express();\nconst port = 3000;\n\n// Middleware to parse JSON bodies\napp.use(express.json());\n\n// Initialize the idempotency middleware. Always provide a production-ready data adapter.\napp.use(\n    idempotency({\n        dataAdapter: new InMemoryDataAdapter(), // Replace with a real data adapter for production!\n        // Other options like idempotencyKeyHeader, intentValidator, responseValidator can be customized here.\n    })\n);\n\n// Declare `getSharedIdempotencyService` for TypeScript if it's globally available.\n// In a real application, consider explicitly importing a service factory if available,\n// or accessing a request-scoped service if the middleware attaches it (e.g., `req.idempotencyService`).\ndeclare function getSharedIdempotencyService(): {\n    isHit(req: express.Request): boolean;\n    reportError(req: express.Request): void;\n};\n\napp.post('/process-payment', (req, res) => {\n    // Retrieve the IdempotencyService instance.\n    const idempotencyService = getSharedIdempotencyService();\n\n    // Crucial: Check if the request is an idempotency hit. If so, prevent further processing.\n    if (idempotencyService.isHit(req)) {\n        console.log('Idempotency hit detected, a cached response should have been sent by the middleware.');\n        // The middleware is designed to send the cached response and then call next().\n        // Your route handler should return early here to avoid re-executing business logic.\n        return;\n    }\n\n    // --- Your business logic starts here (only for non-idempotent requests) ---\n    console.log('Processing new payment for:', req.body);\n    const transactionId = uuidv4();\n    const amount = req.body.amount;\n\n    if (amount <= 0) {\n        // If an error occurs during processing, report it to the middleware\n        // so that the idempotency state for this key can be cleared or updated.\n        idempotencyService.reportError(req);\n        return res.status(400).json({ error: 'Amount must be positive.' });\n    }\n\n    // Simulate an asynchronous payment processing operation\n    setTimeout(() => {\n        const responseData = {\n            message: `Payment for ${amount} processed successfully.`,\n            transactionId: transactionId,\n            status: 'completed',\n        };\n        res.status(200).json(responseData);\n        console.log('Payment processed and response sent.');\n    }, 1000);\n});\n\napp.listen(port, () => {\n    console.log(`Server listening at http://localhost:${port}`);\n    console.log(`\nTo test, send a POST request with an 'Idempotency-Key' header:`)\n    console.log(`curl -X POST -H \"Content-Type: application/json\" -H \"Idempotency-Key: my-unique-key-123\" -d '{\"amount\": 100}' http://localhost:${port}/process-payment`);\n    console.log(`\nRepeat the curl command with the *same* 'my-unique-key-123' to observe idempotency in action (cached response).`);\n    console.log(`Use a *different* key for a new payment.`);\n});","lang":"typescript","description":"This quickstart demonstrates how to install `express-idempotency`, initialize it with a basic in-memory data adapter, and integrate it into an Express route. It highlights how to use `isHit(req)` to prevent re-processing and `reportError(req)` for failed operations, ensuring idempotent behavior for a POST request."},"warnings":[{"fix":"Upgrade Node.js to version 18.0.0 or higher and npm to version 9.0.0 or higher. For example, `nvm install 18 && nvm use 18`.","message":"Version 2.0.0 updated the minimum required Node.js version to `>=18.0.0` and npm to `>=9.0.0`. Projects running on older environments must upgrade their Node.js and npm installations.","severity":"breaking","affected_versions":">=2.0.0"},{"fix":"Implement a custom `IIdempotencyDataAdapter` using a persistent storage solution like Redis, MongoDB, PostgreSQL, or a distributed cache. The library provides examples for custom adapters, and dedicated adapters might be available (e.g., `express-idempotency-mongo-adapter`).","message":"The default data adapter provided by `express-idempotency` is an in-memory store, which is explicitly stated as not suitable for production environments due to lack of persistence across restarts and inability to scale. Using it in production can lead to lost idempotency state.","severity":"gotcha","affected_versions":">=1.0.0"},{"fix":"Always include `if (idempotencyService.isHit(req)) { return; }` at the beginning of your route handler's business logic to ensure it's skipped on idempotency hits.","message":"The idempotency middleware is designed to always call `next()` in the Express chain, even if an idempotency hit is detected and a cached response is sent. Developers must explicitly check `idempotencyService.isHit(req)` within their route handler and `return` early to prevent accidental execution of business logic for replayed requests.","severity":"gotcha","affected_versions":">=1.0.0"},{"fix":"Understand that `getSharedIdempotencyService()` provides access to the middleware's internal service instance. For testing, consider mocking this global function or structuring your code to inject the service instance if custom patterns allow. Ensure the middleware is initialized before this function is called.","message":"The `getSharedIdempotencyService()` helper function's availability and exact mechanism can be a source of confusion. It implies a global singleton, which might conflict with certain testing patterns or specific dependency injection setups.","severity":"gotcha","affected_versions":">=1.0.0"},{"fix":"Instruct API clients to generate robustly unique keys, preferably UUID v4 or v5, for each distinct idempotent operation attempt. The same key must be reused only for retries of the *exact same* original request.","message":"The security and reliability of idempotency heavily depend on clients generating truly unique and unpredictable `Idempotency-Key` headers. Reusing the same key for different operations or using weak/predictable keys can lead to security vulnerabilities or bypass the idempotency mechanism.","severity":"security","affected_versions":">=1.0.0"}],"env_vars":null,"last_verified":"2026-04-23T00:00:00.000Z","next_check":"2026-07-22T00:00:00.000Z","problems":[{"fix":"For CommonJS, use `const { idempotency } = require('express-idempotency');` or `const idempotency = require('express-idempotency').idempotency;`. For ESM, use `import { idempotency } from 'express-idempotency';`.","cause":"This typically occurs when attempting to use CommonJS `require()` without correctly destructuring the named export `idempotency`, or trying to call the `express-idempotency` module root directly as a function.","error":"TypeError: Cannot read properties of undefined (reading 'idempotency')"},{"fix":"Clients must ensure that when retrying a request, they use the *exact same* `Idempotency-Key` and *identical* request parameters (method, URL, query, body). If the intent or payload of the request changes, a new, unique `Idempotency-Key` must be generated and used.","cause":"A request was received with an `Idempotency-Key` that matches a previously processed request, but the current request's method, URL, query parameters, or body differs from the original. This indicates a potential misuse of the idempotency key.","error":"Idempotency key misuse detected (HTTP 409 Conflict)"},{"fix":"Ensure that `app.use(idempotency(...))` has been called to initialize the middleware before attempting to call `getSharedIdempotencyService()`. If using TypeScript, you might need a `declare function getSharedIdempotencyService(): ...;` statement in a type definition file or at the top of your file to resolve type checking issues if it's implicitly global.","cause":"The `getSharedIdempotencyService()` helper function, which provides access to the `IdempotencyService` instance, is not recognized in the current scope. This often happens if the `express-idempotency` middleware hasn't been properly initialized in the Express application, or if the function's availability is misunderstood.","error":"ReferenceError: getSharedIdempotencyService is not defined"}],"ecosystem":"npm","meta_description":null,"install_score":null,"install_tag":null,"quickstart_score":null,"quickstart_tag":null}