Supabase
Supabase
Supabase emerged as an alternative to Firebase, an open-source backend bundle. PostgreSQL sits at the core, with auth, storage, realtime, and serverless functions stacked around it.
1. About Supabase
Supabase started in 2020. The founders (Paul Copplestone and Ant Wilson) have stated the direction "put a Firebase-like developer experience on top of PostgreSQL." The code is published under Apache 2.0, and both managed cloud and self-hosting are supported.
| Event | Year |
|---|---|
| Project starts | 2020 |
| GA | 2021 |
| Storage added | 2021 |
| Edge Functions (Deno) | 2022 |
| pgvector default support | 2023 |
| Branching, Realtime improvements | 2023 ~ 2024 |
2. Database (PostgreSQL)
The core itself is PostgreSQL. Users can connect with regular PostgreSQL clients (psql, JDBC, ORM). Extensions are richly enabled, and pgvector, pg_cron, postgis and similar extensions can be activated from the console.
Beyond public, system schemas like auth, storage, and realtime live in the same DB. As a result, RLS policies can read these system tables alongside user tables.
3. Auth (GoTrue)
GoTrue is an open-source auth server originally started by Netlify. Supabase forked and continues developing it. It supports email/password, magic link, OTP, OAuth, and phone authentication. Issued JWTs carry the user ID (sub) of auth.users, and RLS policies use that information.
4. Storage, Edge Functions, Realtime
Storage — implements an S3-compatible object store. The backend can also bind to other object stores via the S3 API. Metadata is stored in PostgreSQL's storage.objects table, with permissions handled by RLS.
Edge Functions — serverless functions running on the Deno runtime. Deploying TypeScript code lets it respond at the global edge. Database connections reach the same project's PostgreSQL.
Realtime — subscribes to PostgreSQL's logical replication and streams change events over WebSocket. Clients receive changes on specific tables and filters in real time. Broadcast and presence channels are also offered separately.
5. supabase-js client
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(URL, ANON_KEY);
// read
const { data, error } = await supabase
.from('posts')
.select('id, title, author:users(name)')
.eq('published', true)
.order('created_at', { ascending: false })
.limit(10);
// write
await supabase.from('posts').insert({ title: 'hi' });
// auth
await supabase.auth.signInWithPassword({ email, password });
// realtime
supabase.channel('posts')
.on('postgres_changes', { event: '*', schema: 'public', table: 'posts' }, (p) => console.log(p))
.subscribe();
Characteristics.
- Queries are translated by PostgREST (RESTful relational SQL mapping).
- There are
anonkeys andservice_rolekeys. The latter must be used only on servers. - Responses come as
{ data, error }, so handle with branching, not throws.
6. RLS — Row Level Security
PostgreSQL has provided row-level security since 9.5 (2016). Supabase stacks its own model on top.
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
CREATE POLICY "own posts read" ON posts
FOR SELECT
USING (author_id = auth.uid());
CREATE POLICY "insert own" ON posts
FOR INSERT
WITH CHECK (author_id = auth.uid());
- The
auth.uid()function returns the user ID from the current JWT. - Policies split into
USING(matching for read and UPDATE) andWITH CHECK(post-shape for INSERT and UPDATE). - The
service_rolekey bypasses RLS — backend only.
The advantage is that permission rules live inside the DB. The same rules apply regardless of which client connects. The limit is that as policies grow, debugging gets harder, and complex joins can raise policy evaluation cost.
7. Self-hosting
All Supabase components are published as containers deployable via docker compose. The following containers come up together on one host.
db (Postgres), gotrue, storage-api, realtime
kong (gateway), studio (UI), meta, imgproxy
Trade-offs of self-hosting:
- Pros: data sovereignty, cost control, integration with private networks.
- Limits: backups, scaling, and upgrades become the operator's responsibility.
The choice between managed and self-hosted depends on data volume, sensitivity, and operational headcount.
8. Comparison with Firebase, Appwrite, PocketBase
| Item | Supabase | Firebase | Appwrite | PocketBase |
|---|---|---|---|---|
| Core DB | PostgreSQL | Firestore (NoSQL), RTDB | MariaDB | SQLite (single binary) |
| License | Apache 2.0 | proprietary | BSD-3 | MIT |
| Self-hosting | possible | not available | possible | possible (single executable) |
| Auth | GoTrue | Firebase Auth | own | own |
| Realtime | logical replication | own (Firestore listeners) | own | own |
| Functions | Deno | Cloud Functions (Node) | Functions (multi-language) | (via Go middleware) |
| SDK | js, py, dart, swift | multi-language, multi-platform | js, flutter, py, etc. | js, dart |
Firebase's strength is the maturity of mobile SDKs and Google Cloud integration. Supabase's strength is SQL, the relational model, and data portability. PocketBase comes up often for small projects thanks to its very small operational surface (single binary, SQLite). Appwrite offers a Firebase-like API model in open source.
9. Key separation and migration
The anon key is for public clients. RLS sets the permission rules. The service_role key is server-only and bypasses RLS.
Environment variable separation and CI checks are recommended so that service_role does not end up in built client bundles.
The supabase CLI handles SQL migration files and a local development environment (docker compose based). Commands like supabase db push and supabase db diff apply and inspect schema changes.
10. Common pitfalls
Tables without RLS — when a newly created table has RLS off, the anon key can read every row. The default being disabled is often pointed out.
service_role key exposure — once it lands in a client bundle, all data access becomes possible.
Function call cost in policies — calling heavy functions other than auth.uid() inside a policy evaluates them per row.
Free tier pause — managed free projects may pause after a period of inactivity (plans and pricing vary by period).
Storage RLS and metadata — object permissions follow policies on the metadata table. Missing policies cause unintended access.
Mixing migration tools — schema changes from Prisma or TypeORM mixed with the supabase CLI on the same DB scatter tracking. Pick one as SSOT.
Closing thoughts
Supabase is a good tool for small teams to do a lot quickly. As ops scale grows, the operational burden of self-hosting becomes visible, and managed costs become visible. That is when the choice between going with PostgreSQL alone or keeping the Supabase full stack arrives.
Next
- fcm-push
- image-pipeline
References: Supabase official docs, Supabase architecture, Postgres RLS docs, PostgREST docs, Supabase self-hosting, PocketBase official.