Creating an Idempotent API using Node.js

Subhasis Das
Developer Student Club, HIT
7 min readSep 29, 2020
Image Courtesy : https://zafulabs.com/2019/06/19/humans-can-code-too-idempotency/

Imagine this : you get back home from a long day at work, famished and hungry. So, you decide to order some food online and open your favorite food ordering app (lets call it ‘Buggy’ 😉). You select your food from your favorite restaurant on Buggy, place an order by completing the payment but then your app shows ‘Your order was not placed. Please retry’. Now given your ravenous appetite at the moment, you’d probably hit that retry button, pay again and maybe have your order placed successfully this time. You navigate to the order history page and boom! You find that two similar orders were placed and you’ve been charged FOR BOTH! Now how you deal with the Buggy customer service (and your bad luck🌚) is completely up to you but in this blog post I’ll illustrate how this problem could have been avoided in the first place.

Let’s analyze where things went wrong. In an ideal case, the following would have happened:

  1. You place an order on the Buggy client side app. The Buggy app would send an HTTP POST request to the Buggy server.
  2. The Buggy server would handle the incoming request (provided it passes all the server side validations) by creating a new order in the database with the details sent in the request body of the POST request.
  3. The server would respond with a status code 201 saying that a new order has been created(placed).
  4. The client would receive this response and display an appropriate feedback (message) to the user.

Clearly something must have gone wrong between steps 3 and 4. The communication between the client side app and the server might have been disrupted by a network failure (or a similar reason). This led to a false feedback to the end user and made them place another order. For a naïve user, it is natural to hit that retry button, but a good engineer’s job is to ensure that such a scenario never arises in the first place. Our system must have the capability to automatically retry failed requests safely — by not handling the same request twice. In other words, we must be able to identify duplicate requests to place the same order on the server and thus instead of creating a new order on the database, send a 304(not modified) response. This property is called idempotence.

Wikipedia defines idempotence as ‘ the property of certain operations in mathematics and computer science whereby they can be applied multiple times without changing the result beyond the initial application.’

According to the HTTP specification RFC 2616 section 9 the HTTP verbs GET , PUT and DELETE are said to be idempotent whereas the verb POST is not idempotent by default. However, we use POST requests to create a resource on the server (or in this case, place an order). So let us see how we can use some minor tweaks to make the POST method act in an idempotent manner.

The idea is simple :

  1. Identify all incoming requests for order creation uniquely using a custom header called x-idempotence-key . This header shall bear an ‘idempotence key’, which shall basically be a unique ID generated on the client side (in JS we have the uuid package).
  2. Every incoming request shall be cached along with it’s corresponding response on the server side using the custom header x-idempotence-key as the key. This cache could something as simple as a hash map or set (or an object in JS) or something as advanced as redis.
  3. We’d have a check (typically a middleware function) on the server side to determine if the incoming request has already been served. We do so by querying the cache using the idempotence key on the request. If it hits the cache, we send the cached response directly, else we pass it on to the handler for processing. This prevents the scenario where duplicate orders could be generated.

Let’s write some code illustrate the above idea using Node.js and Express. (I’ll assume the reader has some basic familiarity with JavaScript, Node.js and Express).

In the above code, we do some trivial setup for an express project. The orders array is intended to serve as a mock database of orders, where every newly created order shall get stored. The value of the variable walletBalance (initialized to 100) reduces every time a successful order is placed. Now, if we run the server ( node non_idempotent.js ), we can test the endpoints using Postman.

POST /place_order without idempotency

In the above endpoint we have placed an order of amount ‘50’ and that has led to the deduction of the amount from our walletBalance .

GET /wallet_balance returns 50, the balance remaining after the order got placed.

Now, to replicate the scenario of duplicate requests, we simply send the POST request again using postman using the same request body …

…and obtain the following response

Response with status code 201 on successful order creation

This also leads to further reduction of the wallet balance to 0.

Our goal is to prevent such a scenario — client may send a duplicate request, but it’s the server’s job to detect that as a duplicate and not process it. So now we introduce idempotency into the system in the following code :

In the above code , we have added a new JS object called dummyCache which supposed to act as a mock cache for the application. We expect all POST requests shall have a header called x-idempotence-key . The value of this idempotence key could be anything that is unique in nature. Typically we’d use a uuid in JS (something like 11bf5b37-e0b8–42e0–8dcf-dc8c4aefc000).

In the POST request handler, we query the cache using x-idempotence-key at first. If if hits the cache, that means the request has been previously processed. So we send a response with a 304 status instead of actually processing it. If it misses the cache, it means this is a new request, so we go on to process it as usual. Note that in line 38, we add every request-response pair to the cache before sending the response to the client.

Now let’s run the code ( node idempotent.js ) and test the endpoints again using Postman.

First, we place an order using the POST endpoint, but this time we pass an extra header :

…and the same body as before :

…and the response :

Response on successful order creation with 201 status

The wallet balance has now reduced by 50, as expected:

Response from GET /wallet_balance

Now to replicate the duplicate request scenario, we send the request to the POST endpoint again, with the same header and body. Notice the response this time :

Empty response with 304 status

We receive an empty response with a 304 (Not modified) status. This means that the second order for amount of 50 was not processed. We can further verify this by checking the wallet balance:

Wallet balance after duplicate request call

As we can see, the wallet balance is still 50, which simply means the second order was not processed by the server.

So that’s how Idempotency can be implemented on a POST endpoint in an express server. Just a simple caching mechanism! Feel free to have a look at the code here.

NOTE: This code is not production grade. It’s only meant for a simple demonstration of the above idea.

Client Side :

On the client side, we’d typically have a middleware or an interceptor that catches failed requests and retries them with the same idempotence key. If you’re using JS frameworks like React, React Native or Vue, I’d suggest using the axios npm package. You can configure “interceptors” in axios that would catch failed requests and retry them using the same idempotence key (somewhat like a middleware!). Refer to this amazing blog post on axios interceptors .You could also use npm packages like axios-retry.

Hope you found this useful. 😃

--

--

Subhasis Das
Developer Student Club, HIT

Cloud, DevOps & Platform Engineer. Also dabbled in the world of web.