Rezme is a restaurant reservation platform. Guests discover restaurants, book tables and invite friends to a reservation; restaurants run their floor from a dashboard and a staff app.
What’s in it
One TypeScript monorepo holds six apps: a guest app and a business app (both Expo), a business dashboard and an internal admin panel (Next.js on Cloudflare through OpenNext), the API (Hono on Cloudflare Workers, oRPC with OpenAPI) and a notifier worker that owns every email and push message. Infrastructure is code with Alchemy, and GitHub Actions run CI, staging and production deploys, native builds and over-the-air updates.
Parts: Guest app (Expo), Staff apps (Expo · Next.js), Admin panel (Next.js), API Worker (Hono · oRPC), Postgres (rows + outbox), Event relay (leases · retries), Notifier (email · Expo push), Live channel (Durable Object).
- Guest app to API Worker
- Staff apps to API Worker
- Admin panel to API Worker
- API Worker to Postgres (one tx)
- Postgres to Event relay
- Event relay to Notifier (queue)
- Event relay to Live channel
Bookings that can’t collide
A booking commit takes a Postgres advisory lock for each local day the reservation touches, which is two days when a late booking runs past midnight, always in sorted order so two bookings can’t deadlock each other. Inside the locks it checks the lead time, the covers left in the slot and whether the same guest already holds an overlapping booking. A guest looking at stale availability gets a 409 and picks another slot, never a double booking.
- Guest app to API Worker: book slot + party size
- API Worker to Postgres: lock each day in order
- API Worker to Postgres: recheck covers + overlap
- API Worker to Postgres: insert + outbox event
- Postgres to API Worker: commit
- API Worker to Guest app: pending or approved
- API Worker to Event relay: drain the outbox
- Event relay to Postgres: claim rows (SKIP LOCKED)
- Event relay to Notifier: event on the queue
- Event relay: live update to staff
The insert and its domain event commit in one transaction. A relay drains the outbox after the response, with a cron as the backstop: it claims rows with FOR UPDATE SKIP LOCKED under a lease, retries with jittered backoff, and delivers each event to the notifier’s queue, the live channel and analytics separately, so one slow destination never holds back another.
From there a reservation follows the restaurant’s decisions: pending, approved, rejected, cancelled, completed or no-show, with availability, floor plans and table assignment on top.
Parts: Booking commit (under day locks), Pending (manual approval), Approved (or auto-approved), Rejected (by the restaurant), Completed (or no-show), Cancelled (guest or staff).
- Booking commit to Pending
- Booking commit to Approved
- Pending to Approved
- Pending to Rejected (reject)
- Approved to Completed
- Approved to Cancelled
Many restaurants, one API
Data is scoped per restaurant, and staff role changes take a row lock so a venue can’t lose its last owner. A revocable support session lets support staff see a restaurant’s dashboard as the owner does. The notifier sends email and push with reminders in the restaurant’s time zone, and staff screens update live over WebSockets on a Durable Object. Restaurants subscribe through Polar, guests sign in with a verified phone number, and a staff assistant built on the AI SDK and Workers AI answers questions through tool calls on live service, reports, top guests and reservations.
