TL;DR: 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! ✌️