Overview
Theidempotency middleware ensures that repeated requests with the same idempotency key return the same response, preventing duplicate operations like double charges or duplicate records.
Use it when you need:
- Safe payment processing retries
- Duplicate request prevention
- Reliable webhook handling
Installation
Quick Start
Configuration
Options
| Option | Type | Default | Description |
|---|---|---|---|
KeyHeader | string | "Idempotency-Key" | Header name for key |
Methods | []string | ["POST", "PUT", "PATCH"] | Methods to track |
TTL | time.Duration | 24h | How long to store responses |
KeyGenerator | func(string, *mizu.Ctx) string | Identity | Custom key generation |
Examples
Basic Usage
Custom Header
Specific Methods Only
User-Scoped Keys
Custom Store
How It Works
- Client sends request with
Idempotency-Keyheader - Middleware checks if key exists in store
- If exists: returns cached response with
Idempotent-Replayed: true - If not: executes handler, caches response, returns normally
- Subsequent requests with same key get cached response
API Reference
Functions
Store Interface
Response Headers
| Header | Description |
|---|---|
Idempotent-Replayed: true | Response was replayed from cache |
Technical Details
Cache Key Generation
By default, the middleware generates cache keys using SHA-256 hashing of:- The idempotency key from the header
- The HTTP method (POST, PUT, etc.)
- The request URL path
Response Capture
The middleware uses a customresponseCapture wrapper that:
- Captures the response status code (defaults to 200 OK)
- Clones all response headers
- Buffers the response body in memory
- Writes everything to both the buffer and the underlying ResponseWriter
Memory Store Implementation
The built-inMemoryStore:
- Uses
sync.RWMutexfor thread-safe concurrent access - Runs a background cleanup goroutine that removes expired entries every 10 minutes
- Stores responses with their expiration timestamps
- Returns
nilfor expired entries automatically
Request Flow
- Method Check: Verifies the HTTP method is in the configured Methods list
- Key Extraction: Retrieves the idempotency key from the specified header
- Cache Lookup: Checks if a response exists for the generated cache key
- Cache Hit: If found and not expired, replays the cached response with
Idempotent-Replayed: trueheader - Cache Miss: Wraps the ResponseWriter, executes the handler, captures the response, and stores it in the cache
- Expiration: Responses are stored with a TTL and automatically cleaned up
Best Practices
- Always use idempotency keys for payment operations
- Generate unique keys client-side (UUIDs work well)
- Include user context in key generation for multi-tenant apps
- Set appropriate TTL based on your use case
- Use distributed store (Redis) for multi-instance deployments
Testing
Test Coverage
| Test Case | Description | Expected Behavior |
|---|---|---|
TestNew | Basic idempotency with default configuration | First request executes handler, second request with same key returns cached response with Idempotent-Replayed: true header |
TestWithOptions_NoKey | Requests without idempotency key | All requests execute handler normally (no caching) |
TestWithOptions_DifferentKeys | Multiple requests with different keys | Each unique key executes handler independently |
TestWithOptions_CustomHeader | Custom header name configuration | Middleware uses custom header (e.g., X-Request-Id) for idempotency key |
TestWithOptions_Methods | Method filtering (POST only) | POST requests are cached, PUT requests are not cached |
TestWithOptions_GET | GET requests with idempotency key | GET requests bypass middleware (not cached by default) |
TestWithOptions_ResponseHeaders | Custom response headers | Cached response includes all custom headers from original response |
TestWithStore | Custom store integration | Middleware works with custom Store implementation |
TestMemoryStore | Memory store operations | Set, Get, and Delete operations work correctly |
TestMemoryStore_Expiry | Expired entries | Store returns nil for expired entries |
TestWithOptions_CustomKeyGenerator | User-scoped keys | Same idempotency key with different user IDs creates separate cache entries |
TestWithOptions_ResponseBody | JSON response caching | Complete response body (including JSON) is cached and replayed |