https://github.com/idempot-dev/idempot-js
Idempotency middleware for Express, Fastify and Hono
https://github.com/idempot-dev/idempot-js
express express-middleware fastify hono idempotency idempotency-key idempotent javascript middleware
Last synced: about 1 month ago
JSON representation
Idempotency middleware for Express, Fastify and Hono
- Host: GitHub
- URL: https://github.com/idempot-dev/idempot-js
- Owner: idempot-dev
- License: other
- Created: 2026-03-15T19:13:02.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2026-04-27T20:24:36.000Z (about 2 months ago)
- Last Synced: 2026-04-27T22:10:42.852Z (about 2 months ago)
- Topics: express, express-middleware, fastify, hono, idempotency, idempotency-key, idempotent, javascript, middleware
- Language: JavaScript
- Homepage: https://js.idempot.dev
- Size: 1.72 MB
- Stars: 4
- Watchers: 0
- Forks: 1
- Open Issues: 7
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- Funding: .github/FUNDING.yml
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
- Agents: AGENTS.md
Awesome Lists containing this project
README
# idempot
Idempotency middleware for Hono, Express, and Fastify.
## Features
- Implements the IETF draft [draft-ietf-httpapi-idempotency-key-header-07](https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-idempotency-key-header-07) specification
- Request fingerprinting for conflict detection
- Built-in resilience: retries, timeouts, circuit breaker
- Modular packages reduce install time and dependencies
## TypeScript Support
This library uses JavaScript with JSDoc comments for type information. Each package ships `.d.ts` declaration files generated from the JSDoc-annotated source.
TypeScript picks them up automatically via the `types` field in each `package.json`.
```typescript
import { idempotency } from "@idempot/hono-middleware";
```
The declarations are generated at publish time to ensure types match the published version.
## Supported Runtimes, Frameworks, and Stores
| Category | Options |
| -------------- | ------------------------------------------------------------------- |
| **Runtimes** | Node.js, Bun, Deno (Lambda & Cloudflare Workers planned) |
| **Frameworks** | Express, Hono, Fastify, Bun Server |
| **Stores** | Redis, PostgreSQL, MySQL, SQLite (DynamoDB & Cloudflare KV planned) |
## Response Headers
Duplicate requests return cached responses with `x-idempotent-replayed: true`.
## Error Responses (RFC 9457)
Error responses follow [RFC 9457](https://datatracker.ietf.org/doc/html/rfc9457) (Problem Details for HTTP APIs) and include:
- `type` - URI identifying the problem type
- `title` - Short human-readable summary
- `detail` - Detailed explanation
- `status` - HTTP status code
- `instance` - Unique identifier for this error occurrence
- `retryable` - Whether retrying might succeed
- `idempotency_key` - The idempotency key from the request (when applicable)
### Content Negotiation
The middleware supports content negotiation via the `Accept` header:
**Default (application/problem+json):**
```bash
curl -X POST http://localhost:3000/orders \
-H "Content-Type: application/json" \
-d '{"item": "widget"}'
```
Response:
```json
{
"type": "https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-idempotency-key-header-07#section-2.1",
"title": "Idempotency-Key is missing",
"detail": "This operation is idempotent and it requires correct usage of Idempotency Key.",
"status": 400,
"instance": "urn:uuid:550e8400-e29b-41d4-a716-446655440000",
"retryable": false
}
```
**Markdown format (for AI agents):**
```bash
curl -X POST http://localhost:3000/orders \
-H "Accept: text/markdown" \
-H "Content-Type: application/json" \
-d '{"item": "widget"}'
```
Response:
```markdown
---
type: "https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-idempotency-key-header-07#section-2.1"
status: 400
instance: "urn:uuid:550e8400-e29b-41d4-a716-446655440000"
retryable: false
---
# Idempotency-Key is missing
## What Happened
This operation is idempotent and it requires correct usage of Idempotency Key.
## What You Should Do
**Correct the issue.** This error requires changes to your request. Do not retry with the same idempotency key until the issue is resolved.
```
The markdown format includes YAML frontmatter with all error fields and human-readable guidance for AI agents.
## Quick Start
```bash
npm install @idempot/hono-middleware @idempot/sqlite-store
```
```javascript
import { Hono } from "hono";
import { idempotency } from "@idempot/hono-middleware";
import { SqliteIdempotencyStore } from "@idempot/sqlite-store";
const app = new Hono();
const store = new SqliteIdempotencyStore({ path: ":memory:" });
app.post("/orders", idempotency({ store }), async (c) => {
return c.json({ id: "order-123" }, 201);
});
```
## Configuration
The middleware accepts an options object with the following properties:
| Option | Type | Default | Description |
| --------------- | ------------------ | --------------------- | ---------------------------------------------------- |
| `store` | `IdempotencyStore` | required | Storage backend (Redis, PostgreSQL, MySQL, SQLite) |
| `required` | `boolean` | `true` | Whether the `Idempotency-Key` header is required |
| `ttlMs` | `number` | `86400000` (24 hours) | Time-to-live for idempotency records in milliseconds |
| `minKeyLength` | `number` | `21` | Minimum length for idempotency keys |
| `maxKeyLength` | `number` | `255` | Maximum length for idempotency keys |
| `excludeFields` | `string[]` | `[]` | Body fields to exclude from request fingerprint |
| `resilience` | `object` | see below | Circuit breaker and retry configuration |
**Resilience options:**
| Option | Type | Default | Description |
| -------------------------- | -------- | ------- | ---------------------------------------- |
| `timeoutMs` | `number` | `500` | Timeout per store operation |
| `maxRetries` | `number` | `3` | Retry attempts for failed operations |
| `retryDelayMs` | `number` | `100` | Delay between retries |
| `errorThresholdPercentage` | `number` | `50` | Error rate to trigger circuit breaker |
| `resetTimeoutMs` | `number` | `30000` | Time before testing recovery |
| `volumeThreshold` | `number` | `10` | Minimum requests before circuit can open |
**Example with custom configuration:**
```javascript
app.post(
"/orders",
idempotency({
store,
ttlMs: 7 * 24 * 60 * 60 * 1000, // 7 days
excludeFields: ["timestamp", "$.metadata.requestId"],
resilience: {
timeoutMs: 1000,
maxRetries: 5
}
}),
handler
);
```
Monitor circuit breaker state:
```javascript
const middleware = idempotency({ store });
console.log(middleware.circuit.status); // 'closed', 'open', or 'half-open'
```
See the [full configuration guide](https://js.idempot.dev/guide/configuration) for detailed documentation.
## Examples
See the `examples/` directory for complete examples.
## Changelog
See [GitHub Releases](https://github.com/idempot-dev/idempot-js/releases) for the changelog.
## License
BSD-3