Back to articles
Vibe CodingDatabasesDataAI

Vibe Coding Without Losing Your Data: Database Precautions

When you vibe code, the agent runs migrations, UPDATEs, and DELETEs with no backup and no WHERE guard. How to keep an AI agent from quietly destroying the data it's supposed to protect.

Yash Thakur
4 min read
Vibe Coding Without Losing Your Data: Database Precautions

The short answer: the quiet danger of vibe coding a data layer is that the agent will happily run a destructive migration, an un-WHEREd UPDATE, or a DELETE with no backup behind it — and the demo still looks fine right up until the data is gone. Protect yourself before you let it touch the database: a backup plus verified point-in-time recovery before any migration, forward-only migrations a human reads first, no destructive operation in production without a guard, transactions around every multi-step write, and a deliberate plan for PII. The code is reversible. The data usually isn't.

Choosing which database to use is a separate decision — I covered that in how to choose a backend you won't regret. This post is about the thing that's easier to get catastrophically wrong: not losing or corrupting the data once it's in there. When you vibe code, the agent writes migrations and data operations the same confident way it writes a component. The difference is that a bad component throws an error you can see, and a bad DELETE returns 200 OK on an empty table.

Back up before the migration, and prove you can restore

The single highest-leverage habit: a backup and a verified restore path before any schema change, not as something you'll "set up later." An agent asked to add a column will cheerfully rewrite a table, and if that migration goes sideways on real data, your only way back is a backup that already existed.

"We have backups" is not the same as "we can restore." Point-in-time recovery — the ability to roll the database back to a specific second before the bad migration ran — is what actually saves you, and the only way to know it works is to have restored from it at least once. Postgres gives you this (pg_dump for snapshots, WAL archiving for PITR); most managed providers hand it to you with a toggle. Turn it on, then test a restore into a throwaway database so you find the gaps while it's cheap.

Migrations: forward-only, reviewed, and never silently destructive

Let the agent generate migrations, but treat each one as a change a human reads before it runs. Two rules keep them safe:

  • Forward-only. Don't rely on a "down" migration to undo a mistake in production — once data has changed shape, the reverse migration often can't reconstruct what was lost. Roll forward with a new corrective migration, and lean on your backup for true disaster recovery.
  • Separate the destructive step. Adding a column is safe. Dropping one, dropping a table, or rewriting a type is not. Keep those in their own reviewed migration, ship them after the code that stopped using the old column, and never let "add a field" and "drop three others" ride in the same agent-generated change.

Never run a destructive operation in prod without a guard

This is the one that bites hardest, because the agent's version looks almost identical to the safe version — it's just missing the line that matters.

The habits that make this safe are boring and non-negotiable: scope every UPDATE/DELETE with a WHERE, SELECT the affected rows first and read the count, and wrap the whole thing in a transaction so a surprise is a ROLLBACK instead of an incident. In application code, require a WHERE at the query-builder level and block raw destructive SQL in the production path entirely.

Transactions, or partial writes will corrupt your state

Any operation that touches more than one row or table has to be all-or-nothing. The classic vibe-coding bug is a "transfer" that debits one account, then the process dies before it credits the other — now the money has evaporated and the database is internally inconsistent with no error anywhere. An agent optimizing for "it works in the happy path" routinely skips the transaction, because the happy path never fails.

Wrap multi-step writes in a single transaction so they commit together or not at all. If the agent split a logical write across two calls on two connections, that's not atomic no matter how close together they run — thread one transaction through the whole operation and commit once.

Handle PII like it's radioactive

The data model is also where privacy mistakes ossify. An agent will log a full request body (password and email included), store personal data it was never asked to collect, and keep it forever — because none of that breaks a test. Decide this deliberately:

  • Don't log PII. Redact emails, tokens, and payment details before anything hits your logs or your error tracker. Logs get shipped to third parties and kept for months.
  • Encrypt at rest and minimize what you collect — the safest field is the one you never stored.
  • Give it a retention policy. Delete personal data when you no longer need it; "keep everything forever" is a liability, not a feature.

Review the schema before it ossifies

Your schema is the hardest thing in the whole stack to change later, so it's worth a human read before real data accumulates behind it. The agent tends to flatten a genuinely relational model into one wide table with JSON blobs because that's the fastest thing to generate — and it's fine until you need to query inside those blobs or keep them consistent. Get the relational shape right early, while the only thing a migration has to move is test data.

Use agents and skills deliberately

  • A backend-architecture / backend-patterns skill to review the data model and migration strategy before the first real row lands — ask it specifically "what in this schema will be painful to change once we have production data?"
  • A security-and-hardening skill pointed at how PII flows through the app: what gets logged, what's encrypted, what's retained, and where real data might leak into fixtures or traces.
  • A coding-standards skill to enforce the non-negotiables in the query layer — transactions around multi-step writes, a required WHERE on destructive statements, no raw SQL in the prod path.

The move that de-risks the data layer is the same one that de-risks the rest: have a specialized agent audit the destructive and privacy-sensitive paths the generalist agent generated, because those are exactly the mistakes that pass every test and only show up when the data is already gone.

Your data outlives your code, your framework, and probably the whole first version of the app — which is why it deserves more caution than anything else the agent touches. Back it up and prove the restore, keep migrations forward-only and reviewed, guard every destructive operation, wrap multi-step writes in transactions, and treat PII as a liability. This is one of the vibe-coding deep-dives — start from the vibe-coding precautions overview, and read it alongside how to choose a backend and security and networking precautions.

Frequently asked questions

How do I safely run database migrations?

Take a backup and confirm point-in-time recovery works before the migration, not after. Write forward-only migrations that a human reads before they run, keep destructive steps (dropping a column or table) in a separate reviewed change, and run every multi-step write inside a transaction so a failure rolls back cleanly instead of leaving the schema half-applied.

Why do you need a WHERE clause on UPDATE and DELETE?

An UPDATE or DELETE with no WHERE clause hits every row in the table — one missing line turns 'fix this one record' into 'overwrite all of them.' Always scope the statement with a WHERE, run it inside a transaction so you can roll back, and SELECT the rows first to confirm the count before you commit.

How should an app handle PII in its database?

Keep personally identifiable information out of your logs, encrypt it at rest, and give it a retention policy so you delete it when you no longer need it. Collect the minimum you actually use, and never let an agent paste real user data into a test fixture or a debug log where it will outlive its purpose.

Written by Yash Thakur

Senior React Developer · 8+ years building for the web

More articles

Keep reading