Objective
Your mission
Create a Supabase project, create a table, connect your app, and test all 4 CRUD operations with error checking. Output: supabase_setup.md.
You need: A project ready for a real database. Supabase account (free tier).
Time: 30 minutes.
Step 1: create a supabase project (3 min)
- Go to supabase.com and sign up
- Click "New Project"
- Name it (e.g., "habit-tracker")
- Set a database password — save this somewhere safe
- Choose a region close to you
- Click "Create" — wait 1-2 minutes for provisioning
Write in your file:
## Project Created
Project name: [name]
Region: [region]
URL: [your project URL — found in Settings → API]
Step 2: get your project keys (2 min)
- Go to Settings → API
- Find: - Project URL:
https://xxxxx.supabase.co - anon public key: eyJhbGciOi... (safe for frontend) - service_role key: eyJhbGciOi... (NEVER for frontend)
Newer projects label these differently: a publishable key (sb_publishable_...) takes the anon key's place and a secret key (sb_secret_...) takes the service_role key's. The rule is the same: the publishable key can go in the frontend, the secret key never can.
The anon key vs. the service_role key
The anon key is safe in frontend code — it only allows operations permitted by RLS policies. The service_role key bypasses ALL security. NEVER put it in frontend code. If someone gets it, they can read, modify, or delete all your data.
Write in your file:
## Keys
- anon key: [first 10 chars]...[last 5 chars] — safe for frontend
- service_role key: [DO NOT record this — just note it exists and must stay server-side]
Step 3: create a table (5 min)
Go to SQL Editor and run:
create table habits (
id bigint primary key generated always as identity,
user_id uuid references auth.users not null,
name text not null,
streak integer default 0,
completed boolean default false,
created_at timestamptz default now()
);
Or use Table Editor (visual): create table habits with columns: id (int8, PK, auto-increment), user_id (uuid), name (text), streak (int4, default 0), completed (boolean, default false), created_at (timestamptz, default now()).
Write in your file:
## Table Created
Table name: habits
Columns: [list your columns and types]
Method used: [SQL Editor / Table Editor]
Step 4: enable RLS (3 min)
Run in SQL Editor:
alter table habits enable row level security;
create policy "Users can see their own habits"
on habits for select
using (auth.uid() = user_id);
This means users only see rows where user_id matches their authenticated ID.
Write in your file:
## RLS Enabled
Policy: "Users can see their own habits" — select only, using auth.uid() = user_id
Step 5: connect your app (5 min)
Install the client:
npm install @supabase/supabase-js
Create src/lib/supabase.ts:
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
Create .env:
VITE_SUPABASE_URL=https://xxxxx.supabase.co
VITE_SUPABASE_ANON_KEY=eyJhbGciOi...
Write in your file:
## App Connected
- Client installed: [yes/no]
- supabase.ts created: [yes/no]
- .env created with URL and anon key: [yes/no]
Step 6: test all 4 CRUD operations (10 min)
Test each operation and always check for errors:
// SELECT
const { data, error } = await supabase.from('habits').select('*')
if (error) console.error('Select failed:', error.message)
// INSERT
const { data, error } = await supabase
.from('habits')
.insert({ user_id: 'test-uuid', name: 'Drink water', streak: 0, completed: false })
// UPDATE
const { data, error } = await supabase
.from('habits')
.update({ completed: true })
.eq('id', 1)
// DELETE
const { data, error } = await supabase
.from('habits')
.delete()
.eq('id', 1)
Write in your file:
## CRUD Test Results
1. SELECT: [success/fail] — [result or error]
2. INSERT: [success/fail] — [result or error]
3. UPDATE: [success/fail] — [result or error]
4. DELETE: [success/fail] — [result or error]
Error checking present in all operations: [yes/no]
Step 7: verify error handling (2 min)
Make sure every query checks for errors before using data:
// CORRECT
const { data, error } = await supabase.from('habits').select('*')
if (error) {
console.error('Failed:', error.message)
// show user-facing error
} else {
setHabits(data)
}
// WRONG — swallowed error
const { data } = await supabase.from('habits').select('*')
setHabits(data) // crashes if error
Write in your file:
## Error Handling Verification
All queries check `if (error)` before using data: [yes/no]
No swallowed errors found: [yes/no]
Which Supabase key must never appear in frontend code?
The service_role key bypasses all row-level security; anyone holding it can read, change or delete all your data. The anon key is limited by RLS policies.