✒️ Becky Isjwara

Database · Guide

Supabase — the backend you don't have to build

Auth, Postgres, storage, and a generous free tier. My default backend.

Difficulty

Medium

Setup time

45 min

Cost

Free tier is generous

Why Supabase?

Supabase is a Postgres database with auth, storage, and real-time sockets layered on top. It's open source, the free tier is generous, and the client libraries are great. If you're not sure what backend to pick, start here.

The alternative is Firebase, but Firebase's NoSQL model is a trap for beginners — you can't easily change your schema later. Supabase uses real Postgres. You can always pick it up and move it elsewhere.

What you'll use it for

  • Auth — magic links, email/password, Google/GitHub OAuth
  • Database — Postgres, any shape of data
  • Storage — file uploads (images, PDFs, whatever)
  • Realtime — websocket updates for collaborative features

Setup

  1. Sign up at supabase.com
  2. Create a new project. Pick a region close to your users.
  3. Wait ~2 min for it to spin up
  4. Go to Settings → API and copy:
    • Project URL
    • anon public key (for client-side)
    • service_role key (for server-side — NEVER expose this)

Add to your .env.local:

NEXT_PUBLIC_SUPABASE_URL=https://xxxxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...
SUPABASE_SERVICE_ROLE_KEY=eyJ...
CriticalThe service_role key bypasses all security rules. Never put it in client code, never commit it to git. Use it only in server-side code (API routes, server components).

Client libraries

npm install @supabase/supabase-js @supabase/ssr

The @supabase/ssr package is essential for Next.js App Router — it handles auth cookies correctly between server and client.

Auth — magic links

Supabase sends magic-link emails via Resend under the hood. In Authentication → Email Templates, customize the email to match your brand.

const { error } = await supabase.auth.signInWithOtp({
  email: 'user@example.com',
  options: {
    emailRedirectTo: `${window.location.origin}/auth/callback`,
  },
});

Database — your first table

In the Supabase dashboard, Table Editor → New Table. Give it a name (say notes), add columns. Or use SQL if you prefer:

create table notes (
  id uuid primary key default gen_random_uuid(),
  user_id uuid references auth.users on delete cascade,
  title text not null,
  body text,
  created_at timestamptz default now()
);

Row-Level Security (RLS)

This is the one thing you must understand. By default, your tables are locked down — nobody can read or write. You enable RLS and add policies that say "users can only see their own rows":

alter table notes enable row level security;

create policy "Users see own notes"
  on notes for select
  using (auth.uid() = user_id);

create policy "Users insert own notes"
  on notes for insert
  with check (auth.uid() = user_id);

Without policies, even authenticated requests return nothing. First thing to check when your queries return empty: RLS policies.

Querying from Next.js

import { createBrowserClient } from '@supabase/ssr';

const supabase = createBrowserClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);

const { data, error } = await supabase
  .from('notes')
  .select('*')
  .order('created_at', { ascending: false });

Storage for file uploads

Create a bucket in Storage → New Bucket. Public buckets serve files at a URL; private buckets require signed URLs. Upload:

const { data, error } = await supabase.storage
  .from('avatars')
  .upload(`${user.id}/avatar.png`, file);

Pricing

  • Free: 500MB database, 1GB storage, 50k monthly active users. Pauses after 1 week of inactivity (annoying for dev projects — just log in weekly).
  • Pro ($25/mo): 8GB database, 100GB storage, no pausing, daily backups.

Common gotchas

  • RLS is silent when it blocks you. Queries return empty arrays, not errors. Always check your policies first.
  • Email confirmation is on by default. In Authentication → Providers → Email, toggle it off during development.
  • Dev project pauses after 7 days. Free tier only. Just log into the dashboard to wake it up.
  • Don't use the anon key on the server. Use createServerClient with cookies, otherwise you lose the logged-in user context.

Related

For auth emails, configure Resend as your SMTP provider in Supabase (much better deliverability than their default). For payments that depend on who's logged in, see the Stripe guide.

Rather skip the DIY?

I'll set this up for you in an afternoon.