The double-booking bug I shipped in my Next.js booking app (and how Postgres fixed it)
I build a multi-tenant booking template with Next.js, Drizzle and Postgres. During an audit I found that my "double booking protection" didn't protect anything. Here's the bug, and the fix. The naive version This is roughly what I had: const conflict = await db . select (). from ( bookings ). where ( and ( eq ( bookings . resourceId , resourceId ), gte ( bookings . startTime , start ), lte (…
During an audit of my Next.js booking app, I discovered that the double booking protection mechanism was flawed. The bug allowed two requests for the same time slot to proceed, resulting in concurrent INSERT operations. To address this, I implemented a fix using Postgres advisory locks within a transaction-scoped scope. By locking on the resource ID, the system serializes bookings for each resource, preventing conflicts while allowing parallel bookings for different resources.
The use of advisory locks was chosen over a unique constraint or exclusion constraint because it offers simplicity and readability without the need for additional extensions. For serverless deployments like Supabase, configuring the database with prepare: false enables the transaction-scoped lock to operate effectively. Additionally, I resolved a timezone bug by converting the business's wall-clock time to UTC using the tenant's IANA timezone, ensuring accurate slot availability across different time zones.
This solution, part of the SlotKit template, was implemented to enhance the reliability and accuracy of the booking system.
Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.