Supabase's core pitch is deceptively simple — it's just Postgres, with a set of tools layered on top — and that simplicity is exactly why it's become a default choice for teams that want a managed backend without giving up SQL.
Supabase is a managed Postgres platform that adds an auto-generated REST and GraphQL API, realtime subscriptions over database changes, built-in authentication (Supabase Auth), file storage, and edge functions — all built directly on top of standard Postgres rather than a proprietary database engine. Because it's real Postgres underneath, you keep full SQL access, standard extensions, and the option to connect any Postgres-compatible tool directly, unlike more proprietary "backend as a service" platforms.
Why Supabase Matters (and When to Skip It)
Firebase popularized the "backend as a service" model, but its underlying database (Firestore) is a proprietary document store with its own query limitations. Supabase offers the same category of convenience — instant APIs, realtime, auth, storage — while keeping the database itself as standard, portable Postgres. If Supabase ever doesn't fit, migrating off is a Postgres migration, not a full data-model rewrite.
Skip Supabase if you need a database configuration or extension Supabase's managed platform doesn't support, or if your scale/compliance requirements call for a fully custom Postgres deployment with infrastructure control Supabase's managed model doesn't give you.
Getting Started with Supabase
The auto-generated REST API works immediately once tables exist — no backend code required for basic CRUD:
import { createClient } from "@supabase/supabase-js";
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
const { data: posts, error } = await supabase
.from("posts")
.select("id, title, author:users(name)")
.order("created_at", { ascending: false })
.limit(10);
That single call performs a joined query (author:users(name)) against real Postgres foreign key relationships, translated automatically into the appropriate SQL by Supabase's API layer (PostgREST).
Core Supabase Concepts Every Developer Should Know
Row Level Security is the actual authorization layer, not an optional add-on — since the auto-generated API exposes tables somewhat directly, RLS policies are what prevent one user's client from reading or writing another user's data:
alter table posts enable row level security;
create policy "Users can read all posts"
on posts for select using (true);
create policy "Users can only edit their own posts"
on posts for update using (auth.uid() = author_id);
Realtime subscriptions push database changes to clients over WebSockets, letting you build live-updating UIs without hand-rolling a WebSocket server:
supabase
.channel("posts-changes")
.on("postgres_changes", { event: "INSERT", schema: "public", table: "posts" }, (payload) => {
console.log("New post:", payload.new);
})
.subscribe();
Edge Functions handle logic that doesn't fit as a database query — webhook handlers, third-party API calls, anything needing server-side compute beyond what Postgres and RLS can express, deployed as Deno-based serverless functions colocated with your database region.
Database functions and triggers push logic into Postgres itself when that's the right layer for it — computing a derived field, enforcing a complex invariant, or syncing data between tables on insert, using standard PL/pgSQL rather than application-layer code.
Common Supabase Mistakes and How to Fix Them
Mistake 1: exposing tables via the auto-generated API without RLS enabled. This is the single most common Supabase security mistake — without RLS, any client with the anon key can read/write the entire table. Fix: enable RLS on every table and write explicit policies before exposing it through the client-facing API.
Mistake 2: doing everything through the client library, including logic that should be server-side. Complex business logic executed entirely in client-side Supabase calls is both harder to secure and harder to change safely. Fix: use Edge Functions or a thin backend layer for logic beyond simple authorized CRUD.
Mistake 3: not indexing columns used in RLS policies. A policy like using (auth.uid() = author_id) needs author_id indexed, or every query pays a full scan cost on top of the policy check. Fix: index any column referenced in an RLS policy's USING/WITH CHECK clause, same as any other frequently-filtered column.
When Should You Use Supabase Instead of a Self-Managed Postgres?
Use Supabase when you want a fast path to a full backend (auth, realtime, storage, API) without standing up separate services for each, while keeping the option to drop to raw SQL or migrate off later since it's standard Postgres. Use a self-managed Postgres deployment when you need infrastructure-level control Supabase's managed platform doesn't expose, or specific extensions/configurations outside its supported set.
Supabase Database in Production
Treat RLS policy review as part of your normal code review process, not a one-time setup step — new tables and new access patterns both need policies revisited. Also use the Supabase CLI to manage schema migrations as version-controlled SQL files rather than making ad-hoc changes through the dashboard, which keeps your schema history reviewable and reproducible across environments.
Before building custom auth, realtime, or file storage from scratch for a new Postgres-backed project, check whether Supabase already covers it — the "just Postgres underneath" design means you're not locking yourself in even if you start there.