In this lesson, you will learn how to design API endpoints that can be safely retried without side effects, even when a network failure leaves the client unsure if the server successfully processed the original request.
The Problem: The "Ambiguous Timeout"
Imagine a client sends a POST /orders request to your Node.js service. The server processes the request and updates the database, but just as the server attempts to send the HTTP 201 response, a network interruption occurs. The client receives a timeout and assumes the request failed, so it retries. If your handler isn't idempotent, you might accidentally create a duplicate order.
Designing Idempotency
To solve this, we introduce the concept of an Idempotency Key. This is a unique client-generated identifier (often a UUID) sent in a custom header like Idempotency-Key.
Instead of blindly executing the logic, your handler must check if it has already processed a request with that specific key.
async function createOrderHandler(req, res) {
const idempotencyKey = req.headers['idempotency-key'];
// 1. Check if we've seen this key before
const existingRecord = await db.idempotency.findOne({ key: idempotencyKey });
if (existingRecord) {
return res.status(200).json(existingRecord.response);
}
// 2. Perform the business logic
const order = await db.orders.create(req.body);
const responseData = { id: order.id, status: 'created' };
// 3. Store the result before returning
await db.idempotency.insert({
key: idempotencyKey,
response: responseData
});
res.status(201).json(responseData);
}
Why this is Resilient
By storing the result of the initial execution, you decouple the "execution" from the "confirmation." If the client retries due to a network glitch, your server identifies the previously stored result using the key and returns the original response immediately. This prevents redundant database writes or duplicate notifications, turning a non-deterministic process into a predictable, repeatable operation.
This approach ensures that your service maintains data integrity during process reboots or intermittent connectivity, as the state of the operation is persisted alongside the request identifier.
Key Takeaway: By requiring an idempotency key and caching the resulting response, you transform risky, state-altering handlers into idempotent operations that safely survive network failures and client retries.