buildwiththis home

Test script: Database for a vibe-coded app

The script we wrote before testing, with every amendment and its timestamp. The verdict it produced: Best database for a vibe-coded app: Neon vs Cloudflare D1 vs Supabase.

Published as written, with private emails and internal file paths removed. Times are Eastern. How we score: the rubric.

  • Job slug: database-for-vibe-coded-app
  • Version / status: v1 · frozen 2026-10-16 11:47 ET, before the first run. Changes after freeze go under “Amendments”, dated, with the reason.
  • Rubric version: v1 (rubric)
  • Author / date: Lando (CEO), 2026-10-16

1. The job

Your AI-built app keeps its data in a JSON file or in memory, so it vanishes on redeploy and can’t handle two people at once. You want a real hosted database on a free plan: create it, add a table, point the app at it, and know your data is safe from strangers and portable if you leave. You’re done when the app’s notes API reads and writes the database and the checks below pass.

2. Candidates

ToolPlanHow the operator gets an accountToS check (2026-10-16)Run mode
Supabase (Postgres)Free org, new free project (MCP get_cost = $0/month)Owner’s existing account, through the official Supabase MCPTerms and AUP, checked for job #2 and #5: no benchmark clause for publishers; bans automated account creation (we use the owner’s account).Agent-driven
Neon (Postgres)Claimable Neon: an unclaimed free project, no account (72-hour life, 100 MB) until a person claims it into a free accountNo signup: Neon’s official agent API (claimable.neon.tech), documented for exactly this casePlatform terms and the Databricks MCSA keyword scan: no benchmark, bot or automation clause found.Agent-driven
Cloudflare D1 (SQLite)Workers FreeOwner’s existing account, through the official Cloudflare MCPSelf-serve terms §2.2.2: benchmarks allowed if we publish what’s needed to repeat them (this script does). §2.2.1(e) bans scripts that create multiple accounts (we use one existing account).Agent-driven
Firebase Cloud FirestoreSpark (free), project buildwiththisOwner’s Google accountTerms keyword scan (2026-10-16): no automation or benchmark clause.Blocked before run: the Firestore API isn’t enabled on the project and our service account can’t enable it. Needs a console click by the owner. Not scored until run.
Turso, ConvexFreeNeed a browser signup (Turso’s form has a captcha; Convex signs in through the browser)Not checked yetQueued, not scored

3. Fixed inputs

  • Starter app: data-starter (local throwaway repo, tag data-starter-v1). A Node 20 + Express 4 notes API (GET/POST /api/notes, PATCH/DELETE /api/notes/:id) with a one-file HTML page in public/. It stores notes in data/notes.json. Each note: id, text, done, created_at.
  • Each tool gets its own copy of the starter and replaces only the storage layer (store.mjs). The API contract and the check script don’t change.
  • Cloudflare D1 exception: D1 is reached from a Cloudflare Worker, not from an outside server (Cloudflare’s docs reserve the D1 HTTP API for admin use). For D1 the same API contract runs as a Worker with a D1 binding, deployed through the Cloudflare API. The check script hits the Worker’s URL instead.
  • Operator machine: the test box (Toronto time; network location noted in the log). Latency numbers are from this one machine and are reported, not scored.

4. Fixed task

The operator (agent) implements this, following only the vendor’s official docs (each docs page used is logged):

Move the notes app from the JSON file to <tool>, on the free plan:
1. Create the database (or project) on the free plan, without a payment card.
2. Create a notes table/collection with id, text, done and created_at.
3. Make every API route read and write the database instead of the file.
4. Keep every secret (connection string, password, key, token) on the server only.
5. Leave the vendor's default access settings as they are, unless the docs tell you to change them.

5. Follow-up rules (agent operator version)

  • Time cap: 60 active minutes per tool.
  • Allowed: the vendor’s official docs, quickstarts, CLI/API references and error messages.
  • Not allowed: copying another tool’s code, third-party tutorials or templates.
  • Manual interventions (counted for C2): anything that needs a person in a web dashboard or a browser (clicks, copying a key, claiming a project), and any workaround the docs don’t cover.

6. Steps

  1. Record the start time. Create the database and log its plan and cost.
  2. Implement the task (§4) in the tool’s copy of the starter.
  3. Run checks/run-checks.mjs against the running app. Save its JSON output.
  4. Fix and rerun until all core checks pass or the cap is hit. The end time is when the last core check first passes (= time to working).
  5. Run the export (D7) and the anonymous-access probe (D6).
  6. Record lines changed, dependencies added, docs pages used, and the free-plan limits and price from the vendor’s pricing page, with the date.

7. Pass/fail checks

IDCheckHow to testPass ifEvidence
D1 [core]Free database, no cardCreate it through the vendor’s CLI/API/MCPCreated on the free plan, no payment method askedAPI response, cost check
D2 [core]Create, list, update, deleteCheck script: POST 3 notes, GET list, PATCH one to done, DELETE oneEvery call returns the expected status and dataJSON log
D3 [core]Data survives a restartCreate a note, stop and restart the server (redeploy the Worker for D1), list againThe note is still thereJSON log
D4 [core]50 writes at onceCheck script fires 50 POSTs in parallel, then countsAll 50 return 201 and all 50 are listedJSON log
D5 [core / G2]No secret in the browserFetch every file the app serves to the browser; search for the run’s real secret values and key-shaped stringsNone foundgrep output
D6 [G2]Strangers can’t read the dataWith only what’s public (the project URL and any public/client key the vendor hands out), try to read the notes directly, bypassing our APIDenied (401/403/permission error or no public endpoint)HTTP log
D7Export in a standard formatUse the vendor’s documented export (e.g. pg_dump, D1 export) on the free plan, and restore it into a local databaseThe dump restores and holds every noteDump file, restore log
D8Reported only: latencyMedian and p90 of 20 sequential GET /api/notes from the operator box(not pass/fail)JSON log

8. Quality checklist (C5): developer experience

IDItemPass if
Q1An official Node.js (or Workers) quickstart exists and works as writtenYes
Q2Tables can be created from code, CLI or API (no dashboard needed)Yes
Q3A local development option exists (local database, emulator or CLI dev mode)Documented
Q4Errors hit during the run were explained by the docs or the error messageYes
Q5The pricing page states free-plan limits clearly (storage, compute/requests, what happens when you hit them)Yes

9. Security checklist (C7)

IDItemPass if
S1Default access: a new table isn’t readable by strangers (= D6)Pass D6 without changing settings
S2Encrypted connections by defaultTLS required or the only option
S3Recovery included on the free planBackups or point-in-time restore on the free plan, per the vendor’s docs
S4The vendor warns about risky settingsA security advisor, lint or warning surface exists on the free plan

10. Weights and tie-breaker

  • Weights: C1 35 · C2 15 · C3 10 · C4 10 · C5 10 · C6 10 · C7 10.
  • C2 anchors (manual interventions + problems the docs didn’t answer): 5 = 0–1 · 4 = 2–3 · 3 = 4–5 · 2 = 6–8 · 1 = more.
  • C3 anchors (T = 60 min): 5 = ≤15 · 4 = ≤30 · 3 = ≤45 · 2 = ≤60 · 1 = cap hit.
  • C5 and C7: checklist-count anchor (rubric §3).
  • C6 for a database means portability: 5 = standard engine (Postgres or SQLite) with a documented full export on the free plan that restores elsewhere · 4 = export works with manual steps · 3 = proprietary data model or export needs a paid plan · 2 = partial export · 1 = none.
  • Tie-breaker: C7 (safe defaults), then C1.
  • Extra metrics (reported, not scored): lines changed, dependencies added, docs pages used, D8 latency.

11. Cleanup

  • Notes are fake text; no personal data. Delete the test databases after the verdict is published (unclaimed Neon projects expire on their own after 72 hours). Keys live only in the gitignored .env.

Amendments

  • v1.1 (2026-10-16 11:49 ET, during j06-supabase-r1, before any check ran):

    1. Supabase runs its notes API as a Supabase Edge Function, not from the starter’s Express server. Reason: the operator box allows only HTTPS traffic (Postgres ports 5432/6543 time out), and Supabase’s server-side secret key and database password are only shown in its dashboard. Edge Functions get the secret key injected by Supabase, as its docs describe. Same API contract and check script. D3’s “restart” for Edge Functions and Workers = redeploying the same code.
    2. D7 runs pg_dump in a short-lived cloud sandbox (Vercel Sandbox, Ubuntu, pg_dump 18.6) for the two Postgres tools, for the same network reason. The dump is restored into a local Postgres in that sandbox. D1’s export uses Cloudflare’s export API and is restored into SQLite on the box.

    Neither change affects how a tool is scored.

Scoring notes (2026-10-16)

  • S1 (Supabase): a table created with SQL on a new project was read (200) and written (201) by an anonymous request with the publishable key. Supabase’s docs say to enable RLS on tables created with SQL; after doing so, D6 passed. S1 measures the default, so it’s a miss.
  • S2: Supabase accepted an sslmode=disable connection through the shared pooler (pg_stat_ssl.ssl = f), matching its docs. Neon refused it (“connection is insecure”). D1 has no database protocol outside Workers and the HTTPS API.
  • S3: per the vendors’ pricing and limits pages: Neon Free has instant restore (up to 6 hours or 1 GB of changes), D1 Free has 7 days of Time Travel, Supabase Free has no backups (daily backups start on Pro).
  • S4: Supabase Security Advisor (free). Neon’s Data API Advisors (they cover databases exposed through Neon’s Data API, which our setup didn’t use). We found no equivalent for D1.
  • C2 (Supabase), 2 issues: the export needs the database password, which only the dashboard shows (we created a dedicated role with SQL, as Supabase’s roles docs recommend, and pg_dump then needed --enable-row-security); and the shared-pooler host name isn’t given outside the dashboard (we found it by trying aws-0/1/2). C2 (Neon): keeping a no-signup project past 72 hours needs a person to claim it in a browser (1 intervention). D1: the account had no workers.dev subdomain yet; the error message named the fix, and we registered one through the API.