> ## Documentation Index
> Fetch the complete documentation index at: https://docs.go-mizu.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Idempotency

> Idempotency key middleware for safe request retries and duplicate prevention.

## Overview

The `idempotency` 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

```go theme={null}
import "github.com/go-mizu/mizu/middlewares/idempotency"
```

## Quick Start

```go theme={null}
app := mizu.New()

// Enable idempotency
app.Use(idempotency.New())

app.Post("/payments", func(c *mizu.Ctx) error {
    // This will only execute once per idempotency key
    result := processPayment()
    return c.JSON(200, result)
})
```

Client sends:

```
POST /payments
Idempotency-Key: unique-key-123
```

## 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

```go theme={null}
app.Use(idempotency.New())
```

### Custom Header

```go theme={null}
app.Use(idempotency.WithOptions(idempotency.Options{
    KeyHeader: "X-Request-Id",
}))
```

### Specific Methods Only

```go theme={null}
app.Use(idempotency.WithOptions(idempotency.Options{
    Methods: []string{http.MethodPost}, // Only POST
}))
```

### User-Scoped Keys

```go theme={null}
app.Use(idempotency.WithOptions(idempotency.Options{
    KeyGenerator: func(key string, c *mizu.Ctx) string {
        userID := c.Request().Header.Get("X-User-ID")
        return userID + ":" + key
    },
}))
```

### Custom Store

```go theme={null}
store := idempotency.NewMemoryStore()
defer store.Close()

app.Use(idempotency.WithStore(store, idempotency.Options{}))
```

## How It Works

1. Client sends request with `Idempotency-Key` header
2. Middleware checks if key exists in store
3. If exists: returns cached response with `Idempotent-Replayed: true`
4. If not: executes handler, caches response, returns normally
5. Subsequent requests with same key get cached response

## API Reference

### Functions

```go theme={null}
// New creates idempotency middleware
func New() mizu.Middleware

// WithOptions creates middleware with configuration
func WithOptions(opts Options) mizu.Middleware

// WithStore creates middleware with custom store
func WithStore(store Store, opts Options) mizu.Middleware

// NewMemoryStore creates in-memory response store
func NewMemoryStore() *MemoryStore
```

### Store Interface

```go theme={null}
type Store interface {
    Get(key string) (*Response, error)
    Set(key string, resp *Response) error
    Delete(key string) error
}

type Response struct {
    StatusCode int
    Headers    http.Header
    Body       []byte
    ExpiresAt  time.Time
}
```

## 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

This ensures that the same idempotency key can be safely reused across different endpoints and methods.

### Response Capture

The middleware uses a custom `responseCapture` wrapper that:

1. Captures the response status code (defaults to 200 OK)
2. Clones all response headers
3. Buffers the response body in memory
4. Writes everything to both the buffer and the underlying ResponseWriter

### Memory Store Implementation

The built-in `MemoryStore`:

* Uses `sync.RWMutex` for thread-safe concurrent access
* Runs a background cleanup goroutine that removes expired entries every 10 minutes
* Stores responses with their expiration timestamps
* Returns `nil` for expired entries automatically

### Request Flow

1. **Method Check**: Verifies the HTTP method is in the configured Methods list
2. **Key Extraction**: Retrieves the idempotency key from the specified header
3. **Cache Lookup**: Checks if a response exists for the generated cache key
4. **Cache Hit**: If found and not expired, replays the cached response with `Idempotent-Replayed: true` header
5. **Cache Miss**: Wraps the ResponseWriter, executes the handler, captures the response, and stores it in the cache
6. **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                                                               |

## Client Example

```javascript theme={null}
// JavaScript client
const response = await fetch('/api/payments', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'Idempotency-Key': crypto.randomUUID()
    },
    body: JSON.stringify({ amount: 100 })
});
```

## Related Middlewares

* [requestid](/middlewares/requestid) - Request ID generation
* [retry](/middlewares/retry) - Automatic retries
* [ratelimit](/middlewares/ratelimit) - Rate limiting
