Module 6: Databases & Auth with Supabase
Lesson 4

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

LetterOperationWhat it does
CCreateAdd a new record
RReadFetch records to display
UUpdateChange an existing record
DDeleteRemove 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 tasks table
  • 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 tasks table 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 tasks table
  • The title comes from the input field
  • Set done to false by 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 done status in Supabase.

Requirements:

  • When a user clicks a checkbox, toggle that task's done value 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 tasks table where id matches
  • 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:

  1. Which table.from('tasks')
  2. Which operation.select(), .insert(), .update(), .delete()
  3. What data — the object you pass in (for create/update)
  4. Which rows — a filter like .eq('id', taskId), meaning "where id equals taskId"

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:

StateWhen it showsWhat's good
LoadingWhile data is being fetchedA spinner, skeleton, or "Loading…"
ErrorThe fetch failedA human-readable message, not Error: 404
EmptyThere's no data yetAn 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 tasks table.


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.
  • update and delete need 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.