JAVASCRIPT

Implementing Idempotent Client-Side API Requests

Learn to make API requests idempotent from the client-side using a unique `Idempotency-Key` header, preventing unintended duplicate actions on retries.

/**
 * Generates a UUID (Universally Unique Identifier).
 * This is a simple implementation, for production consider a dedicated library.
 * @returns {string} A UUID string.
 */
function generateUUID() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
        const r = Math.random() * 16 | 0;
        const v = c === 'x' ? r : (r & 0x3 | 0x8);
        return v.toString(16);
    });
}

/**
 * Makes an API request with an Idempotency-Key.
 * @param {string} url - The API endpoint URL.
 * @param {object} options - Fetch API options (method, headers, body).
 * @param {string} [idempotencyKey] - An optional, explicit idempotency key. If not provided, one is generated.
 * @returns {Promise<Response>} The fetch API response.
 */
async function makeIdempotentRequest(url, options = {}, idempotencyKey = null) {
    const key = idempotencyKey || generateUUID();
    console.log(`Making request to ${url} with Idempotency-Key: ${key}`);

    // Ensure headers exist and add the Idempotency-Key
    const headers = {
        'Content-Type': 'application/json',
        ...options.headers,
        'Idempotency-Key': key,
    };

    try {
        const response = await fetch(url, {
            ...options,
            headers: headers,
        });

        if (!response.ok) {
            const errorBody = await response.text();
            throw new Error(`API Error: ${response.status} ${response.statusText} - ${errorBody}`);
        }

        console.log(`Request successful for key: ${key}`);
        return response;
    } catch (error) {
        console.error(`Request failed for key ${key}:`, error);
        throw error;
    }
}

// Example Usage:
(async () => {
    // Assume a backend endpoint /api/create-order that supports idempotency
    // For testing, this would typically involve a server that stores the key
    // and returns the same result for subsequent requests with the same key.
    const orderData = {
        items: [{ productId: 'prod123', quantity: 1 }],
        total: 99.99
    };

    console.log('
--- First attempt to create an order ---');
    try {
        const response = await makeIdempotentRequest(
            'https://jsonplaceholder.typicode.com/posts', // Placeholder for a real API
            {
                method: 'POST',
                body: JSON.stringify(orderData),
            }
        );
        const data = await response.json();
        console.log('Order creation response (first attempt):', data);
    } catch (error) {
        console.error('Failed on first attempt:', error.message);
    }

    console.log('
--- Second attempt with the SAME Idempotency-Key (if the first failed or for retry logic) ---');
    // In a real application, you'd store the generated key for retries.
    // For this example, we generate a new one, simulating a fresh request with a new key,
    // unless you explicitly pass the same key for retry scenario.
    const explicitKey = generateUUID(); // Let's simulate a retry with a known key
    try {
        const response = await makeIdempotentRequest(
            'https://jsonplaceholder.typicode.com/posts', // Placeholder for a real API
            {
                method: 'POST',
                body: JSON.stringify(orderData),
            },
            explicitKey // Use an explicit key for idempotency
        );
        const data = await response.json();
        console.log('Order creation response (second attempt - same key):', data);
    } catch (error) {
        console.error('Failed on second attempt (same key):', error.message);
    }

    console.log('
--- Third attempt with a DIFFERENT Idempotency-Key (should be treated as new) ---');
    try {
        const response = await makeIdempotentRequest(
            'https://jsonplaceholder.typicode.com/posts', // Placeholder for a real API
            {
                method: 'POST',
                body: JSON.stringify(orderData),
            }
        );
        const data = await response.json();
        console.log('Order creation response (third attempt - new key):', data);
    } catch (error) {
        console.error('Failed on third attempt (new key):', error.message);
    }
})();
How it works: This JavaScript snippet demonstrates how to implement client-side idempotent API requests. Idempotency ensures that performing the same operation multiple times with the same inputs produces the same result and side effects as performing it once. This is crucial for operations like creating orders or processing payments, preventing duplicate charges or entries if a request needs to be retried due to network issues. The `makeIdempotentRequest` function generates a unique `Idempotency-Key` (a UUID) and adds it to the request headers. The backend API is then expected to store this key and, for subsequent requests with the same key, return the original result without re-processing the operation.

Need help integrating this into your project?

Our team of expert developers can help you build your custom application from scratch.

Hire DigitalCodeLabs