A product that runs on the web, iOS and Android can easily turn into three products: three codebases, three sets of business rules, and three places for the same bug to hide. For a small team, or a single developer, that is not sustainable.
RestorePartnerPro is a referral marketplace with a web platform and apps for both mobile platforms. I built all of it on my own and have run it for more than two years, with 300+ leads a month flowing through it. The reason that is manageable is one architectural decision: one Laravel API and one Flutter codebase. Here is why that combination works, and the habits that keep it healthy.
The shape of the system
- Laravel holds everything that matters: the data model, business rules, authentication, permissions, notifications and the admin side.
- A REST API exposes that to every client.
- Flutter builds the iOS and Android apps from a single Dart codebase.
- The web front end is just another client of the same API.
There is exactly one place where a lead’s rules live. Web, iOS and Android can’t disagree about what a lead is, because none of them decides.
Why Flutter for the apps
Flutter compiles to native code for both platforms from one codebase. For a business app, with forms, lists, detail screens, notifications, maps and photos, it covers almost everything you need, and a feature is built once and ships to both stores together.
The practical gains for a lean team:
- One codebase to fix. A bug fixed once is fixed on both platforms.
- Feature parity by default. Android users don’t wait a month behind iOS users.
- One mental model. The same widgets, state management and API client everywhere.
When would I not use Flutter? If the app is mostly a thin wrapper around heavy platform-specific features, such as advanced AR, deep Bluetooth work or very custom native UI. Those cases are rarer than people expect, and it is worth asking the question honestly at the start.
Why Laravel for the API
Laravel gives you the backend pieces a mobile product needs without assembling them yourself: authentication (Sanctum works well for mobile tokens), validation, queues for notifications and emails, policies for permissions, and an admin panel built in the same codebase. Most importantly, it keeps business logic on the server, which matters more for mobile than for web.
Rule one: keep the apps thin
A web deploy reaches every user in seconds. A mobile release goes through app review, and then sits waiting for users to update. Some users never do.
That changes how you design. Anything that might change, such as pricing rules, validation, which actions are allowed and what a status means, belongs on the server. The app asks the API and displays the answer. When a rule changes, you deploy the backend and every client, including the old app versions in people’s pockets, behaves correctly.
Rule two: design the API for the screens
When one person builds both ends, there is no excuse for an API that makes the app do five requests to draw one screen. Shape responses around what the screens need, and keep them consistent with Laravel’s API Resources:
class LeadResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'status' => $this->status,
'status_label' => $this->status->label(),
'submitted_at' => $this->created_at->toIso8601String(),
'referrer' => new ReferrerResource($this->whenLoaded('referrer')),
'can' => [
'update' => $request->user()->can('update', $this->resource),
],
];
}
}
Two habits worth copying from that example. Send display-ready values like status_label, so wording changes don’t need an app release. And send permissions in the payload (can.update), so the app shows or hides buttons based on the server’s rules instead of duplicating them.
Rule three: never break old app versions
Because old versions stay installed, the API has to stay backwards compatible:
- Add, don’t change. New fields are fine; renaming or removing a field breaks older apps.
- Version when you must. If a breaking change is unavoidable, introduce
/v2routes and keep/v1alive until usage dies down. - Have a minimum-version switch. A small endpoint that tells the app the minimum supported version lets you prompt users to update when you truly need to.
Rule four: share the boring parts
On the Flutter side, keep one API client with authentication, error handling and retries in one place, and generate or hand-write typed models that mirror your API Resources. On the Laravel side, the same Resources serve the web front end and the apps. Every screen benefits when the plumbing is done once.
What this buys you
The payoff isn’t elegance; it is time. With one backend and one mobile codebase:
- New features are designed once, built on the API, and appear on every platform.
- Bugs are found and fixed in one place.
- One developer can own the whole product without it slowing down, which is exactly how RestorePartnerPro has kept growing for more than two years.
If you are planning a product that needs web, iOS and Android, this is the setup I recommend for most teams. The RestorePartnerPro case study shows how it worked in production.