Kraken.js POST handlers: choosing the layer that owns retry deduplication
0 reputation · 16 Nov 2020, 13:46 UTC
0 reputation · 16 Nov 2020, 13:46 UTC
I have a Kraken.js application (Express-based, configuration-driven middleware) with POST routes that create records. Clients and an upstream proxy can legitimately retry requests, and Kraken itself does not document any built-in retry or idempotency handling, so a repeated POST can reach the same route handler twice.
The behavior I want: a retried request carrying the same idempotency key returns the original result instead of writing a second record. What is unclear is which layer should own this. Candidates are an Express middleware registered before Kraken's route handlers, the domain service behind the controller, or a database unique constraint on a natural key with the conflict mapped to a safe repeatable response.
My constraints: the app uses Kraken's config-based middleware setup, and I want the guard to apply consistently across several POST/PUT routes rather than being reimplemented per controller. I also do not want to rely on middleware ordering assumptions that may differ between Kraken/Express versions.
35275 reputation · 16 Nov 2020, 19:26 UTC
In a Kraken/Express stack the conventional place for cross‑cutting idempotency/retry deduplication is a piece of Express middleware that runs after the body‑parser and before Kraken‑generated route handlers.
idempotency-middleware.js, that:Idempotency-Key) or from the JSON body.next() and, after the downstream handler finishes, caches the outcome keyed by the idempotency key.middleware.json (or programmatic middleware array) after the body‑parser entry (e.g. bodyParser.json()) and before the router or controllers section that Kraken injects for routes.{
"middleware": {
"init": {
"bodyParserJson": { "module": "body-parser", "method": "json" }
},
"idempotency": {
"module": "./idempotency-middleware",
"method": "",
"args": [ { "store": "redis" } ]
},
"router": { "module": "./router", "method": "" }
}
}
This order guarantees the key is available from the parsed payload before any controller runs.
Add a unique constraint on a column that stores the idempotency key (or a composite key that includes the resource type) in your data store. In the service layer catch the constraint‑violation error and map it to the same successful response you would have returned for the first request. This protects against the rare case where two requests slip past the middleware simultaneously.
Apply the requirement on every mutating endpoint (POST, PUT, PATCH) where duplicate side‑effects are undesirable. GET/HEAD/OPTIONS can omit it, but enforcing it uniformly simplifies reasoning and avoids per‑route decisions.
If your deployment runs more than one Node process (cluster, multiple containers, or horizontal scaling), the idempotency store must be shared across instances. Let me know whether you need a recommendation for a distributed store (e.g., Redis) or if a single‑process in‑memory cache is sufficient.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.