Background jobs and idempotency
A shop owner on weak mobile data taps "Place order", the app times out, and it tries again. This lesson makes that retry safe: the slow work moves to a background job, and the order endpoint remembers each request so the same order sent twice creates one order.
By the end you can
- Explain why a timeout cannot tell the app whether the order was created
- Decide what the request must finish and what a background job can do later
- Hand work to a BullMQ queue safely with a transactional outbox, and write a job that is safe to run twice
- Build an Idempotency-Key check for
POST /v1/ordersthat replays, rejects a changed body and handles a duplicate in flight
The everyday picture
You phone a takeaway counter to order dinner. Before you say anything, the cashier gives you a ticket number, 47. You list your order, and the line goes dead. Did they get it? You cannot know. So you call back and say, "This is ticket 47, did my order go through?"
A good counter keeps a book of ticket numbers. The cashier looks up 47, sees your order written down, and reads it back to you. Nobody cooks it twice. If you had called back without the number, the cashier would have taken a second order, and two dinners would arrive at your door.
Notice the second thing the counter does well. The cashier does not cook while you wait on the phone. She writes the ticket, says "done, 30 minutes", and the kitchen cooks in the back. Your call is short, so it is less likely to drop. An API works the same way: a ticket number the client chooses (the idempotency key), and a kitchen that works after the call ends (the background job).
A timeout does not say what happened
When the Partner app sends POST /v1/orders and gets no answer within 10 seconds, one of three things happened. The request may never have reached the API. The API may have received it and failed before saving anything. Or the API saved the order, sent 201 Created, and the answer was lost on the way back over a weak mobile connection. From the phone, all three look exactly the same: a spinner, then an error.
The app must do something. If it gives up, the shop owner may believe no order exists and phone a sales rep, who places a second one by hand. If it retries blindly, case three becomes two orders, two deliveries and two invoices. GET, PUT and DELETE are safe to repeat by HTTP's rules, but POST is not (Designing an API people can use covered the method rules, and Failure is normal covered retries from the client's side).
The fix is to make the repeat recognisable. The client sends an idempotency key with the request. The server stores the key with the result. When the same key arrives again, the server returns the stored result instead of placing a new order. The operation becomes idempotent: doing it twice has the same effect as doing it once.
Quick check
The Partner app's POST /v1/orders timed out after 10 seconds. What does the app know?
A timeout only says that no answer arrived in time. The request may have been lost, or the order may be saved and only the 201 lost. Treating it as "not created" is the tempting mistake that leads to a second order, from a blind retry or a phone call to a rep.