
Why I Chosen Supabase Over Firebase for PaceFyndr
When I started building PaceFyndr, a social platform for runners to find partners and join community runs, I initially reached for Firebase. It's the default choice for React Native apps: auth is instant, and Firestore is easy to start with.
But as the social features grew—friend requests, run feed, community events—I hit a wall. Here is why I migrated to Supabase.
The Social Data Problem
Social apps are fundamentally relational.
- Users have many Runs.
- Runs have many Attendees.
- Users follow many other Users.
- Feed posts belong to Users and have many Likes and Comments.
The Firebase (NoSQL) Struggle
In Firestore, data is stored in documents. To show a "Feed" of runs from people you follow, you have two bad options:
- Client-side Joins: Fetch all IDs of people you follow, then fetch their latest runs. This destroys performance as your network grows.
- Denormalization: Copy the user's name and avatar onto every single Run document. When a user changes their profile picture, you have to run a cloud function to update it across 1,000 past runs.
I found myself writing massive Cloud Functions just to keep data in sync.
The Supabase (SQL) Solution
Supabase is just PostgreSQL. The "Feed" problem is solved with a single standard SQL query:
select
runs.*,
profiles.username,
profiles.avatar_url
from runs
join profiles on runs.user_id = profiles.id
where runs.user_id in (
select following_id from followers where follower_id = auth.uid()
)
order by created_at desc;That's it. No cloud functions. No stale data. Just a query.
The N+1 Query Problem
With Firebase, fetching a list of runs and then fetching the profile for each runner often leads to the "N+1" problem: 1 request for the list, and N requests for each user profile.
Supabase allows you to select related data in a single request:
const { data, error } = await supabase
.from('runs')
.select(`
*,
profiles (
username,
avatar_url
)
`)This returns a perfectly nested JSON response in one round-trip.
Type Safety
This was the biggest developer experience upgrade. With Firebase, I was manually typing my documents: interface Run { ... }. If I added a field in the DB but forgot to update the code, the app would crash.
Supabase generates TypeScript definitions directly from your database schema:
supabase gen types typescript --project-id "$PROJECT_ID" > types/supabase.tsNow, my entire frontend is strictly typed to my database schema. If I rename a column in Postgres, my React Native build fails immediately.
Row Level Security (RLS)
In Firebase, security rules are a custom JavaScript-like syntax that lives in a text file. They can get incredibly complex for social features ("allow read if request.auth.uid is in the resource.data.members array").
In Supabase, security is just SQL policies attached to the table:
create policy "Public profiles are viewable by everyone."
on profiles for select
using ( true );
create policy "Users can insert their own profile."
on profiles for insert
with check ( auth.uid() = id );Conclusion
Firebase is fantastic for simple apps, chat logs, or presence systems. But for PaceFyndr, where relationships between runners, events, and communities are the core core value, a relational database was the only scalable choice.
Supabase gave me the power of Postgres with the ease of use of a backend-as-a-service.


