Add a database and user logins with Supabase
Set up Supabase for your web app: create the project, understand your two keys, design tables, turn on Row Level Security, add logins and let Claude Code write the code safely.
Most apps need two things that used to take a back-end developer: somewhere to store data, and a way for people to sign up and log in. Supabase gives you both, with a real Postgres database, authentication, file storage and server functions, from one dashboard. It has a free tier to start on, and AI coding tools know it well.
Create the project
Sign up at supabase.com and create a new project. Choose the region closest to your users (there's one in Sydney) and save the database password somewhere safe, like a password manager. You'll rarely need it, but it's painful to lose.
From the project's settings, note three things: the project URL, the publishable key and the secret key. (Older projects call these the anon key and the service_role key.)
Understand your two keys
This is the most important idea in this article:
- The publishable (anon) key is designed to be used in the browser. It's safe there only because Row Level Security (below) decides what each user can read and write.
- The secret (service_role) key bypasses all of that. It can read and change everything. It must only ever live on a server: never in front-end code, never in GitHub, never pasted into a chat.
Keep both in a .env file, make sure .env is listed in .gitignore, and set them as environment variables on your host when you deploy.
Design your tables in plain English
Before any SQL, describe your data the way you'd explain it to a person: "Each user has projects. A project has a name and belongs to one user. Each project has many tasks with a title, a due date and a done flag." Then hand that to Claude Code:
Create a Supabase migration for this data model: [your description].
Use uuid primary keys and created_at timestamps.
Enable Row Level Security on every table, with policies so users
can only read and change their own rows. Explain each policy.
You'll get something like this for each table:
create table public.projects (
id uuid primary key default gen_random_uuid(),
owner_id uuid not null references auth.users (id) on delete cascade,
name text not null,
created_at timestamptz not null default now()
);
alter table public.projects enable row level security;
create policy "Owners can read their projects"
on public.projects for select
using ((select auth.uid()) = owner_id);
create policy "Owners can add projects"
on public.projects for insert
with check ((select auth.uid()) = owner_id);
Read it, ask about anything you don't understand, and only then run it.
Turn on Row Level Security everywhere
Row Level Security (RLS) is a set of rules inside the database that decide which rows each logged-in user can see and change. With RLS on and no policies, nobody can read the table through the public API. With good policies, each user sees exactly their own data. With RLS off, anyone holding your publishable key — which is everyone who visits your site — can read the whole table.
Check every table has RLS enabled, and test it properly: create two accounts, add data as one, and confirm the other can't see it. The dashboard's security advisor also flags tables without RLS.
Add sign-up and login
Supabase Auth handles email and password, magic links and social logins such as Google. Two settings catch almost everyone out:
- URL configuration. Set your Site URL and the allowed redirect URLs for both your local address and your live domain, or login links will send people to the wrong place.
- Email sending. The built-in email service is meant for testing and is rate limited. Before launch, connect your own email provider (custom SMTP) so sign-up and reset emails arrive reliably.
Connect from your code
Install the client library with npm install @supabase/supabase-js, then create one shared client using the publishable key from your environment variables:
import { createClient } from '@supabase/supabase-js';
export const supabase = createClient(
import.meta.env.VITE_SUPABASE_URL,
import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY
);
The variable prefix depends on your framework: Vite uses VITE_, Next.js uses NEXT_PUBLIC_. Only ever give the browser the publishable key.
Keep changes in migrations
It's tempting to click around in the dashboard's table editor. Instead, keep every change to your database as a migration file in your project, using the Supabase CLI. Then your database structure is versioned in GitHub alongside your code, and you can rebuild it, review it and roll it forward safely. Ask Claude Code to write each change as a new migration rather than editing old ones.
You can also connect Claude Code to Supabase directly through Supabase's MCP server, so it can inspect your tables and logs. If you do, start with a development project, not the one with real customers in it.
Before you launch
- RLS is on for every table, tested with two accounts.
- The secret key appears nowhere in front-end code or Git history.
- Auth redirect URLs include your live domain.
- Custom SMTP is set up for auth emails.
- You know your plan's backup policy. Free projects also pause after a period of inactivity, so check the plan you need for a live product.
Next: take payments with Stripe and record who has paid in the tables you've just built.