Supabase
Supabase is an open-source backend-as-a-service (BaaS) built on top of PostgreSQL. The idea is that instead of building your own backend infrastructure — database, authentication, file storage, real-time subscriptions — Supabase gives you all of that out of the box, wired together and ready to use via a simple client library.
It's often described as the open-source alternative to Firebase, but unlike Firebase (which uses a NoSQL document store), Supabase is backed by a real relational database. You get the full power of PostgreSQL: SQL queries, joins, stored functions, indexes, and RLS.
What Supabase provides
- Database: a managed PostgreSQL instance, accessible via SQL or auto-generated REST/GraphQL APIs
- Authentication: email/password, OAuth providers, magic links; sessions managed with JWTs stored as cookies
- Row Level Security (RLS): attach access policies directly to database tables so the database enforces who can see what, not just the app layer (see Row Level Security)
- Real-time: subscribe to database changes over WebSocket — useful for live dashboards or chat
- Storage: file upload and management
- Edge Functions: serverless functions that run close to the user
Two access levels
Every Supabase project comes with two keys:
| Key | Name | Scope |
|---|---|---|
anon |
Public key | Subject to RLS policies |
service_role |
Secret key | Bypasses RLS entirely |
The anon key is safe to expose in frontend code (it's prefixed NEXT_PUBLIC_ in Next.js projects). The service role key must stay server-side only. If RLS is not configured on a table, anyone with the anon key can query that table directly — which is a common misconfiguration trap.
RPC: calling database functions
Supabase exposes a way to call PostgreSQL stored functions remotely via .rpc(). This is called RPC (Remote Procedure Call): you write logic as a function in the database, then invoke it like an API endpoint from your app.
const { data, error } = await supabase.rpc('create_booking_atomic', {
p_venue_id: venueId,
p_date: date,
// ...other params
})
RPC is useful when you need to run logic that must be atomic at the database level — for example, checking for a conflict and inserting a row in a single transaction. Doing this in application code with two separate queries creates a race condition; doing it in a stored function and calling it via RPC keeps everything inside one database transaction.
Typical client setup (Next.js)
In a Next.js app, Supabase recommends using @supabase/ssr which handles cookie-based session management:
// lib/supabase/server.ts — anon client for guest/public operations
import { createServerClient } from '@supabase/ssr'
export function createClient() {
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{ cookies: { ... } }
)
}
A second client using the service role key bypasses RLS and is used for admin operations. That key must never appear in NEXT_PUBLIC_ variables.
Related notes
- Row Level Security — how table-level access policies work
- PostgreSQL — the database Supabase is built on