When developers first discover Supabase, it is usually pitched as an “open-source Firebase alternative.” But from an architectural standpoint, that comparison completely misses what makes the system unique.
Firebase relies on a proprietary NoSQL document store (Firestore) that forces developers to construct application logic around limited querying models, document denormalization, and vendor lock-in.
Supabase takes the exact opposite approach. Instead of building a custom database engine or obscuring the database behind a heavy abstraction layer, Supabase is simply PostgreSQL—wrapped by a modular suite of open-source microservices.
Every Supabase project is a dedicated, fully-featured Postgres database. The platform’s primary trick is turning native Postgres features (like Row Level Security, Write-Ahead Logs, schema catalogs, and extensions) into auto-generated APIs, real-time WebSocket streams, and unified identity systems.
The Supabase System Architecture
Supabase is not a single monolithic server. It is an orchestrated stack of independent open-source tools managed behind an API Gateway (Kong).
How Supabase Connects to Postgres: The Core Components
To understand how Supabase turns a standard database into an end-to-end Backend-as-a-Service (BaaS), you have to trace how each underlying component interacts with Postgres.
1. The REST Layer: PostgREST
Instead of requiring developers to write CRUD endpoints manually (Express, Django, or FastAPI), Supabase uses PostgREST—a lightweight, stateless web server written in Haskell.
- How it works: PostgREST inspects the Postgres
information_schemacatalog at startup. It dynamically generates RESTful endpoints for every table, view, and stored function in yourpublicschema. - Why it’s fast: PostgREST translates incoming HTTP requests directly into single, highly optimized SQL queries (
json_build_object/json_agg). The database executes the query and returns pre-formatted JSON directly to the client, avoiding object-relational mapping (ORM) overhead entirely.
2. The GraphQL Engine: pg_graphql
For developers who prefer GraphQL over REST, Supabase does not run an external GraphQL server. Instead, it uses `pg_graphql`, a native Postgres extension written in Rust.
- How it works: The extension generates a GraphQL schema directly inside Postgres. When a GraphQL query hits the system, it executes a SQL function—
graphql.resolve(...)—which parses the GraphQL AST and translates it into a single SQL statement natively inside the database query engine.
3. Authentication & JWTs: GoTrue
Supabase Auth is handled by GoTrue, a Go-based microservice for managing user signups, OAuth providers, MFA, and magic links.
- How it connects to Postgres: GoTrue doesn’t store user credentials in a separate user database. It writes user records directly into a dedicated database schema inside your Postgres instance called
auth.users. - The JWT Handshake: When a user logs in, GoTrue issues a signed JSON Web Token (JWT) containing the user’s
UUIDandrole(e.g.,authenticatedoranon). This JWT is attached to every subsequent request header forwarded to PostgREST.
{ "sub": "a1b2c3d4-e5f6-7890-1234-56789abcdef0", "role": "authenticated", "email": "developer@zyloth.com", "exp": 1721520000}Authorization at the Core: Row Level Security (RLS)
In a traditional backend, security is enforced in application code. If a developer forgets an authorization check in a custom endpoint, data leaks. Supabase completely flips this model. Because client applications query PostgREST directly, authorization is pushed entirely into Postgres using native Row Level Security (RLS).
How RLS Works Under the Hood
When PostgREST handles a request with a user’s JWT:
- It opens a transaction inside Postgres.
- It sets local configuration variables for that transaction using
SET LOCAL request.jwt.claims. - Postgres evaluates the table’s SQL RLS Policy before returning or modifying any row.
-- Example: Allow users to read only their own profileCREATE POLICY "Users can view own profile"ON public.profilesFOR SELECTUSING ( (SELECT auth.uid()) = user_id );The function auth.uid() extracts the sub claim directly from the current transaction’s JWT claims. If the condition evaluates to false, Postgres omits the row from the result set. The API server doesn’t filter the data—the database query execution engine does.
Performance Optimization Note: A common pitfall in naive RLS policies is callingauth.uid() = user_iddirectly, which re-evaluates the function for every single row scanned. Wrapping the function in a subquery—(SELECT auth.uid()) = user_id—allows Postgres to cache the scalar result once per query, preventing massive execution slowdowns on large tables.
Realtime Streaming: Tapping into the Postgres WAL
How does Supabase broadcast live database changes (INSERTs, UPDATEs, DELETEs) over WebSockets without requiring custom trigger scripts on every table? It uses Postgres Logical Replication.
- Write-Ahead Log (WAL): Whenever data changes in Postgres, the transaction is first written sequentially to the WAL file on disk for durability.
- Logical Decoding: Supabase configures a logical replication slot on the database. The Realtime Engine—an Elixir server running on the Phoenix framework—connects to this replication slot.
- WAL to JSON: Realtime decodes binary WAL changes into structured JSON events using logical decoding plugins.
- Broadcast & Authorization: Realtime checks if the user listening on the WebSocket channel has permission to see the changed row (using a system called WALRUS to evaluate RLS against the streamed payload) and broadcasts the event over WebSockets to subscribed clients.
Connection Pooling: Supavisor & PgBouncer
Because PostgREST, client SDKs, Edge Functions, and serverless environments (Vercel, AWS Lambda) create bursty, short-lived connections, they can quickly exhaust PostgreSQL’s connection limits (where each connection spawns a dedicated OS process).
To prevent connection exhaustion, Supabase sits behind a connection pooler:
- Supavisor: A cloud-native, multi-tenant Postgres connection pooler written in Elixir by Supabase, capable of managing millions of incoming client connections and proxying them down to a small, sustainable pool of physical Postgres connections.
- PgBouncer: Used for co-located, single-tenant transaction pooling.
Summary: Component Breakdown
| Component | Tech Stack | Role in Supabase Architecture |
|---|---|---|
| PostgreSQL | C | Central engine; stores data, enforces RLS, executes logic |
| PostgREST | Haskell | Auto-generates RESTful API directly from Postgres schema |
| `pg_graphql` | Rust (PG Extension) | Auto-generates GraphQL API natively inside Postgres |
| GoTrue | Go | Handles auth, OAuth providers, and issues signed JWTs |
| Realtime | Elixir (Phoenix) | Streams database changes from the WAL over WebSockets |
| Supavisor | Elixir | High-performance connection pooler for serverless workloads |
| Kong | Lua / NGINX | API Gateway that unifies routing, SSL, and auth headers |