Course contentsShow
← Node.js
Lesson 1511 of 1,98013. Error Handling and Resilient SystemsFree lesson

Refactoring Toward Idempotent Handlers

You will convert non-idempotent endpoints to allow safe retries after an ambiguous network failure.

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.