
In motorsport, the driver never talks directly to the tyre factory. When a GR.3 car comes in for a stop at Spa, the driver doesn’t radio Michelin to ask what compound they’re sending. There’s a whole layer between them: the pit wall. Engineers, strategists, data analysts. They take the raw information from the car, the weather, the race, and they decide what happens next. The driver focuses on driving.
I thought about this a lot when I was building my first real mobile app with a backend. Someone explained to me why the intermediary existed, and it wasn’t about social convention. It was about what happens when you remove it.
The intermediary protects both sides from each other.
The rule I learned and why it’s right
A mobile app talking directly to a database is a bad idea, and the reasons aren’t complicated.
You can’t trust the client. A mobile app runs on hardware you don’t control. Anyone can intercept its traffic, decompile the binary, or read the credentials you embedded in it. If your app has a direct connection string to your Postgres instance, that connection string is yours until it isn’t. A backend sits in front of the database and becomes the only thing that ever touches it directly. The client never gets close.
The app also becomes glued to the schema. If you change a table name, rename a column, or move data to a different store entirely, every version of the app that’s ever been shipped needs to know about it. With a backend in the middle, you can change whatever you want internally and keep the API contract stable. The old app versions keep working. The driver keeps driving while the engineers swap the tyres.
And then there’s business logic. Validation, rate limiting, access control, audit trails. None of this belongs in a mobile app. If it does, every client has to implement it, and you’ll spend the rest of your career debugging inconsistencies between Android, iOS, and whatever web version exists.
The pit wall exists for good reasons.
The years I spent building my own
I knew the rule. So I built the pit wall.
First it was Laravel. PHP never felt like mine. I was always fighting the framework slightly, reading docs for things that should have been obvious, spending hours on auth flows that felt like they should take twenty minutes. But it worked, and I shipped things.
Then Lumen, which was Laravel stripped down. Faster to start, same underlying problem: I was a mobile developer spending most of my time in a backend codebase I didn’t fully trust myself in.
Then FastAPI. Python this time, which I liked more. FastAPI is genuinely good software. But the result was the same: two codebases, two sets of dependencies to keep updated, two things to deploy, two things that could break at 2am. The mobile app was what I wanted to build. The backend was the toll I paid to build it correctly.
Every project started with two weeks of backend setup before I wrote a single line of mobile code. And every time something went wrong on the backend, I had to context-switch out of Kotlin and into a world I was less comfortable in. I was building my own pit wall from scratch, every single time.
What changed
I moved to Supabase. The first thing I noticed was that I didn’t need two weeks of setup.
Supabase is a backend. A real one. It has a database (Postgres), authentication, storage, and edge functions if you need them. The difference is that it’s a backend someone else already built, and it comes with a client SDK that feels like talking to a local API. You create tables, you set Row Level Security rules, and your mobile app talks to Supabase the same way it would talk to your own FastAPI instance: through a client, with auth tokens, with no direct database access.
The pit wall is still there. I didn’t remove the intermediary. I just stopped being the one who builds it.
// androidApp/SupabaseClient.kt
val supabase = createSupabaseClient(
supabaseUrl = BuildConfig.SUPABASE_URL,
supabaseKey = BuildConfig.SUPABASE_ANON_KEY
) {
install(Auth)
install(Postgrest)
}
The anon key is not the same as giving someone your connection string. It’s a public key that only works within the boundaries of the RLS rules you’ve defined. Supabase validates every request against those rules server-side, before anything touches the database. That’s the pit wall doing its job. The client just doesn’t see it.
// androidApp/SessionRepository.kt
suspend fun getCurrentUser(): User? {
return supabase.auth.currentUserOrNull()
}
suspend fun fetchUserSessions(userId: String): List<Session> {
return supabase.from("sessions")
.select {
filter { eq("user_id", userId) }
}
.decodeList()
}
Auth is already there. The database is already there. The RLS rules I write are the business logic layer, and they live server-side even though I write them in Supabase’s dashboard. If a user tries to fetch another user’s sessions, the rule blocks it. Not my app code — the backend.
The intermediary didn’t disappear, it was abstracted
I’ve seen people describe Firebase and Supabase as “going directly to the database,” as if the traditional rule no longer applies. It does. Someone else just implemented it.
When your mobile app calls supabase.from("sessions").select(), that request goes to Supabase’s API layer, which authenticates it, checks your RLS rules, and then queries Postgres. Your app never touches the database. The pit wall is still there. You just didn’t have to pour the concrete.
For a solo mobile developer or a small team without backend experience, this changes what’s actually feasible. The constraint used to be clear: if you want to do this properly, you need backend skills or someone who has them. That’s less true now. You can build a production app with auth, a relational database, and proper access control without writing a line of server-side code. It’s not a workaround — it’s a different way of dividing the work.
When building your own pit wall still makes sense
Supabase and Firebase aren’t the right answer for everything.
If you’re working at a company with a backend team, the API they built is the pit wall. Don’t bypass it. The consistency, the versioning, the business logic that already lives there — a mobile app that goes around it creates problems that take months to untangle.
If your logic is genuinely complex — multi-step transactions, integration with external services, heavy computation — RLS rules and edge functions will eventually feel like the wrong tool. At some point, a FastAPI endpoint is just cleaner than a chain of Supabase functions trying to do the same thing.
If compliance is involved, GDPR or anything that requires you to know exactly where data lives and how it moves, you’ll want full control over the stack. And if you’re building something that needs to scale to millions of users with specific performance requirements, you’ll want to own the infrastructure, the query optimisation, the caching layer.
Red Bull doesn’t rent their pit wall.
But if you’re a mobile developer building a product and the backend keeps getting in the way of shipping it, you don’t have to build the pit wall to race.
Wrapping up
The rule hasn’t changed: the client doesn’t talk directly to the database. What changed is who builds the layer in between. Supabase, Firebase, and tools like them are backends, not bypasses. The intermediary is still there.
For mobile developers who’ve spent years building backend infrastructure they didn’t enjoy and weren’t fully confident in, this is worth taking seriously. I lost a lot of time to Laravel, Lumen, and FastAPI — not because they’re bad tools, but because they weren’t mine. Supabase gave me back that time.
The race is still the race. You just get to spend more of it driving. 🏁
The KMP Bits app is available on App Store and Google Play — built entirely with KMP.
Comments
Loading comments...