Back to Articles
Why I Chosen Supabase Over Firebase for PaceFyndr

Why I Chosen Supabase Over Firebase for PaceFyndr

Feb 2, 2026•3 min read
technicalSupabaseFirebaseSQLReact Native
Share

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:

  1. Client-side Joins: Fetch all IDs of people you follow, then fetch their latest runs. This destroys performance as your network grows.
  2. 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:

sql
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:

typescript
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:

terminal
supabase gen types typescript --project-id "$PROJECT_ID" > types/supabase.ts

Now, 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:

sql
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.

Related Articles

Installing a Senior React Native Engineer into My IDE: A Guide to Agent Skills

Installing a Senior React Native Engineer into My IDE: A Guide to Agent Skills

Why Prompts Are Dead: Building Modular AI with Agent Skills

Why Prompts Are Dead: Building Modular AI with Agent Skills

Native Tabs in Expo Router: The iOS 26 Liquid Glass Era

Native Tabs in Expo Router: The iOS 26 Liquid Glass Era