Vibe Code a CRUD App: The Pattern Behind Most Software

Build a create-read-update-delete app with AI — the prompt sequence that works, and the ownership check that decides whether it's safe.

C
CodeIllusion Team
#vibe-coding #crud #tutorial #supabase #saas
Vibe Code a CRUD App: The Pattern Behind Most Software

CRUD is the skeleton of most software. Get comfortable building one and a surprising share of app ideas become achievable.

It’s also where you meet the authorization problem for the first time, which is the real lesson of this project.

What You’re Building

A project tracker. Users sign in, create projects, edit them, mark them done, delete them. Each user sees only their own.

That last clause is the entire security surface, and it’s the part AI skips.

Model the Data First

Before generating anything, decide your fields. This takes two minutes and prevents a lot of churn.

projects
  id          uuid, primary key
  user_id     uuid, references auth.users   ← the important one
  title       text, required
  description text
  status      text: 'active' | 'done' | 'archived'
  due_date    date, nullable
  created_at  timestamptz
  updated_at  timestamptz

user_id is what makes ownership possible. If it’s not on the table from the start, every query below is unenforceable.

Build in Four Steps

Don’t ask for the whole app at once. You’ll get something sprawling that neither of you fully understands.

Step 1 — Schema and auth

Next.js App Router, TypeScript, Tailwind, Supabase. Create a projects table with: id uuid pk, user_id uuid referencing auth.users, title text not null, description text, status text defaulting to ‘active’, due_date date nullable, created_at and updated_at timestamptz.

Enable row-level security. Write policies so users can only select, insert, update and delete rows where user_id = auth.uid(). Give me the SQL.

Add Supabase Auth with email/password. Protect /projects server-side.

Run the SQL. Confirm the RLS badge appears on the table in the Supabase dashboard. Commit.

Step 2 — Read

Build /projects listing the signed-in user’s projects in a table: title, status, due date, created date. Sort by created_at descending. Include an empty state. Fetch server-side, filtering by the authenticated user’s ID.

Check it works. Commit.

Step 3 — Create and update

Add project creation via a modal form with title, description, status and due date. Validate server-side with Zod: title required, max 200 characters; description max 2000; status must be one of the three values.

Add editing via the same form pre-filled. The update query must filter by both id and the authenticated user’s ID.

That final sentence is the one that matters. Ask for it explicitly every time.

Commit.

Step 4 — Delete

Add delete with a confirmation dialog. The delete query must filter by both id and authenticated user ID. Show an undo toast for 5 seconds before committing, or use a soft delete with a deleted_at column.

Soft deletes are worth it here — a confirmation dialog is not much protection against a misclick.

Commit.

The Bug You Now Go Looking For

Read your update and delete handlers. You’re looking for this shape:

// WRONG — acts on any record by ID
await supabase.from('projects').delete().eq('id', id)

Versus this:

// RIGHT — acts only on the user's own record
await supabase.from('projects')
  .delete()
  .eq('id', id)
  .eq('user_id', user.id)

Read operations usually get filtered correctly, because a user seeing someone else’s data is visible in the UI. Update and delete often don’t, because nothing looks wrong — the wrong record just silently changes.

RLS catches this if you enabled it. Both layers is correct; relying only on RLS means one misconfiguration removes all protection.

Test It

Two accounts, five minutes:

  1. Account A creates a project. Note the ID.
  2. Account B, separate browser.
  3. As B, open /projects/[A's id]/edit — should 404 or redirect.
  4. As B, submit an update to A’s ID via the API directly — should fail.
  5. As B, submit a delete for A’s ID — should fail.

Step 5 is the one people skip and the one that bites.

What to Add Next

Once the core works, these are the natural extensions, in the order they usually become necessary:

Search and filter. Ask for server-side filtering, not client-side — filtering after fetching everything means you fetched everything.

Pagination. Before you have a thousand rows, not after.

Add cursor-based pagination, 25 per page. Use range queries, not offset.

Optimistic updates. Makes the app feel instant. Ask for rollback on failure or you’ll show success for things that failed.

Bulk actions. Every bulk endpoint needs the same ownership filter as the single-record one — and it’s skipped even more often.

The Checklist

  • user_id on the table, referencing auth.users
  • RLS enabled with policies for all four operations
  • Every read filters by authenticated user
  • Every update filters by id AND user_id
  • Every delete filters by id AND user_id
  • Server-side validation on all inputs, with max lengths
  • Two-account test passed for read, update and delete
  • Empty states and error states exist
  • Pagination before the list gets long

Why This Project Matters

Almost everything else is CRUD with extra steps. A booking app is CRUD with timezones. An invoicing tool is CRUD with PDFs. An admin panel is CRUD with roles.

Build one carefully and you’ve learned the pattern that underlies most of the rest — including the ownership check that every one of them needs.

Next: Vibe code a SaaS dashboard puts a real interface on this data, or vibe code a REST API exposes it to other applications.

Related reading:

Frequently Asked Questions

What is a CRUD app? +

An app built around four operations on records: create, read, update, delete. Task managers, inventory systems, contact lists, and note apps are all CRUD. It's the most repeated pattern in software, which is why AI tools reproduce it well.

Can AI build a full CRUD app? +

Yes, reliably — this is one of the strongest use cases. Expect a working app with a database, forms, and a list view in an afternoon. What needs checking is whether users can only touch their own records.

What does AI get wrong in CRUD apps? +

Ownership checks on update and delete. Read operations usually get filtered by user because that's visible in the UI. Update and delete often take just a record ID and act on it, meaning any user can modify any record.

What stack should I use for an AI-built CRUD app? +

Next.js with Supabase is the most reliable combination — heavily documented, so AI reproduces it accurately, and Supabase provides auth plus row-level security as a safety net. Ask for it by name.

Tagged:

#vibe-coding #crud #tutorial #supabase #saas

Enjoyed this article?

Get more AI tool picks, coding tutorials, and no-code automation guides every week. No spam, ever.

Found this useful? Share it:

More in AI Coding Tools