Supabase is the open-source Firebase alternative that gives you a PostgreSQL database, authentication, real-time subscriptions, edge functions, and storage โ all in one platform. In 2026, Supabase powers startups and enterprises alike with its developer-friendly DX and powerful PostgreSQL foundation. This guide covers everything from project setup to production.
๐ Table of Contents
Supabase vs Firebase: Which Should You Pick?
This is the comparison most people are actually making, so it is worth settling before the tutorial starts.
| Supabase | Firebase | |
|---|---|---|
| Database | PostgreSQL — relational, SQL | Firestore — NoSQL documents |
| Queries | Full SQL, joins, views, triggers | Limited; no joins |
| Self-hosting | Yes, fully open source | No |
| Vendor lock-in | Low — it is a Postgres database you can export | High |
| Real-time | Postgres replication | Native, more mature |
| Offline sync | Not built in | Strong — a genuine advantage |
| Vector search | Built in via pgvector | Requires a separate service |
| Free tier | 500 MB database, 2 projects | Generous reads/writes |
Pick Supabase if your data has relationships, you want to write SQL, or you care about being able to leave. Pick Firebase if you need offline-first mobile sync, which is the one thing Supabase genuinely does not do well.
The deeper argument for Supabase is that it is Postgres. If the project outgrows the platform, you take a standard database with you. Leaving Firestore means rewriting your data layer.
What Supabase Provides
- PostgreSQL database โ full Postgres with direct SQL access
- Auto-generated REST API โ PostgREST turns your tables into REST endpoints
- Real-time subscriptions โ listen to table changes via WebSocket
- Authentication โ email, OAuth (Google, GitHub, Apple), phone OTP
- Storage โ file uploads with CDN and row-level access policies
- Edge Functions โ Deno-based serverless functions at the edge
- Vector search โ pgvector extension for AI embeddings
Setup
npm install @supabase/supabase-js
# Or create project at supabase.com (free tier: 500MB DB, 1GB storage)
# Environment variables
SUPABASE_URL=https://xyzcompany.supabase.co
SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIs... # public, safe for frontend
SUPABASE_SERVICE_ROLE_KEY=eyJ... # private, server only!
Database Operations
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_ANON_KEY!
);
// SELECT
const { data: users, error } = await supabase
.from('users')
.select('id, name, email, created_at')
.eq('active', true)
.order('created_at', { ascending: false })
.limit(10);
// SELECT with relations (foreign table)
const { data: posts } = await supabase
.from('posts')
.select(`
id,
title,
content,
users(name, email),
categories(name)
`)
.eq('published', true);
// INSERT
const { data, error } = await supabase
.from('posts')
.insert({
title: 'My Post',
content: 'Content here',
author_id: userId,
published: false,
})
.select()
.single();
// UPDATE
const { data: updated } = await supabase
.from('posts')
.update({ published: true })
.eq('id', postId)
.select()
.single();
// DELETE (soft delete via RLS is better)
await supabase.from('posts').delete().eq('id', postId);
// UPSERT
await supabase
.from('user_settings')
.upsert({ user_id: userId, theme: 'dark', language: 'en' })
.onConflict('user_id');
Row Level Security (RLS)
-- Enable RLS
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
-- Users can read all published posts
CREATE POLICY "Anyone can read published posts"
ON posts FOR SELECT
USING (published = true);
-- Users can only see their own draft posts
CREATE POLICY "Users see own drafts"
ON posts FOR SELECT
USING (author_id = auth.uid() AND published = false);
-- Users can only insert/update their own posts
CREATE POLICY "Users manage own posts"
ON posts FOR ALL
USING (author_id = auth.uid());
-- Admin bypass (service role key bypasses RLS)
-- Use service role key in server-side code for admin operations
Authentication
// Sign up
const { data, error } = await supabase.auth.signUp({
email: 'alice@example.com',
password: 'securepassword',
options: {
data: { full_name: 'Alice Chen' } // stored in user metadata
}
});
// Sign in
const { data: session } = await supabase.auth.signInWithPassword({
email: 'alice@example.com',
password: 'securepassword'
});
// OAuth (Google, GitHub, etc.)
await supabase.auth.signInWithOAuth({
provider: 'github',
options: {
redirectTo: `${window.location.origin}/auth/callback`
}
});
// Get current user
const { data: { user } } = await supabase.auth.getUser();
// Listen to auth state changes
supabase.auth.onAuthStateChange((event, session) => {
if (event === 'SIGNED_IN') setUser(session?.user ?? null);
if (event === 'SIGNED_OUT') setUser(null);
});
// Sign out
await supabase.auth.signOut();
// Server-side auth (Next.js)
import { createServerClient } from '@supabase/ssr';
import { cookies } from 'next/headers';
export function createServerSupabase() {
const cookieStore = cookies();
return createServerClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_ANON_KEY!,
{ cookies: { get: (name) => cookieStore.get(name)?.value } }
);
}
Real-Time Subscriptions
// Subscribe to table changes
const subscription = supabase
.channel('posts-changes')
.on(
'postgres_changes',
{ event: '*', schema: 'public', table: 'posts' },
(payload) => {
console.log('Change received:', payload.eventType, payload.new);
if (payload.eventType === 'INSERT') addPost(payload.new);
if (payload.eventType === 'UPDATE') updatePost(payload.new);
if (payload.eventType === 'DELETE') removePost(payload.old);
}
)
.subscribe();
// Cleanup
subscription.unsubscribe();
// Broadcast (custom events between clients)
const channel = supabase.channel('room-1');
channel
.on('broadcast', { event: 'cursor-pos' }, ({ payload }) => {
updateCursor(payload.userId, payload.x, payload.y);
})
.subscribe();
// Send broadcast
await channel.send({
type: 'broadcast',
event: 'cursor-pos',
payload: { userId, x: mouseX, y: mouseY }
});
Edge Functions
// supabase/functions/send-email/index.ts
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';
serve(async (req: Request) => {
const { userId, subject, body } = await req.json();
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')! // admin access
);
const { data: user } = await supabase
.from('users')
.select('email')
.eq('id', userId)
.single();
// Send email via Resend
await fetch('https://api.resend.com/emails', {
method: 'POST',
headers: {
Authorization: `Bearer ${Deno.env.get('RESEND_API_KEY')}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
from: 'app@myapp.com',
to: user.email,
subject,
html: body,
}),
});
return new Response(JSON.stringify({ success: true }), {
headers: { 'Content-Type': 'application/json' },
});
});
Vector Search (AI/Semantic)
-- Enable pgvector
CREATE EXTENSION IF NOT EXISTS vector;
-- Add embedding column
ALTER TABLE articles ADD COLUMN embedding vector(384);
-- Create vector index
CREATE INDEX ON articles USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
-- Semantic search function
CREATE OR REPLACE FUNCTION semantic_search(query_embedding vector(384), match_count int = 10)
RETURNS TABLE(id bigint, title text, similarity float)
LANGUAGE plpgsql AS $$
BEGIN
RETURN QUERY
SELECT
articles.id,
articles.title,
1 - (articles.embedding <=> query_embedding) AS similarity
FROM articles
WHERE articles.embedding IS NOT NULL
ORDER BY articles.embedding <=> query_embedding
LIMIT match_count;
END;
$$;
// Call semantic search
const { data: results } = await supabase.rpc('semantic_search', {
query_embedding: await generateEmbedding(searchQuery),
match_count: 10
});
Supabase in 2026 is the fastest way to build a production backend. You get PostgreSQL power, real-time subscriptions, auth, and edge functions with a developer experience better than Firebase โ and the data is actually yours. Start with the free tier (unlimited API calls, 500MB database), migrate to Pro ($25/month) when you need more resources.
Frequently Asked Questions
Is Supabase free?
There is a free tier covering two projects with 500 MB of database storage each, which is enough for a side project or a prototype. Free projects pause after a week of inactivity and resume when you open them. Paid plans start around $25 per month per project.
Is Supabase production-ready?
Yes. The underlying database is PostgreSQL, which has been production-grade for decades. The pieces built around it — auth, storage, edge functions — are younger, so read the changelog before depending on a recently added feature.
Do I still need a backend with Supabase?
Often not. Row Level Security lets you enforce access rules in the database itself, so the client can query it directly and safely. You still want a server for anything involving a secret — payment webhooks, third-party API keys, work that must not be tampered with — and edge functions cover most of those cases.
What is Row Level Security and do I have to use it?
RLS is a Postgres feature that filters rows per user according to policies you define. With Supabase you should treat it as mandatory: the anon key is public and ships in your client, so without RLS your tables are readable by anyone who opens developer tools. Enable it on every table before launch.
Can I migrate an existing PostgreSQL database to Supabase?
Yes — it is standard Postgres, so a pg_dump and restore works. Afterwards you need to enable RLS and write policies, because a database imported from elsewhere will not have them.
How does Supabase handle vector search for AI apps?
Through the pgvector extension, so embeddings live in the same database as the rest of your data and can be filtered with ordinary SQL in the same query. That removes the usual need to run a separate vector database alongside your primary one.
What are the main limitations?
No built-in offline sync, a smaller ecosystem than Firebase, and free projects pausing when idle. Connection limits also matter on smaller plans — use the pooler for serverless deployments or you will exhaust them.
๐ You might also like
๐ Share this article




โ๏ธ Leave a Comment