Module 15Neill's Vibe · Tier 1 · Basic Vibe Coding~9 min

The backend in a box

A database, user login, and per-user data isolation — three hard things, solved at once. And the two quiet ways it bites: prices come back as NaN, or every user can read everyone's rows.

01 · the scene

"The data's in the database. The app shows NaN."

The row was right there in Supabase — a clean price, a real number. The app read it and rendered NaN. No error. No crash. The pipeline just produced garbage out of perfectly good data.

nightstream — pricing read
// in the DB: base_price = 189
> const price = row.basePrice
// row.basePrice is undefined → undefined * 1.1 =
NaN
Neill51% · the terrain

"The number was in the database. I could see it. The app turned it into NaN. I kept staring at the math thinking the calculation was wrong — the calculation was fine."

Claude49% · the execution

"The database speaks snake_case — base_price. The code asked for basePrice. That field doesn't exist, so it comes back undefined, and undefined in math is NaN. I should've flagged the naming mismatch immediately instead of letting you chase the arithmetic."

02 · the why

Three hard things, handed to you on day one.

To have real users with real saved data, you normally need three separate, genuinely hard systems: a database, user login, and a way to make sure each person only sees their own rows. Supabase hands you all three as a managed service you call from your app — no server to run.

▤

Postgres DB

a real database

A full SQL database (Postgres) behind a friendly client. my-day runs a 17-table schema on it.

⚷

Auth

login, handled

Sign-up, sign-in, sessions, password resets — the account layer built for you, not from scratch.

▦

RLS

row-level security

Rules in the database that decide which user can see which rows — per-user isolation, enforced at the data.

The two classic bites, both silent: forget to turn on RLS and every user can read everyone else's rows (a privacy hole with no error). And the database's snake_case column names won't match your code's camelCase — so fields come back empty, and empty numbers become NaN. Neither throws. Both just quietly produce the wrong thing.

03 · predict it

The number's in the DB. The app shows NaN. Why?

The row clearly has base_price = 189. Your code reads it and gets NaN, no error. What broke?

Not it. The number really is stored and readable — look at the exact spelling the code uses to ask for it versus how the database named the column.
Exactly. Postgres defaults to snake_case; JS code tends to write camelCase. Ask for basePrice on a row that only has base_price and you get undefined — and undefined * 1.1 is NaN. The fix is matching the names (or mapping them), not the math. (The nastier cousin: forgotten RLS, where the data loads fine but everyone's rows load.)
04 · the walkthrough

From empty project to safe per-user data, in four frames.

New project → keys
step 1Create the project. Grab the project URL + anon key — these go in your env vars, not the code.
create table
step 2Define your table. Note the column names are snake_case — your code has to match them exactly.
Enable RLS + policy
step 3Turn ON Row-Level Security and add a policy: a user can only touch rows where user_id = auth.uid().
supabase.from('...')
step 4Call it from the app. Audit the LIVE tables for RLS — not the migration files, the real tables.
05 · prove it · gate

Teach it back to unlock the payoff.

Same rule as always: answer it right, or the module isn't done.

You launch a multi-user app on Supabase but forget to enable Row-Level Security. What actually happens — and does it throw an error?
// one correct answer unlocks Module 15
Not it. The dangerous part is that nothing breaks — think about who can see whose data when there's no rule restricting it.
That's it. Without RLS the data layer happily serves any row to any authenticated request — one user reads another's data and nothing complains. The fix: enable RLS and write a policy keyed on auth.uid(), then verify it against the live tables, not the migration files. Module unlocked. ↓
the durable idea

Supabase gives you DB + auth + isolation for free — but the isolation (RLS) is opt-in, and silence is not safety. Match your field names, and never trust a table you haven't checked has a policy.

06 · factory payoff
⊘ locked — pass the teach-back to claim this piece of the factory

A safe-by-default Supabase client init + the RLS audit checklist.

A real supabase-client.js starter — reads its URL and key from env vars (never hard-coded), and includes the snake_case ↔ camelCase trap written into a comment so you don't get bitten by the NaN bug. Paired with an RLS audit checklist that makes you enumerate the live tables (the exact mistake of auditing policies-on-paper instead of real tables).

✓ Module 15 complete