Hire Lovable Xperts
Production & Scale

Building a Booking App on Lovable

Booking apps look simple: a calendar, a slot picker, a payment. The hard part is concurrency. Two users booking the same slot at the same instant both see it as free unless a database-level lock or hold pattern stops them. Lovable generates the booking UI reliably, but it does not add the atomic reservation, timezone correctness, or payment-on-booking guarantees a real booking app needs.

By Founder Name · Last verified: 2026-06-25

What does a Lovable booking app scaffold actually include?

A Lovable booking scaffold gives you a calendar or date-picker UI, a time-slot selection screen, a Supabase bookings table with slot_start, slot_end, user_id, and status columns, and a Stripe Checkout session. It works for demos with a handful of users on non-overlapping slots. It breaks the moment two users target the same slot or a cancellation must trigger a refund and re-open the slot.

Lovable usually adds a thin admin list of upcoming bookings, but rarely a provider interface for blocking time, setting recurring availability, or adding buffer between appointments. Those are day-one features for any real provider, and they require a structured availability model rather than the flat bookings table the scaffold produces.

Treat the generated output as a working prototype of the happy path, not a backend. The UI can often ship after light review; the data model and the write path almost always need a deliberate engineering pass before you accept money for slots.

Related: Lovable production readiness checklist · Lovable's 5 production gaps

Why do double bookings happen, and how do you prevent them?

Double bookings happen because Lovable checks availability in application code, then inserts in a second step. Between the read and the write there is a race window: two concurrent requests both read the slot as available and both insert. The fix is to make the check and the write a single atomic operation at the database level using a row lock or a Postgres exclusion constraint.

The cleanest pattern is a SECURITY DEFINER Postgres function that locks the slot row with SELECT ... FOR UPDATE, verifies it is still available, flips it to held, and inserts the booking, all inside one transaction. Call that function from a Supabase edge function so you own the transaction boundary instead of leaving it to the client.

  1. Add a slots table: (provider_id, slot_start TIMESTAMPTZ, slot_end TIMESTAMPTZ, status TEXT) with a UNIQUE(provider_id, slot_start) constraint so duplicate slots cannot even exist.
  2. Write a book_slot(p_slot_id UUID, p_user_id UUID) function that does SELECT ... FOR UPDATE on the slot row, checks status = 'available', sets status = 'held', inserts the booking, and returns the booking id, all in one transaction.
  3. Invoke book_slot only from a Supabase edge function, never directly from the client, so the transaction cannot be reordered or skipped.
  4. Add a held_until TIMESTAMPTZ column plus a pg_cron job that resets held slots back to available when held_until < now(), releasing abandoned holds from users who never finished checkout.
  5. In the Stripe webhook, promote held to booked only on payment_intent.succeeded, and return it to available on payment_intent.payment_failed or an expired hold.
A client-side availability check followed by a separate insert is the single most common double-booking bug. Under any real concurrency it lets two users book one slot. The check and the write must be atomic at the database level.

Related: When Lovable apps crash under load

How do you model availability so providers do not create every slot by hand?

A robust availability model has three layers: recurring availability (Monday 9am to 5pm), specific-date overrides (closed on a holiday), and booked or held slots removed from the pool. Lovable scaffolds only the third layer. Without the first two, a provider must hand-create every slot, which collapses past a week of bookings and makes recurring schedules impossible.

A minimal model: provider_schedules(provider_id, day_of_week, open_time TIME, close_time TIME, slot_duration_minutes INT), provider_overrides(provider_id, override_date DATE, is_closed BOOLEAN, custom_open_time TIME, custom_close_time TIME), and a generate_slots Postgres function that materializes available slots for a date range by applying the recurring schedule, subtracting overrides, and subtracting existing bookings and holds.

Generating slots on demand from rules, rather than storing millions of pre-created rows, keeps the table small and lets you change a schedule without rewriting history. Scope every table with row-level security so one provider can never read or mutate another provider's availability.

Related: Lovable Supabase RLS permissions · Lovable RLS and auth best practices

What timezone mistakes break Lovable booking apps?

The classic failure is storing local time and displaying it as-is, so a 2pm slot shows as 2pm to everyone regardless of where they are. Store every instant in UTC using TIMESTAMPTZ, and render in the viewer's zone with Intl.DateTimeFormat or date-fns-tz. The provider's working hours, not the booker's, define which slots exist; the booker only needs them translated for display.

Daylight saving transitions are where naive code breaks: a fixed-offset assumption double-books or skips an hour twice a year. Anchor recurring schedules to a named IANA timezone (America/New_York), not to a numeric offset, so the database resolves the correct UTC instant on each side of a DST change.

Send the booker an ICS attachment with an explicit timezone block. Calendar clients then place the event correctly no matter where the recipient travels, and you avoid support tickets from people who show up an hour off.

Timezone handling: wrong vs right in a booking app
ConcernCommon Lovable defaultProduction-correct approach
Storage typeTIMESTAMP without zone or a text stringTIMESTAMPTZ, all instants normalized to UTC
Recurring hoursNumeric offset baked into the scheduleNamed IANA zone (e.g. America/New_York)
DisplayServer time shown to every userRender per-viewer via Intl.DateTimeFormat / date-fns-tz
DST boundariesFixed offset, silently wrong twice a yearZone-aware resolution so the UTC instant stays correct
Calendar invitePlain time in the email bodyICS attachment with an explicit VTIMEZONE block

Should you charge at booking time, and what payment pitfalls scale badly?

Decide deliberately between authorize-now and charge-now. A common mistake is treating the slot as booked the instant Checkout opens, before payment confirms. Reserve as held, and promote to booked only on the payment_intent.succeeded webhook. If you charge a deposit, store deposit and balance separately so refund math at cancellation is unambiguous and auditable.

The payment-on-booking pitfall that scales badly is treating the Checkout redirect as success. Browsers close, cards decline, and webhooks arrive seconds later. If your code marks the slot booked on redirect, you sell slots you were never paid for and you fight phantom bookings. Make the webhook, not the client, the source of truth.

If the webhook is misconfigured, held slots never advance to booked and never release, so genuine bookings vanish and the calendar appears full. A flaky or unactivated Stripe integration is therefore a booking-correctness bug, not just a billing bug.

Make idempotency mandatory: Stripe can deliver the same webhook more than once. Key your slot promotion and refund handlers on payment_intent.id so a redelivered event never double-charges, double-refunds, or double-books.

Related: Fix: Lovable Stripe payment not activating · Fix: Lovable Stripe webhook not firing

How do you handle cancellations and refunds correctly?

A production cancellation flow needs a policy (full refund up to 24 hours out, 50% within 24 hours, none within 2 hours), a Stripe refund for the correct amount, a slot status reset to available, and a notification to both parties. Lovable may render a cancel button, but the refund math and slot re-release must be added and tested by hand.

  1. Define a cancellation_policies table (or a config constant) holding refund percentage tiers and their time thresholds.
  2. In the cancellation edge function, compute hours_until_slot = slot_start - now() and look up the applicable refund percentage.
  3. Call stripe.refunds.create({ payment_intent: booking.payment_intent_id, amount: Math.round(original_amount * refund_pct) }) keyed idempotently on the booking id.
  4. Set bookings.status = 'cancelled' and slots.status = 'available' in one transaction so the slot is immediately rebookable.
  5. Email the booker a confirmation and notify the provider via your transactional email service (Resend or SendGrid).

Where does Lovable help vs where does the booking backend need hardening?

Lovable is strong on the booking UI: the calendar widget, slot picker, confirmation screen, and confirmation email. It is weak on the concurrency layer, the availability model, timezone correctness, calendar sync, and cancellation enforcement. The UI often ships after light review; the write path and money path almost always need a structured engineering pass before launch.

Lovable booking app: handled well vs needs hardening
AreaLovable handlesNeeds hardening before launch
Booking UICalendar picker, slot selection, confirmation pagePer-viewer timezone display, buffer time, mobile edge cases
AvailabilityBasic slot availability checkRecurring schedule, date overrides, buffer between appointments
ConcurrencySingle-user happy pathAtomic reservation (SELECT FOR UPDATE or exclusion constraint)
PaymentsStripe Checkout for the bookingHold-then-confirm on webhook, deposits, refund tiers, idempotency
CancellationsCancel button (UI only)Policy enforcement, Stripe refund, slot re-release, notifications
Calendar syncNot scaffoldedGoogle Calendar API or CalDAV plus ICS invites with timezone blocks

Related: Building SaaS on Lovable · Building a marketplace on Lovable

When is a Lovable booking app ready for real bookings?

It is ready when slot reservation is atomic at the database level; the payment-to-confirmation flow is tested in Stripe test mode with both a failed and a succeeded payment; webhook handlers are idempotent; times are stored in UTC and rendered per viewer; a cancellation-and-refund path exists; and the availability model covers at least a week of recurring schedules. A productionization pass typically takes one to three days.

Run a deliberate readiness review against each row of the hardening table above, and rehearse the failure cases: a declined card mid-checkout, an abandoned hold, a duplicate webhook, a cancellation inside the no-refund window, and a booker in a different timezone than the provider. Each one should resolve to a single correct state without a manual database edit.

Related: Productionize your Lovable app · Production and scale hub

Frequently asked questions

Can Lovable build a complete booking app with payments?
Lovable scaffolds the core flow (calendar, slot selection, Stripe Checkout) quickly and reliably. The missing production pieces are concurrency-safe reservation, a recurring availability model, timezone correctness, and cancellation-with-refund logic. These require database-level changes and edge-function code that a senior engineer can add in one to two days.
How do I prevent two people from booking the same slot?
Make the availability check and the write atomic. Use a Postgres function that does SELECT ... FOR UPDATE on the slot row, confirms it is available, marks it held, and inserts the booking in one transaction, called from a Supabase edge function. A UNIQUE(provider_id, slot_start) constraint and an exclusion constraint give you a hard backstop at the database level.
What timezone handling does a booking app need?
Store every instant in UTC using TIMESTAMPTZ. Anchor recurring schedules to a named IANA timezone, not a numeric offset, so daylight saving resolves correctly. Display times per viewer with Intl.DateTimeFormat or date-fns-tz, and attach an ICS file with an explicit timezone block so calendar invites land at the right hour everywhere.
Should I charge at booking time or only after payment confirms?
Reserve the slot as held when Checkout opens, but mark it booked only when the payment_intent.succeeded webhook arrives, never on the browser redirect. Treating the redirect as success sells slots you were not paid for. If you take a deposit, store deposit and balance separately so refund math at cancellation stays unambiguous.
Why do my held slots get stuck and never free up?
Usually the Stripe webhook is not firing or not activated, so holds never advance to booked and never release. The calendar then looks full while real bookings disappear. Fix the webhook delivery first, add a pg_cron job to release holds past held_until, and make handlers idempotent so redelivered events do not corrupt slot state.
How do I add Google Calendar sync to a Lovable booking app?
Use the Google Calendar API from a Supabase edge function. On confirmation, call calendar.events.insert() with the booking details and the provider's stored calendar id. Also email the booker an event.ics attachment so it auto-adds to their calendar without OAuth. Include an explicit VTIMEZONE block so the invite lands at the correct hour.
How do I handle buffer time between appointments?
Add a buffer_after_minutes column to the provider schedule or to each booking. When generating available slots, exclude any window within slot_duration + buffer_after_minutes of an existing booking. This is a query-level change in generate_slots, not a UI change, so it applies consistently to every booking path at once.
Can I build a multi-provider platform like Calendly on Lovable?
Yes, but it adds a provider data model with per-provider schedules, provider-scoped RLS, and a Connect-style payment split if you take a platform fee. That is meaningfully more complex than a single-provider app. Start with one provider, validate the concurrency and payment flows, then add multi-provider support as a separate engineering phase.
Do I need to move off Lovable to run a serious booking app?
Not necessarily. Most booking apps can be hardened in place on Supabase. You move off only if you need infrastructure Lovable does not support, want full control of the deploy pipeline, or are scaling beyond what the managed setup allows. The reservation, timezone, and payment fixes apply either way.

Talk to a senior engineer — not a salesperson.

Book a free 30-minute audit call. We'll diagnose what's wrong and tell you exactly what it costs to fix.

Book a free audit call