Case study — Bookable

One booking engine, every service business.

A multi-tenant booking and business-management platform, originally built for massage therapy and generalized to serve spas, salons, legal and consulting, fitness, and home-service businesses — with a companion internal Sales & Commission CRM for the platform's own hired sales reps.

Data model95Prisma models
Backend25+Independently layered modules
AppsTwoProduction SPAs, full e2e coverage
TeamOneSolo full-stack engineer
VerticalsSevenOne generalized engine
01 — The problem

Two problems, one platform — and only one of them was the product.

Service businesses need booking, staff and resource scheduling, payments, and retention tools in one system rather than four subscriptions that don't talk to each other.

Separately, the platform operator needed to pay commission to outside sales reps for new tenant signups — accurately, without manual reconciliation, and without trusting self-reported deal numbers.

02 — Role & stack

Solo full-stack,
end to end

Architecture, backend, both frontend apps, the data model, and the delivery process — owned start to finish.

Express 5 APIHand-rolled clean architecture — domain, application, infrastructure and presentation layers per module, with manual DI.
Platform-admin appReact + Vite — tenant onboarding review, KYC verification, billing oversight, and the sales CRM.
Tenant portalReact + Vite — the customer and staff-facing booking, scheduling and retention surface.
Shared design systemOne Chakra UI v3 system across both SPAs, with dedicated e2e suites per app.
Backend

Express 5 · clean architecture · manual DI · 25+ modules

Data

PostgreSQL · Prisma · 95 models · Decimal-typed money

Frontend

React · Vite · Chakra UI v3 · two SPAs

Payments

Stripe — plans, subscriptions, invoices, payments

Tooling

Nx monorepo · per-app end-to-end suites

03 — What I built

A platform, a portal,
and a CRM

Multi-tenant core

Platform-versus-tenant RBAC scoping underneath every module, enforced independently at the API and the UI.

Real-time booking

Conflict detection, recurring rules, waitlists, and staff and resource availability.

Onboarding & KYC

Tenant onboarding with document verification, reviewed by platform admins before activation.

Billing

Stripe-backed plans, subscriptions, invoices and payments.

Retention suite

Loyalty accounts, gift cards, promotions, campaigns, reviews, reminders and notifications.

Sales & Commission CRM

An internal pipeline and payout ledger for the platform's own hired sales reps.

04 — Deep dive

One engine, seven verticals

A generalized Industry model lets the same booking engine run a massage studio and a home-cleaning business without forking code per vertical.

  • Conflict detection across staff, resources and rooms.
  • Recurring appointment rules and waitlists.
  • Availability modeled per staff member and per resource.
  • Industry-specific behaviour configured, not branched.
MassageSpaSalonLegalConsultingTutoringFitnessHome services
05 — Sales & Commission CRM

Commission you
can't argue with

A Won lead converts into a real tenant signup — no fabricated deal data. Commission is then calculated automatically from that tenant's first real subscription payment.

Lead pipeline
01NewLead enters the rep's pipeline.
02ContactedFirst outreach logged against the lead.
03PitchedPlatform demoed to the prospective tenant.
04NegotiatingPlan and terms under discussion.
05Won / LostA Won lead converts into a real tenant signup.
06CommissionCalculated from that tenant's first real subscription payment.

Every commission record carries a Pending → Confirmed → Clawed-back lifecycle with a 30-day clawback window. Rep dashboards are locked down so a rep only ever sees their own numbers — never the underlying rate or basis amount.

06 — Engineering decisions

Where the money
meets the code

01
Decimal all the way downMoney math

All commission math runs on Prisma's Decimal type end to end, never a plain JS number.

Why — It closes the exact floating-point rounding gap that would undermine trust in every number a rep sees.

02
The path that was never written toDead code

Tracing the obvious data path for payment amounts turned up dead code — nothing had ever written to that table in production.

Why — Re-routed the commission trigger onto the signal that was actually reliable, instead of building a feature on a broken assumption.

03
Clawback modeled on day oneLifecycle

Commission carries a Pending / Confirmed / Clawed-back lifecycle with a 30-day window, even though payout execution itself was out of scope.

Why — Cheap to add before reps see numbers, expensive to retrofit after.

04
RBAC enforced twiceAccess

Permissions are enforced independently at the API and the UI layer, verified with live evidence rather than narrated test claims.

Why — Reps only ever see their own numbers — never the underlying rate or the basis amount.

05
One model, no forksDomain

A generalized Industry model lets the same booking engine run a massage studio and a home-cleaning business.

Why — Adding a vertical is configuration, not a code branch to maintain forever.

07 — Scale

Shipped as four phases and twenty-nine plans.

95 Prisma modelsA single data model spanning bookings, tenancy, billing, retention and the sales CRM.
25+ backend modulesEach independently layered across domain, application, infrastructure and presentation.
2 production appsPlatform admin and tenant portal, each with its own end-to-end suite.
4 phases · 29 plansThe complete milestone shipped for the commission feature.

Built to add verticals, not forks.