Supabase in Action: 3 Patterns for Building Firebase-Grade Backends
You hit Firebase’s free tier limit on a Friday afternoon. Your auth is tightly coupled to their SDK. Your storage bucket is configured through their dashboard. And you’re three months from launch with paying users.
That’s not a hypothetical. It’s a pattern.
Supabase is the open-source alternative that has grown up: a real PostgreSQL database, full auth, real-time subscriptions, and file storage—all composable, with a DX that respects your time. No vendor lock-in. No tier-based feature amputation. Infrastructure that scales from weekend project to Series A.
If you’ve been eyeing Supabase but weren’t sure where to start, these three patterns cover the ground most real projects need.

Pattern 1: Database Operations — PostgreSQL Without the Ceremony
The core of Supabase is a PostgreSQL database you query directly from your frontend. No ORM required. The client gives you a clean interface over REST and WebSockets, while the full power of SQL sits underneath.

Basic CRUD:
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(supabaseUrl, supabaseKey);
// Create
const { data, error } = await supabase
.from('tasks')
.insert([{ title: 'Ship article', completed: false, user_id: userId }])
.select()
.single();
// Read with filters
const { data: tasks } = await supabase
.from('tasks')
.select('*')
.eq('user_id', userId)
.eq('completed', false)
.order('created_at', { ascending: false })
.limit(20);
// Update
const { data: updated } = await supabase
.from('tasks')
.update({ completed: true })
.eq('id', taskId)
.select()
.single();
// Delete
await supabase.from('tasks').delete().eq('id', taskId);
That covers the basics. The real leverage is what PostgreSQL gives you for free: joins, full-text search, transactions, and aggregate functions without leaving JavaScript.
For search across multiple columns without raw SQL:
const { data } = await supabase
.from('posts')
.select('*')
.or(title.ilike.%${query}%,body.ilike.%${query}%)
.limit(10);For aggregations computed server-side in a single round-trip:
const { data } = await supabase
.from('orders')
.select(
count:id.count(),
total:amount.sum(),
average:amount.avg()
)
.eq('user_id', userId)
.single();The schema lives in PostgreSQL. Triggers, views, stored procedures, and row-level security all work as they would in any production Postgres deployment. When you scale from zero to a million rows, you just resize the instance—you’re not migrating to a “real database.”
Pattern 2: Authentication — More Than Email and Password
Supabase Auth handles the full auth lifecycle: sign-up, sign-in, OAuth via GitHub/Google/Facebook/Azure, password reset, and automatic session refresh. A database trigger can provision a user profile row the moment someone creates an account—zero manual provisioning.
A clean auth service:
class AuthService {
constructor(private supabase: SupabaseClient) {}
async signUp(email: string, password: string, metadata?: Record) {
return this.supabase.auth.signUp({
email, password,
options: { data: metadata }
});
}
async signIn(email: string, password: string) {
return this.supabase.auth.signInWithPassword({ email, password });
}
async signInWithOAuth(provider: 'github' | 'google') {
return this.supabase.auth.signInWithOAuth({
provider,
options: { redirectTo: ${window.location.origin}/auth/callback }
});
}
async signOut() { return this.supabase.auth.signOut(); }
onAuthStateChange(callback: (event: string, session: Session) => void) {
return this.supabase.auth.onAuthStateChange(callback);
}
}
onAuthStateChange is the reactive core for React and Next.js apps. Wire it once and your entire UI reacts to sign-in and sign-out events—no prop drilling, no context soup.
Authorization is where most auth implementations get sloppy. Supabase handles it through Row Level Security policies enforced by PostgreSQL itself:
create policy "Users read only their own tasks"
on tasks for select
using (auth.uid() = user_id);
create policy "Users insert only their own tasks"
on tasks for insert
with check (auth.uid() = user_id);
Even if someone crafts a raw HTTP request to your Supabase endpoint, the database blocks unauthorized access. Your application code never checks permissions—the database does it automatically, on every query.

Pattern 3: Real-Time Subscriptions and File Storage
Supabase Broadcast lets you subscribe to database changes over WebSockets. Instead of polling every five seconds, open a channel and PostgreSQL pushes changes the instant they happen:
const channel = supabase
.channel('tasks-changes')
.on(
'postgres_changes',
{
event: '*',
schema: 'public',
table: 'tasks',
filter: user_id=eq.${userId}
},
(payload) => {
if (payload.eventType === 'INSERT') {
setTasks(prev => [payload.new, ...prev]);
} else if (payload.eventType === 'UPDATE') {
setTasks(prev => prev.map(t => t.id === payload.new.id ? payload.new : t));
} else if (payload.eventType === 'DELETE') {
setTasks(prev => prev.filter(t => t.id !== payload.old.id));
}
}
)
.subscribe();File storage works the same way—through buckets you manage from the client. Upload avatars, documents, or generated assets directly from the browser without touching S3:
const { data, error } = await supabase.storage
.from('avatars')
.upload(${userId}/profile.jpg, fileBlob, {
cacheControl: '3600',
upsert: true
});
const { data: { publicUrl } } = supabase.storage
.from('avatars')
.getPublicUrl(${userId}/profile.jpg);
Storage uses the same RLS model as your database. A policy can enforce that users only read avatars in their own folder and only upload files under their own user ID path—no application-level check needed.
The Trade-Off Worth Knowing

Supabase builds on PostgreSQL, which is a bet with decades of proof behind it. The real trade-off is counterparty risk: Supabase the company is younger than Firebase and less proven at extreme scale.
For early-stage products, MVPs, and growing startups, that risk is manageable. The open-source offering is genuinely full-featured. Even if Supabase faces headwinds, your data runs on PostgreSQL—you can self-host or migrate to any Postgres provider without a full rewrite.
If you’re evaluating backends in 2026, spend an afternoon with the docs at supabase.com. You might be surprised how little infrastructure you actually need.

