How We Structured a Turborepo for a Multi-App Restaurant Platform
At some point, separate repositories stop feeling tidy and start feeling fragmented.
That was the situation on one of our restaurant delivery projects. We were not building one interface. We had a customer website, a driver app, a kitchen screen, an admin panel, an API, and a landing site. Managing all of that across isolated repositories would have created more coordination work than it saved.
So we moved to a monorepo with Turborepo.
Why a Monorepo Made Sense
The main reason was not fashion. It was shared logic.
Order statuses, domain types, API contracts, permissions, constants, and validation rules were used across several apps. Keeping those in separate repositories almost guarantees drift over time. A monorepo gave us one place to define shared contracts and one place to see what breaks when those contracts change.
That was especially useful when the same business process flowed through multiple interfaces.
The Setup
The repo was split into separate apps:
- web
- driver
- kitchen
- admin
- api
- landing
And a few shared packages for:
- common types
- utilities
- shared configuration
- UI or design primitives where they made sense
There is nothing unusual about that structure. The value was not novelty. The value was keeping related systems aligned.
What Turborepo Helped With
Shared contracts
This was the biggest benefit.
When the backend model changed, frontend impact surfaced quickly. That reduced the amount of hidden inconsistency between apps.
Build coordination
Caching and task orchestration helped in CI and local development, especially when only one part of the system changed.
Refactoring across apps
Cross-app refactors were much easier when everything lived in one repo. That matters more once the project stops being small.
What Was Annoying
Expo in a monorepo
The driver app used Expo, and Expo inside a monorepo required more setup than the marketing around it usually suggests. Metro configuration, package resolution, and workspace behavior all needed attention before things settled down.
It was manageable. It was just not smooth on the first try.
Shared package boundaries
It is easy to say "share code." It is harder to decide what actually belongs in shared packages.
Share too little and you get duplication. Share too much and the shared layer becomes a dumping ground for mixed concerns. That boundary had to be adjusted over time.
Real-time behavior across multiple apps
Orders moved through customer, kitchen, admin, and driver flows. That meant real-time updates had to be designed around a clear source of truth. Otherwise state logic starts duplicating itself in subtle ways.
The Practical Problem Nobody Sees on Diagrams
One of the least glamorous parts of the system was kitchen printing.
The kitchen needed new orders to appear quickly and print reliably on a thermal printer. A normal web app is not a great interface for raw printer communication, so we used a small local bridge service to handle that part.
It was not elegant architecture on a slide. It was the correct solution for the environment.
What I Would Be More Careful About
If I were doing the same setup again, I would define package ownership and shared boundaries even earlier.
A monorepo only helps when it increases clarity. If too many responsibilities bleed into the shared layer, the repo becomes harder to navigate instead of easier.
I would also centralize validation and domain rules more aggressively from the start. The more interfaces a platform has, the more expensive inconsistency becomes.
Final Thought
I do not think monorepos are automatically better. They are better when several apps are really different views of the same product.
That was the case here.
The apps were not independent projects. They were parts of one operational system, so keeping them together made sense.