DEV Community

Caique Santos
Caique Santos

Posted on Edited on

Preventing duplicate requests with Idempotency

Introduction

Idempotency is a way to ensure that an operation is not executed multiple times with unintended side effects.

Consider this scenario: a client submits an order in your application, but due to a network issue, a double-click on the submit button, or even a malicious attempt to exploit your system, the same request is sent multiple times to your API. How should you handle this?

This is where idempotency comes into play. Below I'll show some strategies used to handle it.

Handling Idempotency in Frontend

There are a few strategies you can apply on the frontend to prevent duplicate requests from being sent, but the frontend alone is not enough to guarantee idempotency. Here are those strategies:

  • Disable the button immediately after the request is sent to the backend
  • Redirect the user to a success page after the request is completed
  • Generate an idempotency key and send it to the backend for proper handling

Handling Idempotency in Backend

To properly handle this issue on the backend, a clean and effective approach is to use middleware to intercept incoming requests and validate the idempotency key provided by the client. Below is an example in C#:

public class IdempotencyMiddleware {
    private readonly RequestDelegate _next;

    public IdempotencyMiddleware(RequestDelegate next) {
        _next = next;
    }

    public async Task Invoke(HttpContext context) {
        var key = context.Request.Headers["Idempotency-Key"].FirstOrDefault();

        // The client didn't send a key, so no idempotency: let the request through
        if (string.IsNullOrEmpty(key)) {
            await _next(context);
            return;
        }

        // Checking and saving happen together, in one step. If we checked first
        // and saved after, two requests arriving at the same moment would both
        // find an empty slot — and the customer gets charged twice.
        var existing = await store.TryReserveAsync(key, TimeSpan.FromHours(24));

        // We've seen this key before. That can mean one of two things.
        if (existing is not null) {

            // First: a request with this key is still running. There's no
            // response to hand back yet, so we ask the client to retry.
            if (existing.Status == IdempotencyStatus.InProgress) {
                context.Response.StatusCode = StatusCodes.Status409Conflict;
                return;
            }

            // Second: it already finished. We return the same response as
            // before, without running the handler again.
            context.Response.StatusCode = existing.StatusCode;
            await context.Response.WriteAsync(existing.Body);
            return;
        }

        // Nothing existed, so we claimed the key. This is the first request —
        // let it through and do the actual work.
        await _next(context);
   }
}
Enter fullscreen mode Exit fullscreen mode

However, there are scenarios where an idempotency key provided by the frontend cannot be fully trusted. In such cases, the backend should generate or derive its own idempotency key to ensure consistency and reliability.

Handling Idempotency in Database

You can also enforce idempotency at the database level. Imagine a scenario where you have a form for creating a user. If your database is properly designed with the correct unique constraints, the same user cannot be stored more than once. For example:

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email TEXT UNIQUE,
    name TEXT,
    ...
);
Enter fullscreen mode Exit fullscreen mode

In this example, the email field is defined as unique, so even if the system attempts to insert the same user multiple times, the database will prevent duplicates.

Conclusion

In summary, idempotency is essential to prevent duplicate operations and ensure system reliability. Whether handled on the frontend, backend, or database level, combining these approaches leads to a more robust and resilient application.

Top comments (0)