Back to projects

QueuePage — Multi-Tenant Booking Platform Architecture (Design Document)

The architecture specification for a multi-tenant booking SaaS for Thai service businesses, revised through five review rounds until every internal contradiction was resolved and the design was frozen. This is design work — no code has been written yet.

  • PostgreSQL
  • Next.js
  • System Design
  • Multi-tenant SaaS
  • Prisma
  • LINE LIFF
  • PDPA

The problem

Thai appointment-based businesses — salons, massage and spa, clinics, barbers — still take most bookings by hand over LINE chat. The result is double-booked slots, no-shows with no cost attached, and no history to learn from.

Existing off-the-shelf systems tend to impose one booking shape, when different businesses genuinely book differently: salons book a specific stylist, spas book a room, clinics issue walk-in queue numbers. A business whose shape doesn't match simply cannot use the product.

What I built

Rather than imposing one model, I designed all four booking modes (by staff member / by room or equipment / walk-in queue number / multi-service and group) as things each business turns on and off itself — with per-industry presets and a five-question setup wizard so a non-technical owner cannot misconfigure it.

The part that took longest to get right was PDPA compliance, because in a multi-tenant system "who owns this data" has no single answer. For booking data the business is the Controller and we are the Processor, requiring a DPA. But for cross-business no-show statistics we determine the purpose ourselves, which makes us the Controller. The role follows the purpose; it is not one setting for the whole platform.

Anything not yet cleared by legal review (minors, health data) is feature-gated so it cannot reach production on its own.

Architecture

A single Next.js App Router application — not a monorepo — split into domain modules, with audiences separated at the routing layer rather than by permission checks alone: customer-facing shop pages live at the root path (`queuepage.com/<shopSlug>`), the business dashboard is served at `app.<domain>`, and our own platform admin at `admin.<domain>`.

Double-booking is prevented by a PostgreSQL `EXCLUDE` constraint — the database enforces it, rather than application logic that loses a race.

Authentication is segmented by audience instead of unified: customers use LINE Login or phone + OTP with no password at all; businesses use phone + OTP; staff use a PIN on a registered device; our own admins are required to use a password plus TOTP, with SMS explicitly forbidden. `Branch` and `branchId` are in the very first migration even though multi-branch features are deferred, because retrofitting a tenant scope later means migrating the entire database.

My contribution

The design is entirely mine — framing the problem, making the architectural decisions, writing the specification, and then reviewing my own document for contradictions across five rounds. Every decision is recorded with its reasoning, and the document draws an explicit line between what is frozen (multi-tenancy, booking lifecycle, auth model, LINE architecture) and what remains free to change (UX copy, presets, pricing).

Challenges and trade-offs

Contradictions inside my own document. The second review found eleven internal conflicts; the third surfaced problems in the slot lifecycle and in who the booking actor is; the fourth forced splitting consent from agreement, which had been conflated. Every time, I fixed the document first, so the spec and the intent never drifted apart.

Knowing when to stop designing. This was harder than the design itself. I wrote into the document that further architecture review now returns less than putting it in front of three to five pilot businesses, and froze v1 — changeable only with a genuinely new reason, recorded as an ADR. Hunting for more edge cases feels productive, but it is really just postponing the day you hear from a real user.

Result

A frozen, implementable specification, with delivery split into Phase 1a (3–5 pilot businesses in real use) → 1b (public launch) → 2 → 3, deliberately shipping early to get real feedback before building the rest.

No code has been written yet. This is included as systems design work, not as a shipped product.