Supabase — The Open-Source Backend Platform Replacing Firebase
Posted on: 4/27/2026 4:14:07 PM
Table of contents
- 1. What Is Supabase and Why Does It Matter?
- 2. Architecture Overview
- 3. PostgreSQL — The Heart of Supabase
- 4. Comprehensive Authentication with GoTrue
- 5. Realtime — Live Data Synchronization
- 6. Edge Functions — Serverless on Deno Runtime
- 7. Storage — File Management with RLS
- 8. Vector Embeddings — AI/ML Search with pgvector
- 9. Supabase vs Firebase Comparison
- 10. Pricing — Free Tier and Real-World Costs
- 11. Integration with Vue.js — Practical Example
- 12. Self-Hosting — Full Control
- 13. When to Choose Supabase?
- 14. Conclusion
- References
1. What Is Supabase and Why Does It Matter?
Supabase is an open-source backend development platform built entirely on PostgreSQL — the world's most popular open-source relational database. Instead of being locked into a proprietary ecosystem like Firebase (Google), Supabase lets you own your data, self-host when needed, and migrate at any time.
Supabase's tagline — "Build in a weekend, Scale to millions" — is not marketing fluff. With Supabase, you instantly get: database, authentication, real-time subscriptions, storage, edge functions, vector embeddings, and auto-generated RESTful APIs — all from a single dashboard.
Why Choose Supabase Over Firebase?
Firebase uses Firestore (proprietary NoSQL) — all your data is locked into Google Cloud. Supabase uses standard PostgreSQL: you can export, migrate, or self-host at any time. Zero vendor lock-in.
2. Architecture Overview
Supabase is not a monolithic service but a tightly integrated suite of open-source tools. Each component can run independently.
graph TD
A["Client App
(React, Vue, Flutter)"] --> B["API Gateway
Kong / Supabase Gateway"]
B --> C["PostgREST
Auto REST API"]
B --> D["GoTrue
Auth Service"]
B --> E["Realtime
Elixir Server"]
B --> F["Storage API
S3-compatible"]
B --> G["Edge Functions
Deno Runtime"]
C --> H["PostgreSQL
+ pgvector, PostGIS"]
D --> H
E --> H
F --> I["Object Storage
(S3 / local)"]
G --> H
style A fill:#e94560,stroke:#fff,color:#fff
style B fill:#2c3e50,stroke:#fff,color:#fff
style C fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style D fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style E fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style F fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style G fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style H fill:#4CAF50,stroke:#fff,color:#fff
style I fill:#16213e,stroke:#fff,color:#fff
Supabase Architecture — each component is an independent open-source project
| Component | Technology | Role |
|---|---|---|
| PostgREST | Haskell | Auto-generates RESTful API from PostgreSQL schema |
| GoTrue | Go | Authentication: email, OAuth, magic link, phone |
| Realtime | Elixir/Phoenix | Broadcasts changes via WebSocket from PostgreSQL WAL |
| Storage | Node.js | File upload/download with RLS integration |
| Edge Functions | Deno | Serverless functions running at the edge, globally deployed |
| pg_graphql | Rust extension | Auto-generated GraphQL endpoint from PostgreSQL schema |
| pgvector | C extension | Vector embeddings for AI/ML search |
3. PostgreSQL — The Heart of Supabase
The biggest difference between Supabase and Firebase lies in the database layer. Supabase uses PostgreSQL with its full power:
- Row Level Security (RLS) — access control at the database layer, not the application layer
- Foreign keys, joins, transactions — ACID-compliant relational data
- Extensions: pgvector (AI embeddings), PostGIS (geospatial), pg_cron (scheduled jobs), pg_stat_statements (monitoring)
- Triggers and Functions — business logic running directly in the database
- Full-text search — fast search without needing Elasticsearch
3.1 Row Level Security — Security at the Deepest Layer
RLS is the key feature. Instead of writing permission checks in application code (which is error-prone), you define policies directly in PostgreSQL:
-- Only allow users to view their own data
CREATE POLICY "Users can view own data"
ON profiles FOR SELECT
USING (auth.uid() = user_id);
-- Users can only update their own profile
CREATE POLICY "Users can update own profile"
ON profiles FOR UPDATE
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);
-- Admins can view everything
CREATE POLICY "Admins can view all"
ON profiles FOR SELECT
USING (
EXISTS (
SELECT 1 FROM user_roles
WHERE user_roles.user_id = auth.uid()
AND user_roles.role = 'admin'
)
);
Why RLS Is Superior to Firestore Security Rules
Firestore Security Rules use a custom syntax that's hard to test, and only apply to Firestore. PostgreSQL RLS uses standard SQL, applies to every access method — REST API, GraphQL, direct connection, Edge Functions — ensuring no security gaps are missed.
4. Comprehensive Authentication with GoTrue
Supabase Auth (GoTrue) supports all major authentication methods:
- Email + Password with email confirmation
- Magic Link — passwordless login
- OAuth 2.0 — Google, GitHub, Apple, Discord, Twitter, Azure AD, 20+ providers
- Phone / SMS OTP
- SAML 2.0 SSO — for enterprise
- Anonymous sign-in — let users try the app before registering
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
// Sign up
const { data, error } = await supabase.auth.signUp({
email: 'user@example.com',
password: 'secure-password'
})
// OAuth login
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'google',
options: { redirectTo: 'https://myapp.com/callback' }
})
// Get current user
const { data: { user } } = await supabase.auth.getUser()
5. Realtime — Live Data Synchronization
Supabase's Realtime server (built with Elixir) listens to PostgreSQL WAL (Write-Ahead Log) and broadcasts changes via WebSocket to all subscribing clients.
sequenceDiagram
participant C1 as Client A
participant RT as Realtime Server
participant DB as PostgreSQL
participant C2 as Client B
C1->>RT: Subscribe channel "todos"
C2->>RT: Subscribe channel "todos"
C1->>DB: INSERT INTO todos (task)
DB->>RT: WAL change event
RT->>C1: Broadcast: new row
RT->>C2: Broadcast: new row
Realtime flow: PostgreSQL WAL → Elixir Server → WebSocket → Clients
Three types of Realtime events:
- Postgres Changes — listen to INSERT/UPDATE/DELETE on specific tables
- Broadcast — send arbitrary messages between clients (chat, cursors, notifications)
- Presence — track user online/offline status
// Subscribe to changes on "messages" table
const channel = supabase
.channel('room-1')
.on('postgres_changes',
{ event: 'INSERT', schema: 'public', table: 'messages' },
(payload) => {
console.log('New message:', payload.new)
}
)
.on('presence', { event: 'sync' }, () => {
const state = channel.presenceState()
console.log('Online users:', Object.keys(state).length)
})
.subscribe()
// Broadcast cursor position
channel.send({
type: 'broadcast',
event: 'cursor',
payload: { x: 100, y: 200, userId: 'abc' }
})
6. Edge Functions — Serverless on Deno Runtime
Edge Functions are serverless functions running on the Deno runtime, deployed globally close to users. They use TypeScript/JavaScript and support npm packages via the npm: specifier.
// supabase/functions/process-payment/index.ts
import { serve } from "https://deno.land/std/http/server.ts"
import { createClient } from "npm:@supabase/supabase-js@2"
import Stripe from "npm:stripe@14"
serve(async (req: Request) => {
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
)
const { orderId, amount } = await req.json()
// Verify order exists
const { data: order } = await supabase
.from('orders')
.select('*')
.eq('id', orderId)
.single()
if (!order) {
return new Response(JSON.stringify({ error: 'Order not found' }), {
status: 404
})
}
// Process payment
const stripe = new Stripe(Deno.env.get('STRIPE_KEY')!)
const payment = await stripe.paymentIntents.create({
amount: amount * 100,
currency: 'usd'
})
// Update order status
await supabase
.from('orders')
.update({ status: 'paid', payment_id: payment.id })
.eq('id', orderId)
return new Response(JSON.stringify({ success: true, payment_id: payment.id }))
})
Edge Functions Rate Limit (March 2026)
Supabase introduced rate limits for recursive/nested Edge Function calls: each request chain has a minimum budget of 5,000 requests/minute. Design functions to avoid deep fan-out patterns.
7. Storage — File Management with RLS
Supabase Storage manages files (images, videos, documents) with an S3-compatible backend. The unique aspect: Storage also uses RLS — you control who can upload/download which files using SQL policies.
// Upload file
const { data, error } = await supabase.storage
.from('avatars')
.upload(`${userId}/profile.jpg`, file, {
cacheControl: '3600',
upsert: true
})
// Create signed URL (expires in 1 hour)
const { data: { signedUrl } } = await supabase.storage
.from('documents')
.createSignedUrl('report.pdf', 3600)
// Public URL for static assets
const { data: { publicUrl } } = await supabase.storage
.from('public-images')
.getPublicUrl('banner.jpg')
8. Vector Embeddings — AI/ML Search with pgvector
With the pgvector extension, PostgreSQL becomes a vector database — storing and searching embeddings from OpenAI, Hugging Face, and Cohere right alongside your business data.
-- Create table for documents + embeddings
CREATE TABLE documents (
id BIGSERIAL PRIMARY KEY,
content TEXT,
embedding VECTOR(1536) -- OpenAI ada-002 dimension
);
-- Create index for fast similarity search
CREATE INDEX ON documents
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
-- Find 5 most similar documents
SELECT id, content,
1 - (embedding <=> '[0.1, 0.2, ...]'::vector) AS similarity
FROM documents
ORDER BY embedding <=> '[0.1, 0.2, ...]'::vector
LIMIT 5;
Advantage Over Dedicated Vector Databases
Using pgvector in Supabase, you don't need a separate service (Pinecone, Weaviate). Metadata filtering combined with SQL WHERE clauses is far more powerful than vector-only databases.
9. Supabase vs Firebase Comparison
| Criteria | Supabase | Firebase |
|---|---|---|
| Database | PostgreSQL (relational, ACID) | Firestore (NoSQL, document) |
| Query Language | Standard SQL | SDK-specific queries |
| Auth | GoTrue (20+ providers, SAML SSO) | Firebase Auth (comparable) |
| Realtime | WebSocket + PostgreSQL WAL | Firestore listeners |
| Storage | S3-compatible + RLS | Cloud Storage + Security Rules |
| Serverless | Edge Functions (Deno) | Cloud Functions (Node.js) |
| Vector Search | pgvector built-in | Requires additional service (Vertex AI) |
| Pricing Model | Resource-based (predictable) | Per-operation (unpredictable) |
| Vendor Lock-in | None — self-hostable | High — Google Cloud only |
| Offline Support | Limited | Strong (Firestore offline cache) |
| Open Source | Yes (Apache 2.0) | No |
| Cost at Scale | 30–50% cheaper | Higher due to per-read/write billing |
10. Pricing — Free Tier and Real-World Costs
| Plan | Database | Storage | Bandwidth | Price |
|---|---|---|---|---|
| Free | 500 MB (shared CPU) | 1 GB | 5 GB | $0 |
| Pro | 8 GB (dedicated CPU) | 100 GB | 250 GB | $25/month |
| Team | 8 GB + SOC2 | 100 GB | 250 GB | $599/month |
| Enterprise | Custom | Custom | Custom | Contact sales |
11. Integration with Vue.js — Practical Example
Supabase has an official SDK supporting all popular frameworks. Here's an example with Vue 3 Composition API:
// composables/useSupabase.ts
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
import.meta.env.VITE_SUPABASE_URL,
import.meta.env.VITE_SUPABASE_ANON_KEY
)
export function useTodos() {
const todos = ref([])
const loading = ref(true)
// Fetch initial data
async function fetchTodos() {
const { data } = await supabase
.from('todos')
.select('*')
.order('created_at', { ascending: false })
todos.value = data ?? []
loading.value = false
}
// Realtime subscription
const channel = supabase
.channel('todos-changes')
.on('postgres_changes',
{ event: '*', schema: 'public', table: 'todos' },
(payload) => {
if (payload.eventType === 'INSERT') {
todos.value.unshift(payload.new)
} else if (payload.eventType === 'DELETE') {
todos.value = todos.value.filter(t => t.id !== payload.old.id)
} else if (payload.eventType === 'UPDATE') {
const idx = todos.value.findIndex(t => t.id === payload.new.id)
if (idx !== -1) todos.value[idx] = payload.new
}
}
)
.subscribe()
onMounted(fetchTodos)
onUnmounted(() => supabase.removeChannel(channel))
return { todos, loading }
}
12. Self-Hosting — Full Control
Supabase can be fully self-hosted using Docker Compose. This is a major advantage for organizations with strict compliance requirements or those wanting to run on-premise:
# Clone and start self-hosted Supabase
git clone https://github.com/supabase/supabase
cd supabase/docker
cp .env.example .env
# Edit .env: JWT_SECRET, POSTGRES_PASSWORD, ...
docker compose up -d
The self-hosted stack includes: PostgreSQL, PostgREST, GoTrue, Realtime, Storage, Kong Gateway, Studio UI — all running in separate containers.
13. When to Choose Supabase?
Choose Supabase when:
- Your data has complex relationships (orders → items → products → categories)
- You need powerful SQL: joins, aggregations, window functions
- You want data ownership and the ability to migrate at any time
- You need integrated vector search for AI features
- You're budget-conscious: predictable pricing, generous free tier
- Compliance: need self-hosting or SOC2/HIPAA
Firebase may be a better fit when:
- Building a mobile-first app that needs strong offline-first sync
- Your data is document-shaped and doesn't need relations
- You're deeply invested in the Google Cloud ecosystem
- You need Firebase ML Kit, Remote Config, Analytics integration
14. Conclusion
Supabase has proven that backend-as-a-service doesn't have to trade convenience for control. With PostgreSQL as its foundation, RLS for security, Realtime for synchronization, Edge Functions for serverless, and pgvector for AI — Supabase is the default choice for most new projects in 2026.
Especially with its free tier of 500 MB database + 50K MAU + unlimited API requests, you can fully prototype and launch a product without spending a dime.
References
Disclaimer: The opinions expressed in this blog are solely my own and do not reflect the views or opinions of my employer or any affiliated organizations. The content provided is for informational and educational purposes only and should not be taken as professional advice. While I strive to provide accurate and up-to-date information, I make no warranties or guarantees about the completeness, reliability, or accuracy of the content. Readers are encouraged to verify the information and seek independent advice as needed. I disclaim any liability for decisions or actions taken based on the content of this blog.