CRUD Operations — Create, Read, Update, Delete for real
Your app is connected to the database. Now you'll make it actually do things: add records, show them, change them, and remove them. These four operations — CRUD — are the bulk of what any data-driven app does. You'll drive each one with a precise prompt to Claude, then test it by hand.
The Four Operations
| Letter | Operation | What it does |
|---|---|---|
| C | Create | Add a new record |
| R | Read | Fetch records to display |
| U | Update | Change an existing record |
| D | Delete | Remove a record |
By the end of this lesson, your main data type (tasks, posts, projects — whatever your app uses) supports all four.
Prompts for Claude Code
Instead of writing code yourself, hand Claude precise, requirement-by-requirement prompts. The more specific you are, the better the result.
Read — Fetching Data
Update the task list on my home page to fetch real tasks from Supabase instead of hardcoded data.
Requirements:
- Use the Supabase client to fetch all tasks from the
taskstable- Sort by
created_at, newest first- While loading, show a simple loading spinner or "Loading…" text
- If there's an error, show a friendly error message
- Display the tasks using the existing task list UI
The
taskstable has:id(uuid),title(text),done(bool),user_id(uuid),created_at(timestamp).
Create — Adding New Records
Update the "Add Task" form to save new tasks to Supabase.
Requirements:
- When the user clicks Add (or presses Enter), insert a new row into the
taskstable- The title comes from the input field
- Set
donetofalseby default- For
user_id, use a placeholder UUID for now:00000000-0000-0000-0000-000000000000(we'll swap in real auth later)- After inserting, refresh the task list so the new task appears
- Clear the input field after a successful insert
- If the insert fails, show an error message
Update — Changing Records
Update the task checkbox to change the task's
donestatus in Supabase.Requirements:
- When a user clicks a checkbox, toggle that task's
donevalue in the database- Send an UPDATE to Supabase for the matching task
id- Update the UI immediately (optimistic update) — don't wait for the server
- If the update fails, revert the UI and show an error
Delete — Removing Records
Update the delete button to delete the task from Supabase.
Requirements:
- When the user clicks the trash/delete icon, show a brief confirmation ("Are you sure?")
- If confirmed, delete the row from the
taskstable whereidmatches- Remove the task from the UI after a successful delete
- If the delete fails, show an error
What the Code Looks Like
Claude writes all of this — but recognizing the shape of it makes you a far better reviewer and debugger:
// READ all tasks
const { data, error } = await supabase
.from('tasks')
.select('*')
.order('created_at', { ascending: false })
// CREATE a task
const { data, error } = await supabase
.from('tasks')
.insert({ title: 'My new task', done: false, user_id: userId })
.select()
// UPDATE a task
const { error } = await supabase
.from('tasks')
.update({ done: true })
.eq('id', taskId)
// DELETE a task
const { error } = await supabase
.from('tasks')
.delete()
.eq('id', taskId)
Every query follows the same four-part pattern:
- Which table —
.from('tasks') - Which operation —
.select(),.insert(),.update(),.delete() - What data — the object you pass in (for create/update)
- Which rows — a filter like
.eq('id', taskId), meaning "whereidequalstaskId"
Notice that update and delete always carry a .eq(...) filter. An update or delete with no filter would hit every row in the table — your RLS policies are the safety net that stops that from wiping out other users' data, which is exactly why every table has them.
Handle the Three States
Good apps always account for three situations. Verify Claude covered each:
| State | When it shows | What's good |
|---|---|---|
| Loading | While data is being fetched | A spinner, skeleton, or "Loading…" |
| Error | The fetch failed | A human-readable message, not Error: 404 |
| Empty | There's no data yet | An encouraging line, not a blank screen |
If any are missing:
Make sure the task list handles all three states: loading, error, and empty. The empty state should be encouraging — something like "No tasks yet — add your first one above!"
Real-Time Updates (Optional Bonus)
Supabase can push changes to your UI automatically — when a row is added, changed, or deleted, the screen updates with no refresh. If you want it:
Add Supabase realtime to the task list. When tasks are added, updated, or deleted from any source, the UI should reflect the change automatically without a page refresh. Use Supabase's real-time subscriptions for the
taskstable.
Test Every Operation by Hand
After Claude implements each one, verify it yourself. Don't trust it until you've seen it work in both the app and the Supabase dashboard.
Create: Type a title, click Add → the task appears → check Table Editor → tasks in Supabase and confirm the new row is there.
Read: Refresh the page → your tasks are still there (they persisted!). Add a row directly in Supabase's editor → it shows up in your app.
Update: Check and uncheck a task → refresh → the checkbox state is preserved.
Delete: Delete a task → it disappears → refresh → it's gone for good.
This dashboard-plus-app check is the habit that separates "it looked like it worked" from "it actually works."
Summary
- CRUD = Create, Read, Update, Delete — the four operations almost every app needs.
- Drive each one with a specific, requirement-by-requirement prompt to Claude.
- Every Supabase query is: which table, which operation, what data, which rows.
updateanddeleteneed a filter; RLS is your safety net against runaway writes.- Always handle loading, error, and empty states in the UI.
- Test each operation in both the app and the Supabase dashboard to confirm data really persists.