MauApa Booking API
Booking and live queue API for barbershops, built around one guarantee.
0 double bookings 2026
Overview
Two customers message a barbershop at the same time asking for the 10:00 slot with the same barber. Whoever the owner replies to first gets it, and often both do, because the owner reads both messages at 10:05. Slot maths, the queue and the dashboard are ordinary CRUD by comparison. The two requirements that actually matter are that simultaneous bookings for one staff member must never both succeed, and that one shop must never see another shop's data.
Check-then-insert cannot deliver the first one. Between the check and the insert there is a window, and the row that must not exist yet cannot be locked, so SELECT FOR UPDATE has nothing to take a lock on. The rule therefore lives in the database as a GiST exclusion constraint over staff id and a half-open time range, made partial so a cancelled booking releases its slot. Running it surfaced a second problem: conflicting inserters wait on each other and Postgres kills the resulting cycle. Across forty rounds of three concurrent bookings, 60 requests died as deadlocks rather than honest conflicts.
Key features
The database holds the rule
An exclusion constraint rejects overlapping bookings regardless of interleaving. The application's only job is translating that rejection into a 409.
Advisory lock kills the deadlocks
A transaction-scoped lock per staff member turned 60 deadlocks into 0 across the same forty rounds, while bookings for different staff still run in parallel.
Unowned shops answer 404
A 403 would confirm the id exists and hand anyone probing ids a free oracle, so another tenant's shop is indistinguishable from one that was never created.
Queue broadcasts over Redis
Socket.IO rooms are keyed per shop and published through Redis pub/sub, so the live queue keeps working once the API runs more than one instance.
Screens