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
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.
The fork
Three shapes solve this. Two of them cost more than they look like they cost.
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.
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