Supabase is an open-source alternative to Firebase built on PostgreSQL. You get Auth, Realtime, Storage, Edge Functions, and Row Level Security, all out of the box. And yes, you can self-host it.
If you've ever worked with Firebase, you know the feeling: everything's great at first, but at some point you realize you're locked into a proprietary ecosystem. That's exactly where Supabase comes in. Let's take a look at what it brings to the table.
What is Supabase anyway? π€
Supabase is an open-source platform that gives you a complete backend, built on PostgreSQL. Instead of a proprietary NoSQL database (like Firestore), you get a real relational database with all the bells and whistles. The best part: you can export your data and migrate anywhere, anytime. No vendor lock-in.
At its core, Supabase consists of several open-source tools that together form a powerful backend:
- PostgreSQL, The database that holds everything together
- GoTrue, Authentication server
- PostgREST, Automatic REST API from your DB schema
- Realtime, WebSocket server for live updates
- Storage, S3-compatible file storage
- Edge Functions, Serverless functions powered by Deno

PostgreSQL under the hood π
The heart of Supabase is PostgreSQL, and that's no accident. Postgres is one of the most powerful open-source databases out there. You get:
- Real SQL queries instead of proprietary query languages
- Foreign keys, joins, views, stored procedures
- Extensions like
pgvectorfor AI/embedding search - Full-text search directly in the database
- JSON/JSONB columns when you want schemaless flexibility
The Supabase dashboard even gives you a SQL editor where you can run queries directly. No extra tools needed.
Client Setup π οΈ
Before we dive into the individual features, let's set up the client. It's incredibly quick:
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = 'https://your-project.supabase.co'
const supabaseKey = 'your-anon-key'
const supabase = createClient(supabaseUrl, supabaseKey)That's it. Seriously. One import, two variables, one function call. From here on, you can access everything through the supabase client.
CRUD Operations π
The Supabase client library makes CRUD operations a breeze. Here are some examples:
// Read data
const { data: posts, error } = await supabase
.from('posts')
.select('id, title, content, created_at')
.order('created_at', { ascending: false })
.limit(10)
// Read a single record
const { data: post } = await supabase
.from('posts')
.select('*')
.eq('id', 42)
.single()
// Create a new record
const { data: newPost, error: insertError } = await supabase
.from('posts')
.insert({
title: 'My first post',
content: 'Hello World!',
author_id: user.id
})
.select()
.single()
// Update a record
const { data: updated } = await supabase
.from('posts')
.update({ title: 'Updated Title' })
.eq('id', 42)
.select()
.single()
// Delete a record
const { error: deleteError } = await supabase
.from('posts')
.delete()
.eq('id', 42)The beauty of it: the API is chainable and type-safe (when using generated types). No more string-based query building.
Authentication π
Supabase comes with a complete auth system. Email/password, magic links, OAuth with Google, GitHub, Apple, and many more, all built in.
// Sign up
const { data, error } = await supabase.auth.signUp({
email: '[email protected]',
password: 'secure-password-123'
})
// Sign in
const { data: session, error: loginError } = await supabase.auth.signInWithPassword({
email: '[email protected]',
password: 'secure-password-123'
})
// OAuth login (e.g. GitHub)
const { data: oauthData, error: oauthError } = await supabase.auth.signInWithOAuth({
provider: 'github',
options: {
redirectTo: 'https://your-app.com/callback'
}
})
// Get current user
const { data: { user } } = await supabase.auth.getUser()
// Sign out
await supabase.auth.signOut()The auth system integrates seamlessly with Row Level Security (more on that in a moment). This means your database automatically knows who's currently logged in.

Realtime Subscriptions β‘
One of the coolest features of Supabase: Realtime. You can subscribe to changes in your database and receive updates pushed via WebSocket.
// Listen to all changes in the posts table
const channel = supabase
.channel('posts-changes')
.on(
'postgres_changes',
{
event: '*', // INSERT, UPDATE, DELETE
schema: 'public',
table: 'posts'
},
(payload) => {
console.log('Change detected:', payload.eventType)
console.log('New data:', payload.new)
console.log('Old data:', payload.old)
}
)
.subscribe()
// Listen only to INSERTs in a specific table
const insertsChannel = supabase
.channel('new-messages')
.on(
'postgres_changes',
{
event: 'INSERT',
schema: 'public',
table: 'messages',
filter: 'room_id=eq.42'
},
(payload) => {
console.log('New message:', payload.new)
}
)
.subscribe()
// Unsubscribe from channel
supabase.removeChannel(channel)With this, you can build real-time features like chat, live dashboards, or collaborative editing, without setting up a separate WebSocket server.
Storage π¦
Need to upload files? No problem. Supabase Storage is an S3-compatible file store with fine-grained access control.
// Upload a file
const { data, error } = await supabase.storage
.from('avatars')
.upload('user-123/profile.png', file, {
cacheControl: '3600',
upsert: true
})
// Generate a public URL
const { data: urlData } = supabase.storage
.from('avatars')
.getPublicUrl('user-123/profile.png')
// Signed URL for private files
const { data: signedUrl } = await supabase.storage
.from('private-docs')
.createSignedUrl('report.pdf', 3600) // valid for 1 hourEdge Functions π
Sometimes you need server-side logic that shouldn't run directly in the database. That's what Edge Functions are for, serverless functions running on Deno, deployed globally.
// supabase/functions/send-welcome-email/index.ts
import { serve } from 'https://deno.land/[email protected]/http/server.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
serve(async (req) => {
const { email, name } = await req.json()
// Supabase client with service role key
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
)
// Send email, write log, etc.
const { error } = await supabase
.from('email_log')
.insert({ email, name, sent_at: new Date().toISOString() })
return new Response(
JSON.stringify({ success: !error }),
{ headers: { 'Content-Type': 'application/json' } }
)
})Edge Functions are perfect for webhooks, third-party integrations, or anything where you need server-side secrets.
Row Level Security (RLS) π‘οΈ
This is where it gets really exciting. Row Level Security is THE killer feature of Supabase. It lets you define directly in PostgreSQL who can see and modify which rows.
-- Enable RLS
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
-- Everyone can read published posts
CREATE POLICY "Public posts are visible"
ON posts FOR SELECT
USING (published = true);
-- Users can only update their own posts
CREATE POLICY "Users can update own posts"
ON posts FOR UPDATE
USING (auth.uid() = author_id)
WITH CHECK (auth.uid() = author_id);
-- Users can only create their own posts
CREATE POLICY "Users can create posts"
ON posts FOR INSERT
WITH CHECK (auth.uid() = author_id);
-- Users can only delete their own posts
CREATE POLICY "Users can delete own posts"
ON posts FOR DELETE
USING (auth.uid() = author_id);The brilliant part: these policies apply always, whether you access data through the REST API, the client, or directly via SQL. Security is enforced at the database layer, not in your application logic. This is a fundamental difference from Firebase Security Rules.
Self-Hosting with Docker π³
Want full control? No problem! Supabase can be completely self-hosted with Docker. The official repository provides a docker-compose.yml that lets you spin up the entire stack locally or on your own server.
# Clone the Supabase repository
git clone --depth 1 https://github.com/supabase/supabase
cd supabase/docker
# Configure environment variables
cp .env.example .env
# Edit .env as needed (secrets, ports, etc.)
# Start the stack
docker compose up -dAfter that, you'll have the complete Supabase stack running: PostgreSQL, Auth, REST API, Realtime, Storage, and the Dashboard. Perfect for development, testing, or when you want to keep your data off the cloud.
Supabase vs. Firebase, The Comparison βοΈ
| Feature | Supabase | Firebase |
|---|---|---|
| Database | PostgreSQL (relational) | Firestore (NoSQL) |
| Open Source | Yes, fully | No |
| Self-Hosting | Yes (Docker) | No |
| Auth | GoTrue (Email, OAuth, etc.) | Firebase Auth |
| Realtime | WebSocket (Postgres Changes) | Firestore Listeners |
| Storage | S3-compatible | Cloud Storage |
| Functions | Edge Functions (Deno) | Cloud Functions (Node.js) |
| Security | Row Level Security (SQL) | Security Rules (custom syntax) |
| Vendor Lock-in | Minimal | High |
| Pricing | Generous free tier | Generous free tier |
When should you use Supabase? π―
Supabase is particularly well-suited when you:
- Have relational data, meaning your data has relationships (and it usually does)
- Already know SQL or want to learn it, you work directly with PostgreSQL
- Want no vendor lock-in, you can migrate anytime
- Need self-hosting, GDPR, compliance, or simply control
- Love type safety, the generated TypeScript types are first-class
- Want to leverage existing PostgreSQL knowledge
Firebase, on the other hand, might be the better choice if you have a very simple project, are deep in the Google ecosystem, or prefer NoSQL data models.
Conclusion π
Supabase has quickly become a serious alternative to Firebase, and in many areas, it's actually superior. The combination of PostgreSQL, built-in auth, Realtime, Storage, and Edge Functions gives you a complete backend without tying you to any single vendor.
Next time you start a new project and need a backend, give Supabase a shot. You'll be surprised how quickly you become productive.
Happy Coding! βοΈ