import type { Redis } from "ioredis";
export interface StoredResponse {
status: "pending" | "completed";
fingerprint: string;
httpStatus?: number;
headers?: Record<string, string>;
body?: unknown;
}
export class IdempotencyStore {
private readonly pendingTtlMs = 30_000;
private readonly completedTtlMs = 24 * 60 * 60 * 1000;
constructor(private readonly redis: Redis) {}
private redisKey(key: string): string {
return `idem:${key}`;
}
async begin(key: string, fingerprint: string): Promise<{ claimed: boolean; existing?: StoredResponse }> {
const record: StoredResponse = { status: "pending", fingerprint };
const ok = await this.redis.set(this.redisKey(key), JSON.stringify(record), "PX", this.pendingTtlMs, "NX");
if (ok === "OK") return { claimed: true };
const raw = await this.redis.get(this.redisKey(key));
return { claimed: false, existing: raw ? (JSON.parse(raw) as StoredResponse) : undefined };
}
async complete(key: string, response: Omit<StoredResponse, "status">): Promise<void> {
const record: StoredResponse = { status: "completed", ...response };
await this.redis.set(this.redisKey(key), JSON.stringify(record), "PX", this.completedTtlMs);
}
async release(key: string): Promise<void> {
await this.redis.del(this.redisKey(key));
}
}
import { createHash } from "crypto";
import type { Request, Response, NextFunction, RequestHandler } from "express";
import { IdempotencyStore } from "./IdempotencyStore";
const UNSAFE = new Set(["POST", "PATCH", "PUT", "DELETE"]);
function fingerprint(req: Request): string {
return createHash("sha256")
.update(req.method + req.originalUrl + JSON.stringify(req.body ?? {}))
.digest("hex");
}
export function idempotency(store: IdempotencyStore, opts: { required?: boolean } = {}): RequestHandler {
return async (req: Request, res: Response, next: NextFunction) => {
if (!UNSAFE.has(req.method)) return next();
const key = req.header("Idempotency-Key");
if (!key) {
if (opts.required) return res.status(400).json({ error: "Idempotency-Key header is required" });
return next();
}
const fp = fingerprint(req);
const { claimed, existing } = await store.begin(key, fp);
if (!claimed && existing) {
if (existing.fingerprint !== fp) {
return res.status(422).json({ error: "Idempotency-Key reused with a different request body" });
}
if (existing.status === "pending") {
return res.status(409).json({ error: "A request with this Idempotency-Key is still in progress" });
}
res.set(existing.headers ?? {});
return res.status(existing.httpStatus ?? 200).json(existing.body);
}
const originalJson = res.json.bind(res);
res.json = (body: unknown) => {
void store.complete(key, {
fingerprint: fp,
httpStatus: res.statusCode,
headers: { "Content-Type": "application/json" },
body,
});
return originalJson(body);
};
res.on("close", () => {
if (!res.writableEnded) void store.release(key);
});
next();
};
}
import { Router } from "express";
import Redis from "ioredis";
import { IdempotencyStore } from "./IdempotencyStore";
import { idempotency } from "./idempotency";
import { chargeCard, recordPayment } from "./billing";
const redis = new Redis(process.env.REDIS_URL!);
const store = new IdempotencyStore(redis);
export const payments = Router();
payments.post("/payments", idempotency(store, { required: true }), async (req, res, next) => {
try {
const { amount, currency, source } = req.body as {
amount: number;
currency: string;
source: string;
};
if (!amount || amount <= 0) {
return res.status(400).json({ error: "amount must be positive" });
}
// Runs exactly once per Idempotency-Key; retries replay the stored response.
const charge = await chargeCard({ amount, currency, source });
const payment = await recordPayment(charge);
return res.status(201).json({
id: payment.id,
status: payment.status,
amount,
currency,
chargeId: charge.id,
});
} catch (err) {
next(err);
}
});
Idempotency keys let a client safely retry a POST without risking a second charge or duplicate record. The idea: the client generates a stable key (usually a UUID) and sends it in an Idempotency-Key header. The server records the first response under that key and replays it for any later request carrying the same key, so retries after a timeout or dropped connection become no-ops that return the original result.
The IdempotencyStore tab wraps Redis and encodes the state machine each key moves through. begin uses SET key value NX PX to atomically claim a key in the pending state; if the SET fails, another request already owns it and the caller learns the current record instead. This atomic claim is the crux — it collapses the check-and-set race that would otherwise let two concurrent retries both proceed. complete overwrites the record with the captured status, headers, and body and gives it a longer TTL so genuine retries within the retention window replay successfully, while release clears a pending slot when the handler throws so the client can try again cleanly.
The idempotency middleware tab drives that store. It only engages for unsafe methods, and returns 400 when the header is missing on a route that requires it. It also fingerprints the request body with sha256 and stores it alongside the response; if the same key arrives with a different payload, it responds 422 rather than silently returning a mismatched result — a subtle but important guard against key reuse bugs. When a record is already completed, it short-circuits and replays the stored response. To capture the response transparently it monkey-patches res.json, buffering the payload so it can persist the outcome exactly once the handler succeeds.
The payments route tab shows the realistic payoff: a charge endpoint wrapped by idempotency({ required: true }), where the actual side effect runs only on the first request. The trade-offs worth noting are TTL choice (too short and legitimate retries miss the cache), the pending window returning 409 under concurrent duplicates, and the fact that non-deterministic handlers should persist their own result rather than trust replay. This pattern is standard in payment and webhook APIs where exactly-once semantics over an at-least-once network matter.
Related snips
export interface RetryOptions {
retries: number;
baseMs: number;
maxMs: number;
signal?: AbortSignal;
onRetry?: (attempt: number, delay: number, err: unknown) => void;
Exponential backoff with jitter for retries
package dbutil
import (
"context"
"github.com/jackc/pgconn"
Retry Postgres serialization failures with bounded attempts
use crossbeam::channel::unbounded;
use std::thread;
fn main() {
let (tx, rx) = unbounded();
Crossbeam for advanced concurrent data structures
// Creating a Promise
const myPromise = new Promise((resolve, reject) => {
const success = true;
setTimeout(() => {
if (success) {
Promises and async/await patterns for asynchronous JavaScript
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
Channels (mpsc) for message passing between threads
export type Settled<R> =
| { status: 'fulfilled'; value: R }
| { status: 'rejected'; reason: unknown };
export interface ConcurrencyOptions {
limit: number;
Simple concurrency limiter for batch operations
Share this code
Here's the card — post it anywhere.