Nexum: Building a Multi-Tenant Booking SaaS in 6 Months

From a single-user prototype to a production multi-tenant platform servicing barbers, salons, clinics, and studios. 555 API routes, 56 database tables, 9 backend services.

The idea

Nexum started as a question: why is booking software still terrible? Every barber and salon we talked to was using a spreadsheet, a WhatsApp group, or an overpriced enterprise solution. There was a gap between "free but painful" and "expensive but ugly."

Six months later, Nexum is live — 555 API routes, 56 database tables, 9 services, 235 source files, and 99 Zod validation schemas — processing real reservations across multiple stores.

This is the story of those six months — and the numbers behind them.

Nexum (Latin: connection) — the platform that connects appointment-based businesses with their customers. Multi-tenant by design. Built on Bun, Hono, SvelteKit, Drizzle + PostgreSQL.
0 Months to first booking
0 API routes
0 Database tables
0 Zod validation schemas

Multi-tenancy from day one

Every tenant (store) gets isolated data scoped by storeId. One PostgreSQL database, partitioned data. Every API endpoint validates that the authenticated user belongs to the store they're querying.

The requireStoreAccess middleware runs on every business-facing route — it fetches the store, checks membership, and attaches it to the context. 240+ rate-limiting rules protect sensitive endpoints across both Nexum and Identitas.

Three frontends, one backend

Nexum ships three independent SvelteKit apps, all served by the same Hono backend:

  • nexum-ui-admin (205 files) — Platform admin panel. Internal users manage tenants, billing, support.
  • nexum-ui-business (282 files, 54 routes) — Business portal. Store managers run their agenda, staff, services, reports.
  • nexum-ui-client (255 files, 49 routes) — End-user booking UI. Customers browse, book, pay, review.
// One backend, three apps, one identity
// Every request flow:
Browser → Cloudflare → Hono API
  ├─ Auth cookie → POST /internal/validate (Identitas)
  ├─ Tenant scope → requireStoreAccess middleware
  ├─ Rate limit → IP + appId scoped
  └─ Response → { success: true, data, message }

The hard parts (with real bugs)

Not everything was smooth. Some of the bugs we shipped, found, and fixed:

  • The business cookie bug (v0.12.1). The login endpoint set a cookie named nexum_session. The business routes middleware checked for business_session. Every business user got a 401 on every request. It took a week to notice — we tested with a super admin that used a different auth path. Fix released in v0.12.2, the same day we shipped. Lesson: test every user role, not just yours.
  • Real-time slot conflicts. Two customers booking the same slot simultaneously. Fixed with PostgreSQL FOR UPDATE row-level locking + a reservation timeout. The lock releases in 30 seconds; the client polls for confirmation.
  • OIDC deep link for mobile. The OIDC flow with Capacitor (Android app) needs native deep links. Three iterations to get the redirect URI right across local dev, preview, and production.
  • Identitas sync race. Nexum and Identitas both create users. Without idempotency keys, a retry creates duplicate accounts. Now upsert with externalId as the stable key.

What's next

Nexum is live, with 57 commits in the backend repo and 1,038 commits across the entire migration history. The roadmap includes automated waitlist management, AI-powered scheduling suggestions, and a public API for third-party integrations.

The product that took six months to build will take years to mature. The foundation — multi-tenant by design, independently deployable, observable — is built to last.

Nexum is open for business at nexum.primigenum.com. We're booking real appointments. If you run a barbershop, salon, clinic, or studio — or if you want to build something similar — talk to us.