npm package: @geekyvishal/idempotency-key-middleware
Description: Fintech-grade idempotency middleware for Express/Fastify with Redis/Postgres adapters.
Latest version: 0.1.0

# Fintech-Grade Idempotency-Key Middleware

[![npm version](https://badge.fury.io/js/%40geekyvishal%2Fidempotency-key-middleware.svg)](https://badge.fury.io/js/%40geekyvishal%2Fidempotency-key-middleware)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

A production-grade middleware for Express that enforces idempotent API requests using an `Idempotency-Key` header.

This middleware is essential for building robust, fault-tolerant systems, particularly in fintech where duplicate operations (like charging a customer twice) can have serious consequences. It ensures that if a client retries a request due to a network failure, the operation is executed only once.

---

## The Problem: Why Idempotency is Critical

In distributed systems, clients may retry requests when they don't receive a timely response. This can happen due to network timeouts, server-side issues, or simple client-side logic. Without an idempotency layer, these retries could trigger the same operation multiple times, leading to data corruption or incorrect financial transactions.

This middleware solves that problem by implementing the **Idempotency-Key** pattern, a standard practice used by major payment processors like Stripe and Razorpay.

---

## How It Works

The middleware intercepts incoming `POST` or `PATCH` requests and uses a combination of the `Idempotency-Key` header and a request "fingerprint" to manage the request lifecycle.

1.  **First Request**: When a request with a new `Idempotency-Key` arrives, the middleware creates a unique fingerprint of the request (method, path, and body). It then reserves the key in the storage layer (e.g., Redis or Postgres) and allows the request to proceed to your business logic.
2.  **Response Capture**: Once your handler sends a response, the middleware captures the status code, headers, and body, and stores them against the idempotency key.
3.  **Duplicate Request**: If another request arrives with the same key, the middleware immediately returns the cached response from storage without ever hitting your business logic again.
4.  **Conflict Handling**: If a request arrives with a key that has already been used but with a different request fingerprint (e.g., a different request body), it is rejected with a `409 Conflict` error to prevent accidental misuse of the key.

---

## Core Features

-   **Guaranteed Idempotency**: Ensures "exactly-once" execution for critical API endpoints.
-   **High-Performance Adapters**:
    -   **Redis**: For distributed, high-throughput environments where speed is critical.
    -   **PostgreSQL**: For persistent, auditable records of idempotent requests.
    -   **In-Memory**: For simple, zero-dependency development and testing.
-   **Intelligent Caching**: Automatically captures and returns the original response (status, headers, and body).
-   **Highly Configurable**: Customize the header name, key TTL, error handling, and response caching behavior.
-   **Production Ready**: Built with modern TypeScript, ES Modules, and includes a full suite of unit tests.

---

## Installation

```bash
# Install the core package
npm install @geekyvishal/idempotency-key-middleware

# Install your preferred storage adapter's driver
npm install redis  # For Redis
# or
npm install pg     # For Postgres
```

---
# Quick Start (Express)
Protecting an endpoint is as
 simple as initializing an adapter and adding the middleware to your route.

```ts

import express from 'express';
import { createIdempotencyMiddleware, RedisAdapter } from '@geekyvishal/idempotency-key-middleware';

const app = express();
app.use(express.json());

// 1. Initialize your chosen storage adapter.
const idempotencyStore = new RedisAdapter({ url: 'redis://localhost:6379' });
await idempotencyStore.init();

// 2. Create the middleware instance.
const idempotencyMiddleware = createIdempotencyMiddleware(idempotencyStore, {
  ttlSec: 24 * 3600, // Keep records for 24 hours
});

// 3. Apply the middleware to your critical endpoints.
app.post('/api/payments', idempotencyMiddleware, async (req, res) => {
  try {
    // This logic will only execute ONCE for a given Idempotency-Key.
    // const payment = await processPayment(req.body);
    res.status(201).json({ status: 'SUCCESS', transactionId: 'txn_123' });
  } catch (error) {
    res.status(500).json({ status: 'FAILED', message: 'Payment processing failed.' });
  }
});

const port = 3000;
app.listen(port, () => {
  console.log(`Server is running on http://localhost:${port}`);
});
```


---

## API Reference

### `createIdempotencyMiddleware(store, options?)`

| Option                 | Type                     | Default             | Description                                                                                                                              |
| ---------------------- | ------------------------ | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `headerName`           | `string`                 | `'Idempotency-Key'` | The HTTP header to read the key from.                                                 |
| `ttlSec`               | `number`                 | `3600`              | Time-to-live in seconds. The key and its response will be purged from storage after this duration. |
| `requireHeader`        | `boolean`                | `true`              | If `true`, rejects requests missing the idempotency header with a `400 Bad Request`.  |
| `onInProgress`         | `'reject'` \| `'hint'`   | `'reject'`          | Behavior for concurrent requests with the same key. `'reject'` sends `409 Conflict`, `'hint'` sends `202 Accepted`. |
| `storeOnStatusBelow`   | `number`                 | `500`               | Only cache responses with a status code below this value. Avoids caching server errors.  |

---

## Performance Benchmarks

The middleware is designed for high-performance applications. Benchmarks were conducted using `autocannon` on a MacBook Air against a local Redis instance.

The test scenario simulates a heavy load of retried requests to measure the performance of the caching layer.

-   **Command**: `npm run bench:auto`
-   **Connections**: 200 concurrent connections
-   **Duration**: 20 seconds

# Results:

![results](image.png)



### Summary

-   **Total Requests**: 870k requests in 20.02s
-   **Successful Responses (2xx)**: 869,216
-   **Failed Responses (non-2xx)**: 687
-   **Total Data Read**: 246 MB



| Metric                 | Average                | 99th Percentile      |
| ---------------------- | ---------------------- | -------------------- |
| **Requests/Second** | **43,497** | N/A                  |
| **Latency** | **4.31 ms** | **8 ms** |

These results demonstrate that the middleware adds negligible overhead and can serve cached responses at an extremely high throughput, making it suitable for demanding production workloads.

---

## Development and Contribution

This project is built with a modern toolchain and is ready for contributions.

1.  **Clone the repository:**
    ```bash
    git clone https://github.com/geeekyvishal/idempotency-key-middleware
    cd idempotency-key-middleware
    ```
2.  **Install dependencies:**
    ```bash
    npm install
    ```
3.  **Start services (Redis & Postgres):**
    The `docker-compose.yml` file makes this easy.
    ```bash
    docker-compose up -d
    ```
4.  **Run the tests:**
    Tests are written with Jest and use the in-memory adapter.
    ```bash
    npm test
    ```
5.  **Run the example app:**
    ```bash
    npm run dev
    ```

---

## License

This project is licensed under the **MIT License**.