I have used Firebase, MongoDB Atlas, PlanetScale, Neon, RDS, raw self-hosted Postgres on a Hetzner box. They are all fine. Supabase keeps winning, project after project, because it removes a specific set of frictions that I used to spend hours on every single time.
It is Postgres, not a Postgres-shaped abstraction.
You get real Postgres. Not a Postgres-flavoured query syntax over a different storage engine. Real psql. Real schemas. Real CTEs, real partial indexes, real materialised views, real triggers, real CHECK constraints. Whatever you can do in vanilla Postgres, you can do in Supabase.
This matters because Postgres is a forty-year-old, deeply mature system that survives every fashion cycle. SQL skills compound. Postgres skills compound. Whatever you learn solving a problem in Supabase is portable to any Postgres deployment, anywhere, forever.
Row-level security is the unfair advantage.
RLS is a feature of Postgres itself, but Supabase makes it the default authorisation model. Every table policy you write is a permanent, declarative, database-enforced statement of who can see and change what. It is not a middleware function that can be bypassed by a buggy route handler. It is a property of the data.
Once you have built a system with RLS, application-layer permission checking feels reckless. The permissions follow the data wherever it is queried — server actions, edge functions, server components, anywhere. Auditing access becomes a SQL query against the policies. New developers can read the policies and immediately understand the access model.
This is the single biggest productivity multiplier in the Supabase stack. Other databases technically support RLS; almost no other platform makes it the obvious thing to reach for.
“Once you have built a system with RLS, application-layer permission checking feels reckless.”
Auth, storage, realtime, all in the same project.
You get auth (JWT-based, integrated with RLS), object storage (S3-compatible API, integrated with RLS), realtime channels (Postgres replication, integrated with RLS), and edge functions (Deno-based, integrated with auth). One project, one dashboard, one set of credentials, one ecosystem.
Each of these used to be a separate vendor relationship. Cognito or Auth0 for auth. S3 for storage. Pusher or Ably for realtime. Lambda for functions. Each one had its own SDK, its own pricing model, its own outage page. Supabase folds them into one mental model and one bill.
- →Postgres — real Postgres, no abstraction tax
- →Auth — JWT, OAuth, magic links, integrated with RLS
- →Storage — S3-compatible, with RLS over buckets and objects
- →Realtime — Postgres replication, no separate WebSocket service
- →Edge functions — Deno runtime, geographically distributed
Where it is rough, and how I work around it.
It is not all roses. The migration tooling — Supabase CLI plus SQL files — is functional but not delightful. I generally end up writing my own thin wrapper for repeatable seed data, RLS policy templates, and per-environment overrides.
The realtime layer is excellent for small-scale collaboration but I would not put a 100,000-concurrent-connection workload on it without testing carefully. For most internal platforms it is more than enough. For consumer-scale realtime, you may want a dedicated tool.
The dashboard, while useful, is not a substitute for psql. I keep a local psql connection open during any non-trivial work. The dashboard is for the 80%; psql is for the 20%.
Why not just self-host Postgres?
I have done this. Many times. It is a perfectly reasonable choice if you have someone whose job involves thinking about backups, replication, point-in-time recovery, version upgrades, security patches, and connection pooling.
For a solo engineer or a small team, self-hosting Postgres at production quality is an ongoing tax that pulls attention away from the work that actually matters. Supabase handles all of that operational layer for a fee that is, frankly, lower than what you would spend on time alone.
The day my workload outgrows Supabase, I can run pg_dump, restore on any Postgres instance anywhere, and continue. The data is mine. The schema is portable. The policies are portable. The lock-in is real but bounded — it is on the operational layer, not on the data layer.
“The lock-in is real but bounded. It is on the operational layer, not on the data layer.”