Beyond Vibe Coding Patterns for Building Real Software with Claude Code

Data: The Part of Your App That Can't Be Regenerated

On this page

Part I chapter draft. An immersive, single-sitting treatment of data for the non-developer — the one place a reader can go to understand the subject whole, rather than assembling it from vocabulary entries, product comparisons, and a pathology write-up scattered across the book. Additive: it does not replace Family 12 in Chapter 1, Building Blocks, the SQLite/MySQL/Postgres comparison in Chapter 2, The Ecosystem Map, or Natural-Key Bias in Chapter 9, Additive Bias and Calling the Question — it is the narrative home those reference points hang off. Non-devs read; devs skip. Working title.


Why this chapter exists

Most explanations of databases start with tables and rows and SELECT statements, and by the third page you’re learning join syntax you will never type. That is a course in using a database. You don’t need it. Claude writes the queries.

Here is the framing that actually matters for you: everything else in your app can be regenerated. Your data cannot.

Sit with that, because it reorganizes how you should think about risk. If Claude mangles a component, you ask it to rewrite the component. If it corrupts the architecture, you throw the branch away and start again. If your entire codebase evaporated tonight, you would lose time, and you would be annoyed, and then you would rebuild it — probably faster than the first time, because you know what you’re building now. Code is replaceable. That is what makes vibe coding possible at all.

Your users’ accounts, their orders, their uploads, the eight months of records that accumulated while the app was quietly working — none of that can be rebuilt from a prompt. It exists in exactly one place, it was produced by events that already happened, and no model can regenerate it. It is the only genuinely irreversible thing you own.

The named move: code is replaceable, data is not. Scale your caution to what can’t be regenerated. This is the book’s blast-radius principle (Chapter 9, Additive Bias and Calling the Question, theme 5) at its sharpest point: wave through the reversible, gate the irreversible. Almost everything Claude does to your code is reversible — version control (Chapter TK, Version Control) sees to that. Almost nothing it does to your data is. That asymmetry, not any fact about SQL syntax, is what this chapter is teaching.

So this chapter is not a database course. It is the shape of the thing, the four decisions that are hard to take back, and enough vocabulary that you can name what you want when you ask Claude for help — which, as Chapter 3, Speaking Claude’s Language argues, is most of the battle. “Should this be a surrogate key?” is eleven syllables that put Claude in an entirely different frame than “does this look right?”


What a database actually is

A database is the part of your app that remembers things after everyone goes home.

That’s it. Close the browser, restart the server, redeploy the whole application — whatever was in the database is still there. Whatever wasn’t is gone. Accounts, posts, orders, messages, settings: if a user expects it to still exist tomorrow, it lives in the database.

The honest starting analogy is a spreadsheet, and it’s a better analogy than experts like to admit:

  • A table is one sheet — Customers, Orders, Messages. An app has many.
  • A row (also called a record) is one line on that sheet — one customer, one order.
  • A column (also called a field) is one piece of information every row has — email, price, created date.

Where the analogy breaks is the important part. A spreadsheet will let you type “banana” into a column of dates, leave the price blank on half your orders, and enter the same customer four times. A database can be told to refuse all three. The rules about what’s allowed — which columns exist, what type each one holds, what’s required, what must be unique — are called the schema, and the schema is enforced. Bad data doesn’t get quietly written and discovered six months later; it gets rejected at the door.

That is worth recognizing as an old friend in new clothes. It’s fail-fast — one of the durable principles in Chapter 5, Principles That Outlast the Tool — implemented in storage. A database’s willingness to say no is not obstruction. It’s the feature.

The other thing tables do is point at each other. An Orders table doesn’t repeat the customer’s name and address on every row; it stores a reference — “this order belongs to customer 4471” — and the customer’s details live once, in Customers. That reference is called a foreign key, and the general practice of storing each fact exactly once and pointing at it is normalization. You now know enough to recognize why an experienced eye twitches at a table with customer_name, customer_email, and customer_shipping_city columns repeated across three tables: that’s the same fact stored in several places, and the moment one copy changes, they disagree. It is DRY (Chapter 5, Principles That Outlast the Tool) violated in storage, and it’s one of the shapes Compound Naming takes in the pathology catalog (Chapter 9, Additive Bias and Calling the Question).

Finally, SQL (say it “sequel” or “ess-cue-el”; both are correct and people are strange about it) is the language you use to ask a database questions — “give me every order from May over $100.” It is not a database. This matters more than it sounds, and we’ll come back to it.


The one confusion to clear up first: the database is not part of your app

Almost every newcomer mistake in this chapter grows from one misunderstanding, so let’s kill it early.

You probably picture your app as a single thing — the code, the pages, the data, all one bundle that gets deployed together. It isn’t. Your app and your data are two separate things with two separate lifecycles, and they are joined only by a connection.

  • The app is code. It gets rewritten, redeployed, and replaced constantly. Deploying a new version wipes out the old code entirely and nobody minds — that’s the point of deploying.
  • The database sits to the side and persists across every one of those deployments. Deploying does not replace it. It accumulates.

Picture a restaurant. The staff, the menu, the layout of the dining room — that’s the app, and you can change all of it overnight. The reservation book is the database. New management doesn’t get a fresh reservation book; they inherit the one with eight months of bookings in it, and if they lose it, no amount of redecorating brings it back.

This single distinction explains most of what follows. It’s why “Claude, rewrite this entire module” is a Tuesday and “Claude, change this table” deserves a pause. It’s why your database has its own password, its own backups, and its own hosting bill separate from your app’s. And it’s why the scariest sentence in this chapter is not about code at all.

The second confusion, quickly: people say “we use SQL” when they mean “we use a relational database.” SQLite, MySQL, and PostgreSQL are three different databases that all speak SQL — but they speak it with dialect differences, the way English is one language in London and Lagos and Louisville and yet you can still get tripped by a word. Most of your queries will move between them unchanged. Some won’t. That gap is small enough to ignore right up until the day it isn’t, which is a section of its own below.


The four things you will ever do to data

Strip out the syntax and every database operation in your app is one of four moves. They are emphatically not equally dangerous, and knowing which one you’re in is the whole skill.

1. Define its shape (the schema). Decide what tables exist, what columns they have, what’s required, what’s unique, what points at what. This happens at the start, and — critically — every time you add a feature that needs to remember something new.

2. Write to it (create, update, delete). Add a customer. Change an address. Remove a post. This is what your app does all day, every day, in response to users.

3. Read from it (query). Ask questions and get answers back. “This user’s last ten orders.” “How many signups this week.” Reading is the overwhelming majority of what an app does, and it is the safest thing in this chapter — a question changes nothing.

4. Change its shape (migrate). Alter a schema that already has real data living in it. Add a column, rename one, split a table in two, change what a field is allowed to hold.

Here’s the asymmetry that matters. Moves 2 and 3 are routine, and your app performs them thousands of times without supervision — they operate within rules you already set. Move 1 is consequential but happens on a blank page, where mistakes are cheap. Move 4 is the dangerous one, and it is the one this chapter slows down for, because it is where irreversibility lives.


Schema changes are a one-way door

Adding a column to an empty table is nothing. Adding a column to a table holding four thousand customer records is a different act performed with the same words, and the difference is that real data is now standing in the room while you rearrange it.

Changing the shape of a database that already contains data is called a migration. The name is apt — you are moving the existing data from an old shape into a new one, and every row has to survive the trip. A migration is a small program: add this column, backfill it with this value, drop that one, rename this table. Claude writes them fluently.

Which is exactly the problem. Consider what the following instructions look like from inside a migration:

  • Rename a column. Fine — unless it’s implemented as drop the old column, create a new one, in which case every value that was in it is gone.
  • Change a column’s type from text to number. Fine — for the rows that hold clean numbers. The row where someone typed “n/a” in 2024 either blocks the whole migration or gets silently turned into nothing, and which one happens depends on details you will not be reviewing.
  • Add a required field to a table with existing rows. Every existing row has no value for it. The migration must decide what those rows get, and “the migration decided” is doing a lot of work in that sentence.
  • Drop a column that “isn’t used anymore.” Reversible in the code. Not reversible in the data.

None of these are exotic. They are the ordinary consequences of ordinary feature requests, and Claude will produce a confident, well-structured migration for each one — because a migration that runs successfully and a migration that preserves your data look identical from the outside, and Claude’s confidence tracks the first (Chapter 9, Additive Bias and Calling the Question, theme 6: confidence is not a correctness signal).

The reflex to build. Before any migration touches data you care about: back it up, and run the migration against the backup first. Not because you’ll read the SQL — you probably can’t yet, and that’s fine. Because the only reliable check on “did this preserve everything” is watching it not preserve everything somewhere it doesn’t count. This is supervision by outcome, not review by reading (Chapter 4, The Engineer, Not the Programmer) — you are judging the result, not the implementation.

Three questions to put to Claude before a migration runs, none of which require you to read code:

  • “Is this migration reversible? If it goes wrong halfway, what’s the state?”
  • “Which rows lose information here — and what happens to rows that don’t fit the new shape?”
  • “What’s the rollback plan?”

If the answers are vague, that vagueness is your signal — not a reason to be embarrassed about not following. A migration nobody can explain in plain language is one nobody has thought through.

Honesty tag — where recognition stops. Anyone can catch this: asking the three questions, insisting on a backup first, noticing when the answer is hand-wavy. That’s judgment, not code-reading, and it catches most of the damage. Needs a developer: verifying the migration actually does what Claude says it does. If the data genuinely matters — paying customers, anything you’d have to email people about losing — this is a fine moment to have someone read it. Knowing which side of that line you’re on is the honest skill.


Keys: the decision Claude makes for you, quietly

Every row needs a way to be pointed at — a unique handle so other tables and other code can say “that one.” That handle is the primary key, and choosing it is one of the few schema decisions whose consequences show up years later.

There are two schools:

  • A natural key uses information that’s already meaningful in the real world: an email address, a username, an ISBN, a Social Security number.
  • A surrogate key uses a value invented purely to be an identifier and meaning nothing outside the system: an auto-incrementing number (1, 2, 3…) or a UUID (a long random string like f47ac10b-58cc-...).

The natural key looks obviously better. It’s already there, it’s human-readable, and it saves inventing something. This is precisely why Claude reaches for it — and it’s a documented pathology, Natural-Key Bias, catalogued in Chapter 9, Additive Bias and Calling the Question and Appendix B, The Pathology Field Guide.

Here’s what the experienced eye knows that the reasonable-sounding argument doesn’t: real-world facts change, and identifiers must not. A user changes their email address. A company rebrands and every username shifts. A country reissues its ID numbers. When the identifier is the fact, changing the fact means changing the identifier — and every row in every other table that pointed at it now points at nothing. A surrogate key can’t have that problem, because it never meant anything to begin with. Nobody ever needs to update it, because there’s no truth it could fall out of sync with.

The rule worth adopting wholesale, and worth putting in your CLAUDE.md (Chapter 7, The Control Stack) so you never have to remember it again:

Always use surrogate primary keys — a UUID or an auto-increment integer. Never use email addresses, usernames, or externally-controlled IDs as primary keys. Keep them as ordinary columns, unique if you like, but not as the key.

Note what that CLAUDE.md line is doing. It’s a guideline, the weakest rung of the control stack — but for a decision Claude makes silently and correctly-looking, a standing instruction beats catching it case by case. This is the whole reason the control stack exists.


SQLite → Postgres: the jump you’ll probably make

Almost every app that starts with Claude starts on SQLite, and that’s a good default: the entire database is one file sitting inside your project, with nothing to install, configure, or pay for. It is genuinely capable — SQLite runs on billions of phones — and for a prototype, a personal tool, or an app with one writer at a time, it may be all you ever need.

The wall it eventually hits is concurrent writes. SQLite is built around one writer at a time; when several users try to save at the same moment, they queue, and past a certain traffic level that queue becomes the thing your users experience as “the site is slow.” That is the day people move to PostgreSQL or MySQL — databases that run as their own separate service, on their own machine, built for many simultaneous users. (Chapter 2, The Ecosystem Map covers how to choose between them; this section is about the move itself.)

The move is a known, plannable operation. It is not an emergency and not a rewrite. But it is the single most reliable source of the sentence “it worked perfectly on my laptop” — because the two databases differ in exactly the ways that don’t show up until they do:

  • Types are stricter. SQLite is famously relaxed about what you put in a column; Postgres is not. Data SQLite happily accepted for months can be rejected on arrival.
  • Dates and times are handled differently, and time zones are where this bites.
  • Dialect gaps. Some SQL that works in one produces an error or, worse, a subtly different answer in the other.
  • It’s now a service. A separate thing that has to be running, reachable, credentialed, and backed up. A whole class of “can’t connect to the database” problems begins existing on this day.

The judgment call, which is yours and not Claude’s: make this jump before you need it, not during the crisis that forces it. Migrating a database under load, with real users, at the moment the app is already failing, is the same work done in the worst possible conditions. If you can see the growth coming, move early, while a mistake costs you an afternoon instead of a weekend and an apology email.

Ask Claude directly: “What in this codebase is SQLite-specific and would break on Postgres?” It’s a question you can ask today, cheaply, without committing to anything — and the answer tells you how big the eventual jump is while it’s still hypothetical.


Backups: the only insurance that exists

Everything above is risk management. This section is the actual insurance, and it is the highest-value paragraph in this chapter for the smallest amount of technical understanding.

A backup is a copy of your data as it existed at a moment in time, stored somewhere that won’t die with the original. That’s the whole concept. The nuances that matter are three:

It has to be somewhere else. A backup on the same server as the database protects you from a bad migration. It does not protect you from losing the server, which is the scenario people actually mean when they say “we lost everything.”

It has to be recent enough. Backups run on a schedule, and the gap between the last backup and the disaster is data you lose. Nightly means “up to a day of orders.” Decide what gap you can live with before something forces the answer.

And it has to have been restored — at least once, by you. This is the one that separates people who have backups from people who think they do.

The named reflex: restore-tested, or it’s not a backup. An untested backup is a belief, not a safeguard. Backups fail silently in mundane, humiliating ways — the job errored six weeks ago and nobody read the email, it’s been copying an empty database, the file is there but corrupt, nobody knows the restore procedure and it’s 2am. Every one of these is invisible until the moment you need the backup, which is the moment you cannot afford to discover it. Restore it into a scratch copy and look at the data. Then you have a backup.

This is the purest illustration of the book’s thesis in the whole chapter, so it’s worth naming: catching this requires zero code-reading. You do not need to know SQL, or what a schema is, or how the backup job is implemented. You need to ask “has anyone ever restored one of these?” and be unsatisfied with a confident yes that isn’t backed by a specific memory of doing it. That question is available to every reader of this book right now. It is also, reliably, the question nobody asks.

Ask Claude to set up backups. It will do it well. Then ask it to walk you through a restore, and watch it happen. Tag it anyone can catch this.


Your data is also a liability

One more reframe, and it’s the one non-developers are least prepared for.

While you’re building, data feels like an asset — the thing your app accumulates, the proof it’s working. It is also a liability, and the moment real people’s information is in it, that liability is legal, not just technical.

Names, emails, addresses, phone numbers, payment details, anything about health or finances or location — collectively PII (personally identifiable information) — carry obligations that exist whether or not you knew about them: how you store it, how long you keep it, who you can share it with, what you must do if it leaks. GDPR, CCPA, and their relatives don’t have a hobbyist exemption, and “an AI wrote it” is not a defense anyone has successfully used.

Two habits, both of which are judgment rather than implementation:

Don’t collect what you don’t need. Every field is a permanent obligation. Claude, left to itself, will happily generate a user table with a dozen fields because that’s what user tables look like in its training data — date of birth, phone, full address — none of which your app uses. This is Speculative Complexity (Chapter 9, Additive Bias and Calling the Question) with legal consequences. The question to ask of every column on a user table: what breaks if we don’t store this? If the answer is “nothing,” it shouldn’t exist.

Know where it lives and who can reach it. Once the database is a separate service, it has an address and a password, and the failure mode that makes the news is a database reachable from the open internet with a default password. Ask: “Is this database reachable from outside our app? Who can connect to it?”

Chapter 12, Security & Cost at SME Scale treats this properly. What belongs here is the reflex: data you didn’t need to collect is pure downside — no feature depends on it, and it can still be the thing that leaks.


The one performance thing worth caring about

You will be tempted, once you know a little, to worry about database performance. Mostly, don’t. Claude over-weighs execution speed as a rule (Chapter 9, Additive Bias and Calling the Question — the miscalibration map lists performance over-weighting explicitly), and an app with a few thousand rows is not slow for any reason a clever query will fix.

There is one exception worth carrying, because it explains the overwhelming majority of “why did the app get slow?”:

An index is a lookup shortcut for a column, exactly like the index at the back of a book. Without one, finding every order belonging to customer 4471 means the database reads every row in the table and checks each one. With ten rows, that’s instant. With ten million, it’s the reason the page takes nine seconds. Adding an index makes that lookup jump straight to the answer.

The reason this earns its place: it’s the one performance problem that is invisible during development and severe in production, because the tables you tested on were small. Everything felt fast, nothing changed, and then it wasn’t.

The whole move: when a page that used to be fast becomes slow, ask “is this query missing an index?” You will be right startlingly often, and it’s a one-line fix. That’s the entire performance section, deliberately. Everything else you might read about — query plans, connection pools, caching layers — either won’t matter at your scale or is a problem Claude should solve when it actually appears, not before. Optimizing a query that would be fine with an index is Frame-Locked Optimization (Chapter 9, Additive Bias and Calling the Question) — polishing inside the wrong frame.


The vocabulary, in one place

The terms Claude will say, with the one-line version so they stop being noise:

TermWhat it means
databaseThe part of your app that remembers things after everyone goes home.
SQLThe language for asking databases questions. Not a database itself.
tableOne kind of thing being stored — customers, orders. Like a sheet.
row / recordOne entry in a table. One customer.
column / fieldOne piece of information every row has.
schemaThe definition of what tables and columns exist and what’s allowed in them.
primary keyThe unique handle for a row. Should be meaningless (a surrogate key), not an email (a natural key).
foreign keyA column pointing at a row in another table.
UUIDA long random identifier, unique without coordination. A common surrogate key.
normalizationStoring each fact exactly once and pointing at it. DRY, for data.
indexA lookup shortcut. The fix for “why did this page get slow?”
queryA question asked of the database. Reading changes nothing.
migrationA change to the shape of a database that already holds data. The dangerous move.
transactionA group of changes that all succeed or all fail together — never half.
backup / restoreA point-in-time copy / putting it back. Untested = not a backup.
ORMA layer letting code work with objects instead of writing SQL by hand. Claude usually picks one for you.
N+1 queryA common slowness bug: asking one question per row instead of one for all of them.
connection stringThe address-and-password that lets your app reach the database. A secret — never commit it.
PIIPersonally identifiable information. An obligation, not just data.
SQLite / MySQL / PostgreSQLA database in a file / two databases that run as their own service.

Where this hooks into the rest of the book

Data is where several of the book’s arguments stop being abstract:

  • Blast radius (Chapter 9, Additive Bias and Calling the Question, theme 5) has its clearest instance here. Nearly every pathology in the catalog is additive and reversible; data loss is neither. The triage rule — gate the irreversible, wave through the reversible — is easiest to teach on a migration.
  • Natural-Key Bias (Chapter 9, Additive Bias and Calling the Question, Appendix B, The Pathology Field Guide) is a pathology this chapter gives you the vocabulary to actually act on. The catalog names the pattern; this is where you learn what a key is, so the recovery move is usable.
  • Version control (Chapter TK, Version Control) is the counterweight and the contrast. Git makes your code fearlessly reversible — and it is worth noticing that Git protects the schema files but not the data inside the database. Backups are the data’s version control, and they are a separate system you must set up on purpose.
  • The control stack (Chapter 7, The Control Stack) is how the key rule and the backup-before-migration rule stop depending on your memory — a CLAUDE.md guideline for the first, ideally a hook or gate for the second.
  • Correct by design (Chapter 6, Correct by Design) shows up as the schema itself. Constraints — required, unique, this-must-point-at-a-real-customer — are deterministic rules that make bad data impossible rather than merely detected. A constraint is a gate you never have to run.
  • Security and cost (Chapter 12, Security & Cost at SME Scale) inherits the liability half of this chapter: PII obligations, database credentials, and the fact that a separate database service is also a separate bill.
  • Testing (Chapter 11, Testing When Claude Writes the Tests Too) is where “run the migration against a copy first” becomes automated rather than remembered.

If Part I gives you the vocabulary to follow the book, this chapter gives you the one thing you cannot ask Claude to rebuild. Everything else in your project is a draft. The data is the original.


Draft notes (not for the reader)

  • This chapter is additive by explicit author decision. It does not absorb Family 12 in term-families.md, take the SQLite-vs-Postgres comparison from Chapter 2, The Ecosystem Map, or relocate Natural-Key Bias out of the pathology catalog. The precedent is version-control.md, which keeps its own “vocabulary, in one place” table while the glossary also carries the terms: the glossary is the lookup tier, the chapter is the teaching tier. They’re different jobs, not duplication. If a later pass tries to “fix” the overlap by deleting one, it’s misreading the structure.
  • The distinctive frame is “code is replaceable, data is not,” not “here’s how databases work.” Every section should be justifiable as an irreversibility or judgment point. If a section drifts toward teaching SQL usage — joins, query syntax, aggregate functions — it’s regressing into the primer this chapter exists to not be.
  • The performance section is deliberately one idea (indexes) and explicitly deprioritizes the rest. This is self-consistent with the miscalibration map in anti-patterns-catalog.md, which lists performance over-weighting as one of Claude’s characteristic errors. Don’t let this section grow; growing it would enact the pathology the book warns about.
  • Backups are the chapter’s strongest anyone can catch this moment — genuinely zero code-reading, high consequence, reliably neglected. If the chapter has to be cut for length, this section is the last thing to go. Consider whether it deserves a callout treatment in the Introduction as a thesis demonstration.
  • Audience tags follow the book convention. The migration section carries the main explicit boundary (ask the three questions = anyone; verify the migration = developer). Backups are tagged anyone. Keys are anyone via the CLAUDE.md rule.
  • Named concepts introduced: code is replaceable, data is not (chapter frame, ties to blast radius / theme 5) and restore-tested, or it’s not a backup. If either gets promoted to book-level vocabulary, add to engineering-principles-catalog.md and keep the three copies in sync the way supervise, don’t review is handled.
  • Open question — placement within Part I. Should follow Chapter 1, Building Blocks (needs Family 12’s vocabulary) and probably Chapter TK, Version Control, since the “Git protects code, backups protect data” contrast lands harder once Git is known. Number left at TK per the deferral method.
  • Open question — does this need a deep-end sibling? Version control spawned Chapter TK, Running Claude in Parallel for its advanced material. The candidate here is migrations-against-live-data and the Postgres jump as an actual operation. Deliberately not pre-committed — decide after a full read, per the TOC’s own method for Chapter 9, Additive Bias and Calling the Question.
  • Not yet covered, possibly should be: transactions get only a vocabulary-table line, and the “all or nothing” idea may deserve a paragraph (it’s the mechanism behind “the payment went through but the order didn’t”). Also unaddressed: hosted database services (Supabase, Neon, PlanetScale) — arguably Chapter 2, The Ecosystem Map’s territory, but the reader meets them the day they leave SQLite.
  • Cross-refs use xref tokens per xref-conventions.md. Registry slug added: ch-data, number null (TK).

Found something wrong, unclear, or plainly disagreeable? Open an issue