Back to articles
engineering July 30, 2026 7 min read

Why Moving from Firebase to Supabase Changed My Development Workflow

Switching from Firebase to Supabase reshaped my workflow: real‑time SQL queries, open‑source flexibility, and row‑level security replaced noisy NoSQL hacks.

When I first started a side project in early 2025, Firebase felt like the perfect backend‑as‑a‑service. Its real‑time database, generous free tier, and client SDKs let me spin up a prototype in a single afternoon. Six months later, after the product grew past the hobby stage, I hit a wall: complex queries, security concerns, and vendor lock‑in started to bite. That’s when I decided to migrate to Supabase, an open‑source alternative built on PostgreSQL. The move didn’t just change the technology stack, it fundamentally altered how I think about data, security, and real‑time updates.

Real‑time Subscriptions: From Document Paths to SQL Queries

Firebase’s real‑time capabilities are centered around a hierarchical document store. You subscribe to a path, and any change under that node triggers an update. It’s simple, but the simplicity can become a curse when you need to filter or join data.

// Firebase: listen to all messages in a chat room
import { getDatabase, ref, onValue } from "firebase/database";

const db = getDatabase();
const messagesRef = ref(db, "rooms/room123/messages");

onValue(messagesRef, snapshot => {
  const msgs = snapshot.val();
  console.log("New messages:", msgs);
});

The code above gives you every message in the room, regardless of who sent it, its status, or any other attribute. If you need only unread messages, you have to either download everything and filter client‑side, or restructure your data to nest unread flags under a separate node, a pattern that quickly proliferates.

Supabase’s real‑time layer sits on top of PostgreSQL’s logical replication. You subscribe to a SQL query, not a static path. That means you can express filters, joins, and even aggregate functions right in the subscription.

// Supabase: listen only to unread messages for the current user
import { createClient } from "@supabase/supabase-js";

const supabase = createClient(
  "https://xyz.supabase.co",
  "public-anon-key"
);

const userId = "user_42";

const subscription = supabase
  .channel("public:messages")
  .on(
    "postgres_changes",
    {
      event: "INSERT",
      schema: "public",
      table: "messages",
      filter: `room_id=eq.room123 and read_by @> '{${userId}}' = false`
    },
    payload => {
      console.log("New unread message:", payload.new);
    }
  )
  .subscribe();

Notice three key differences:

In practice, this reduced the amount of data transferred to my mobile app by roughly 70 % and eliminated a whole layer of client‑side state management.

Open‑Source SQL Power vs. NoSQL Document Store

Firebase’s NoSQL model is great for schemaless data, but it forces you to think in terms of denormalized documents. When relationships become more than one‑to‑many, you start writing duplicated data or performing multiple round‑trips.

// Firebase: fetch a post and its author (two separate reads)
const postRef = ref(db, `posts/${postId}`);
onValue(postRef, snap => {
  const post = snap.val();
  const authorRef = ref(db, `users/${post.authorId}`);
  onValue(authorRef, aSnap => {
    const author = aSnap.val();
    console.log({ post, author });
  });
});

Supabase lets you keep a normalized schema, leverage indexes, and write complex queries with a single request.

// Supabase: fetch a post with its author in one go
const { data, error } = await supabase
  .from("posts")
  .select(`*, author:users(*)`)
  .eq("id", postId)
  .single();

if (error) throw error;
console.log(data);

Beyond convenience, PostgreSQL brings a mature ecosystem:

Because Supabase is open source, I could also spin up a local instance for integration tests, run migrations with pg_dump, and even contribute a bug fix upstream. That level of control simply isn’t possible with Firebase’s closed‑source backend.

Row‑Level Security (RLS) vs. Firebase Security Rules

Firebase’s security model is expressed as a set of declarative rules that run on the server before each read or write. They are powerful, but they operate on the document hierarchy and often require duplicated logic for each path.

// Firebase security rule: only the owner can edit a document
{
  "rules": {
    "posts": {
      "$postId": {
        ".write": "auth != null && auth.uid == data.ownerId"
      }
    }
  }
}

When you need to enforce the same rule across multiple collections, you end up copying the snippet or creating helper functions that can become hard to audit.

Supabase’s Row‑Level Security lives inside PostgreSQL. You write policies that reference any column, join other tables, or call functions. The policies are attached to a table, and PostgreSQL evaluates them automatically for every query.

-- Enable RLS on the posts table
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

-- Policy: users can only update their own posts
CREATE POLICY "owner_can_update" ON posts
  FOR UPDATE
  USING (auth.uid() = owner_id);

-- Policy: anyone can read public posts, owners can read drafts
CREATE POLICY "read_access" ON posts
  FOR SELECT
  USING (
    is_public = true OR auth.uid() = owner_id
  );

From the client side, the same JavaScript call works, and PostgreSQL guarantees that the policies are enforced for every query, even raw SQL executed in a function.

// Supabase: attempt to update a post – policy will reject if not owner
const { error } = await supabase
  .from("posts")
  .update({ title: "New title" })
  .eq("id", postId);

if (error) console.error("Permission denied", error.message);

Benefits I observed:

How the Workflow Shifted

Before (Firebase)

After (Supabase)

The net effect was a 30 % reduction in client‑side boilerplate and a 50 % faster iteration cycle because I could reason about data relationships directly in SQL rather than stitching together document paths.

Migration Tips for Teams Considering the Switch

Final Thoughts

Moving from Firebase to Supabase felt like swapping a Swiss‑army knife for a well‑organized toolbox. I still love Firebase’s instant‑setup vibe for prototypes, but once a product matures, the relational power, open‑source transparency, and robust security model of Supabase pay off in developer velocity and confidence.

If you’re wrestling with noisy client‑side state, duplicated security logic, or the limits of NoSQL queries, give Supabase a spin. The learning curve is modest especially if you already know SQL, and the payoff is a cleaner, more maintainable workflow that scales with your ambitions.

Need something like this built?

I work on full-stack web apps — backend systems, APIs, and the front-ends that sit on top. If this post was useful and you've got a project that needs it, I'd like to hear about it.

Want future posts like this?

No mailing list yet — for now, email me and I'll let you know when something new goes up.

Email me