Idempotency Plugin
Request deduplication solution that ensures operations execute exactly once, even when clients retry requests due to network issues or timeouts.
Overview
The Idempotency Plugin provides idempotency key handling with request fingerprinting, response caching, and concurrent request management. Useful for payment processing, order creation, and any mutation that should not execute multiple times.
| Challenge | Without Idempotency | With Idempotency Plugin |
|---|---|---|
| Network Retry | Duplicate payment processed | Original response returned |
| Client Timeout | Unknown operation state | Safe to retry |
| Double Click | Multiple orders created | Single order |
Key Features
- Idempotency Key Handling — Header validation and processing
- Request Fingerprinting — Detect mismatched requests with same key
- Response Replay — Return cached response for duplicate requests
- Concurrent Request Handling — Second request waits for first to complete
- Configurable TTL — Control how long responses are cached
- Header Preservation — Original response headers included in replay
Installation
bash
npm install @nestjs-redisx/core @nestjs-redisx/idempotency ioredisbash
npm install @nestjs-redisx/core @nestjs-redisx/idempotency redisBasic Configuration
typescript
import { Module } from '@nestjs/common';
import { RedisModule } from '@nestjs-redisx/core';
import { IdempotencyPlugin } from '@nestjs-redisx/idempotency';
@Module({
imports: [
RedisModule.forRoot({
clients: {
host: 'localhost',
port: 6379,
},
plugins: [
new IdempotencyPlugin({
defaultTtl: 86400,
headerName: 'Idempotency-Key',
keyPrefix: 'idempotency:',
}),
],
}),
],
})
export class AppModule {}Usage with Decorator
typescript
import { Controller, Post, Body } from '@nestjs/common';
import { Idempotent } from '@nestjs-redisx/idempotency';
import { CreatePaymentDto, PaymentService } from './types';
@Controller('payments')
export class PaymentsController {
constructor(private readonly paymentService: PaymentService) {}
@Post()
@Idempotent()
async createPayment(@Body() dto: CreatePaymentDto) {
// Executes exactly once per Idempotency-Key
return this.paymentService.process(dto);
}
}Client Integration
bash
# Initial request
curl -X POST https://api.example.com/payments \
-H "Idempotency-Key: pay_550e8400-e29b-41d4" \
-H "Content-Type: application/json" \
-d '{"amount": 10000, "currency": "USD"}'
# Response: {"id": "pay_123", "status": "completed"}
# Retry with same key (safe after timeout or error)
curl -X POST https://api.example.com/payments \
-H "Idempotency-Key: pay_550e8400-e29b-41d4" \
-d '{"amount": 10000, "currency": "USD"}'
# Returns cached response: {"id": "pay_123", "status": "completed"}Request Flow
Request States
Documentation
| Topic | Description |
|---|---|
| Core Concepts | Understanding idempotency |
| Configuration | Configuration reference |
| @Idempotent Decorator | Route-level idempotency |
| Service API | Programmatic idempotency |
| Fingerprinting | Request validation |
| Concurrent Requests | Handling parallel requests |
| Header Caching | Response header preservation |
| Client Guide | Integration guidelines |
| Monitoring | Metrics and observability |
| Testing | Testing idempotent endpoints |
| Recipes | Implementation examples |
| Troubleshooting | Debugging common issues |