Case study / Booka

Five applications, one data model.

Booka is a multi-tenant platform for businesses that work by appointment, starting with salons and barbers. It is not a marketplace: no salon is ever listed next to its competitors. It is the system a business runs its day on, with a public site where its clients book, a web management suite, a superadmin console, and two mobile apps, one for staff and one for clients.

My part
The three web applications, and the database together with Giulio
Ownership
Shared venture with Giulio
Shape
Turborepo with three Next.js and two Expo apps, a separate database repository
Status
In production
01

The constraint

The five applications do not move at the same speed or serve the same people. The public site changes with marketing and the launch. The management suite follows what salons ask for: calendar, clients, staff, till, stock, reports, campaigns, billing. The superadmin console changes rarely and must never break. The mobile apps go through store review, so a release there takes days, not minutes.

But they all describe the same world: the same business, the same location, the same client, the same appointment. A client can belong to several locations of the same business, notes are kept per location, consents are kept per business. Splitting the applications was necessary. Splitting the data would have meant five versions of every rule.

So the architecture had one job: let five products evolve on their own schedules while keeping one truth about tenants, people and appointments.

02

The fork

Three shapes solve this. Two of them cost more than they look like they cost.

  1. One repository per app

    Each application owns its own codebase and pipeline, talking to the same database.

    Authentication, the data client, domain types and the design system get written five times and drift apart within a quarter. Every schema change becomes a five-way release negotiation, and nobody knows which copy of a rule is the current one.

    Rejected. Independence bought at the price of the one thing that had to stay shared.

  2. One web application for everyone

    A single deploy that shows a different interface depending on who is signed in, with the mobile apps on the side.

    The public site and the superadmin console would share a bundle, a release and a blast radius: a marketing change could take down the tool used to fix it, and public pages would ship code only administrators should hold.

    Rejected. Audiences with different risk profiles do not belong in one release.

  3. Monorepo, plus a canonical database repository

    One repository for the five applications and their shared packages, each deployed on its own. A separate repository that owns the database: migrations, RLS, RPCs and Edge Functions.

    The boundaries have to be enforced rather than assumed. Shared packages need an owner, the pipeline has to know which app actually changed, and every frontend change that touches data waits for the migration in the other repository.

    Chosen. The data stays single, the releases do not.

03

What that turned into

One Turborepo holds the three Next.js applications I built, the public site, the management suite and the superadmin console, and the two Expo apps Giulio built. They share packages for domain types, authentication, the API client, the design system, feature flags and translations, so a concept is defined once and every app reads it the same way.

The database lives in its own repository, which is the canonical source for migrations, RLS policies, RPCs, triggers and Edge Functions. We designed it together. It is a shared schema with a tenant id on every row and RLS on top, and the operations that matter most do not trust the client: creating and changing appointments goes through RPCs, and staff reach client records through operational functions that respect their role and the locations they are assigned to, never through a direct read of the table.

Some things are ready but deliberately kept out of the promise. Fiscal documents, for example, exist in the schema behind a flag, and stay out of the MVP until they are validated.

04

What this shape buys

A web release does not wait for a store review, and a mobile release does not drag the management suite along with it, because they are different deploy units.

A schema change is written once, in one repository, and the generated types reach every app from the same package. There is one database, not five that agree on a good day.

Rules about who can see what are enforced where the data is. An app with a bug can show the wrong screen, but it cannot read a client record the database would not give it.

Want the same decision made on your product?

This is the part of the work I am usually hired for: the point where a product is about to become several products, and the choice made in that week decides what the next two years cost.

hello@robertodrago.dev