The Mental Model
React is the Keyboard.
Supabase is the Engine.
A synthesizer makes no sound until you patch the keyboard voltage to the oscillator. In our stack, the Custom Hook is the patch cable.
The Database (Supabase): Stores the raw data (The Sound Source).
The AI (Cursor/Claude): Writes the wiring code (The Technician).
The Hook (useData): Brings the data into the component (The Cable).
The Baton
Don't ask blindly.
AI doesn't know you're using Supabase unless you tell it. Before asking for a hook, you must load the context.
The "System Prompt" Trick
"I am building a Next.js 15 app with Tailwind and Supabase. I am using the @/lib/supabase/client file for connection."
"Write code to fetch users."
"Create a React hook called useUsers. Use the Supabase client from @/lib/supabase. Fetch from the 'users' table. Handle loading and error states. Return an object."
import { createClient } from "@/utils/supabase/client";
export function useUsers() {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchUsers = async () => {
const supabase = createClient();
const { data, error } = await supabase
.from("users")
.select("*");
// AI handles the boring error checking for you
if (!error) setData(data);
setLoading(false);
};
fetchUsers();
}, []);
return { data, loading };
}The Gatekeeper
Row Level Security.
By default, Supabase is like an open club. Anyone with the URL can walk in. RLS (Row Level Security) is the bouncer.
Without RLS: Anyone can DELETE your users.
With RLS: Users only see their own data.
The Prompt to Fix It
The Mutations
Record New
Create a new row. Like dropping a sample into the sequencer.
await supabase
.from("todos")
.insert({
title: "New Task",
is_complete: false
})
Remix Existing
Modify a row. Adjusting the pitch on an existing note.
await supabase
.from("todos")
.update({
is_complete: true
})
.eq("id", todoId)
Erase Take
Remove a row. Deleting that bad vocal take forever.
await supabase
.from("todos")
.delete()
.eq("id", todoId)
Pro tip: Always use .eq() to target specific rows. Without it, you'll affect every row in the table.
Common Patterns
✗ Signal Interference
"I'll learn SQL first"
No. Describe what you want in English. AI writes the SQL.
"I'll set up RLS later"
Dangerous. Enable RLS immediately. One exposed endpoint can leak everything.
"I need complex migrations"
For MVP? Just use the dashboard. Run raw SQL. Keep it simple.
"I need to optimize queries"
With 10 users? No. Make it work first. Optimize when you hit 10,000.
✓ Clean Patch
Prompt-first development
"Create a hook that fetches todos for the current user" — let AI handle syntax.
Enable RLS immediately
Toggle it on in Supabase dashboard. Add policies as you go.
One table at a time
Start with one table. Get CRUD working. Add more when needed.
Console.log the response
Always log data and error to see what Supabase returns.
The Exercises
Synthesize the connection.
The Patch
Setup Supabase
- →Create Supabase Project
- →Copy Env Variables (URL, Key)
- →Prompt AI: "Setup Supabase client in Next.js 15"
The Sequence
Create Table
- →Go to Supabase SQL Editor
- →Ask AI: "Write SQL for a 'todos' table with title, is_complete, and user_id"
- →Run the SQL
The Signal
Fetch Data
- →Insert 1 row manually in Supabase
- →Prompt AI: "Create useTodos hook"
- →Display the data in your UI
📋 The Prompt Template
Copy-paste this when asking AI for database work:
My Supabase client is at @/utils/supabase/client.ts.
Please create a hook called [HookName] that fetches from the [TableName] table.
Handle loading and error states.
Return the data typed properly.