So imagine I’m reading your post. I click Like, the heart turns red, and the request times out. My frontend’s retry logic sends it again.
This time, the success reply arrives. The heart turns grey.
I know I liked your post. But your code apparently has other plans because its not FAULT TOLERANT.
Fault tolerance is an application’s ability to keep working correctly when part of the system fails. Retrying can help us recover from a temporary connection failure, but our API has to handle the second attempt safely.
For database writes, there’s a catch. Saving the change and delivering the HTTP response are separate steps. The connection can drop after the database commits, leaving the client unsure whether the write succeeded.
Let’s follow that click through MongoDB and PostgreSQL on Neon, then make the retry safe in both.
One click became two requests
Our endpoint is POST /posts/42/toggle-like. It reverses the current state. If my like exists, remove it. Otherwise, create it.
Here’s how one click manages to do both.
- The API saves my like.
- The connection drops before the success reply reaches my browser.
- The browser retries.
- The API finds the like and removes it. This reply arrives successfully.
The first write succeeded. Only its reply was lost.
The retry recovered from the connection failure, but it also reversed the user’s action. Making this endpoint fault tolerant means handling both parts correctly.
Our like can live in a MongoDB document or a PostgreSQL row on Neon. Either database will execute a fresh request to remove it. Our API told it to toggle, and it followed the instruction.
Could we wrap the toggle in a transaction?
Yes. A transaction groups database changes so they all commit together or all roll back. That’s atomicity. MongoDB supports transactions across documents on replica sets and sharded clusters. Neon supports PostgreSQL transactions. MongoDB transactions · PostgreSQL transactions
In our example, though, the first transaction already committed before the reply disappeared. The retry starts a new transaction and reverses the like.
Both transactions succeed. I’ve now unliked your post with excellent data integrity.
A row lock also ends when its transaction finishes, leaving the later retry free to make another change. PostgreSQL row locks
We need to change what repeating the request does.
Make the request say what I mean
Instead of asking the API to toggle, ask it to make sure my like exists.
The first request creates it. The retry finds it and leaves it there.
That’s idempotency; repeating the request has the same intended effect as sending it once. The responses can differ while the intended effect stays the same.
We can use PUT /posts/42/like to ensure the like exists. For an unlike, DELETE /posts/42/like ensures the like is absent.
The handler’s database operation must implement that promise. Changing the HTTP verb alone leaves the original toggle behaviour in place.
MongoDB can create a missing like with an upsert. PostgreSQL on Neon can use INSERT … ON CONFLICT DO NOTHING. Both need a unique rule for the user-and-post pair so simultaneous requests also produce one like.
Implementation examples
MongoDB
Create the unique index once. Then use the upsert in the request handler, taking userId from the signed-in session.
// One-time setup
await likes.createIndex(
{ postId: 1, userId: 1 },
{ unique: true }
);
// In the PUT handler
await likes.updateOne(
{ postId, userId },
{ $setOnInsert: { likedAt: new Date() } },
{ upsert: true }
);
An existing like keeps its original timestamp.
PostgreSQL on Neon
Give the likes table a primary key on (post_id, user_id), then run this parameterised query.
INSERT INTO likes (post_id, user_id)
VALUES ($1, $2)
ON CONFLICT (post_id, user_id) DO NOTHING;
The retry finds the existing pair and leaves it alone.
If you keep a separate like counter, increase it only when a new like was inserted, in the same transaction.
What if I need to keep the toggle endpoint?
The PUT approach above repeats the same desired state. If we keep POST /posts/42/toggle-like, we need a way to recognise that a particular action already happened.
Give each deliberate click an idempotency key. This key identifies one action across all its retries. The client reuses it when retrying and generates a new key for a new deliberate click.
In one transaction, claim the key under a unique constraint scoped to the user and endpoint, apply the toggle, and save its result. When the same key comes back, return the saved result.
Now test the exact failure. Let the write succeed, drop its HTTP reply, and retry. There should still be one like when you refresh.
Whether the like lives in MongoDB or Neon, both attempts now leave us with the result we wanted. One click. One like.
So if I liked your post. The heart should stay red 😂.
I hope this helps you and can guide in making your code
Thanks for reading 👍


Top comments (0)