Skip to content
All articles
Articlevibe-codingpatternsdatabasesai

Vibe-Coding with a Database: 10 Patterns That Don't Break

Ten concrete patterns for keeping AI-paired database work clean: typed schemas, migration discipline, RLS-as-authz, write-confirmation gates, and the anti-patterns to avoid.

13 min read

We've audited dozens of AI-paired codebases in the last 18 months. The ones that ship clean and the ones that bog down look almost identical in week one. The difference is a small set of patterns the clean ones used from the start.

Here are the ten that matter most when you're building with an AI doing most of the database typing.

What keeps breaking

The recurring failure modes in vibe-coded database work:

  • The schema in your repo drifts from production because the agent ran ad-hoc ALTER TABLE.
  • The agent invents column names; the next ten PRs carry the bug.
  • The agent disables RLS "to test" and forgets to put it back.
  • A write goes through with no record because the agent didn't know about your audit-logging convention.
  • The agent silently uses an ORM's "skip type checking" escape hatch.

Each one has a concrete pattern that prevents it.

The 10 patterns

1. Generated types, committed to git

Whichever ORM you use (Drizzle, Prisma, sqlc), the type-generation step should run on every schema change and the output should be committed. Not in .gitignore. The agent reads those types every turn.

schema.tsts
// Drizzle example. The agent sees this exact shape every turn.
import { pgTable, uuid, text, timestamp } from "drizzle-orm/pg-core";

export const posts = pgTable("posts", {
  id:         uuid("id").primaryKey().defaultRandom(),
  authorId:   uuid("author_id").notNull(),
  title:      text("title").notNull(),
  content:    text("content"),
  status:     text("status").$type<"draft" | "published">().notNull(),
  createdAt:  timestamp("created_at").defaultNow().notNull(),
});

2. One migration file per change, always

Tell the agent in your project README: "every schema change is a migration in drizzle/. Never modify production directly." Most agents will follow this when it's in the repo's top-level docs.

3. A schema snapshot test

A test that dumps the schema and compares it to a snapshot file. Any drift surfaces as a PR diff. Catches the "agent edited production directly" case before it ships.

4. RLS on day one, never off

Enable Row-Level Security on every user-facing table before you write the first row. The agent will respect it once it sees the first policy. (See our RLS guidefor the policy patterns.)

5. Never let the agent write directly with service_role

The agent's default Supabase key in dev should be the anon key. Make "use service_role" an explicit, audited step in your code. Bugs that bypass RLS through service_role are the single most expensive incident class.

6. Wrap writes in a single audited path

Every INSERT/UPDATE/DELETE in your app goes through one helper that writes an audit log entry. The agent finds that helper quickly and uses it for new mutations.

audited-write.tsts
export async function recordAndApply<T>(
  user: User,
  args: { action: "insert" | "update" | "delete"; table: string; pk?: string },
  apply: () => Promise<T>,
): Promise<T> {
  const result = await apply();
  await db.insert(auditLog).values({
    userId: user.id,
    action: args.action,
    table:  args.table,
    pk:     args.pk,
    at:     new Date(),
  });
  return result;
}

7. Confirm-before-execute for AI-proposed writes

If you use an AI assistant inside your admin tool, the agent should never write directly. It proposes; the human confirms in the UI; the server re-validates and writes. The pattern is documented in our AI admin article and shipped in our chat assistant.

8. Type-narrow your enums

Postgres has check constraints; SQLite has check constraints; both also let your ORM declare an enum type. Use both. The DB enforces valid values; the type system tells the agent which values exist.

9. Make "run the migrations" one command

pnpm db:migrate or equivalent. The agent will run it before every test cycle. Local dev environments that are one command away from production parity are vastly more agent-friendly.

10. Snapshot your schema diagrams in the repo

A short markdown file with an ASCII or mermaid diagram showing your main entities and their relationships. The agent reads it when it starts working on a new feature; you avoid the "every feature ignores the relationships" failure mode.

Anti-patterns

Things to actively avoid:

  • "Just let the agent run SQL" as a development workflow. Agents in 2026 hallucinate confident SQL. Type-checked queries through an ORM catch this; raw SQL doesn't.
  • Custom DSLs the agent doesn't know. If you invent a query helper the public corpus has never seen, the agent will misuse it on the third PR.
  • Multi-step migrations performed in a single deploy. The agent will try to combine the "add column" + "backfill" + "add NOT NULL" steps that should be three separate deploys. Spell that constraint out in your repo.
  • Trusting the agent to write tests. Especially negative-path tests ("the user can't read someone else's row"). Agents tend to write the happy path and skip the negative. Specify them.
  • Live dashboards as the source of schema truth. The schema lives in code. The dashboard reflects it. If anyone (human or agent) edits the dashboard's schema directly, you'll find out at the worst possible time.

A starter prompt

For new vibe-coded projects, drop this into your project README or agent rules file:

.cursorrules / AGENTS.md
When working on database code:

1. Every schema change is a migration file in `drizzle/`. Never
   ALTER TABLE in production. Run `pnpm db:migrate` after every
   migration is added.

2. Read `src/db/schema.ts` before writing any query. Use the types
   from `drizzle-orm` and the inferred row types. Do not invent
   column names.

3. Row-Level Security stays on. Every new table needs a policy in
   the migration that creates the table.

4. Every INSERT / UPDATE / DELETE goes through `recordAndApply`
   in `src/lib/audit.ts`. The audit log is non-negotiable.

5. When in doubt, use `@/db/types`. If a value's type is `string`
   but should be a tagged union, fix the schema, not the type cast.

Agents in 2026 follow rule files quite consistently. The five rules above prevent ~80% of the vibe-coded database bugs we've seen in customer codebases. None of them are exotic; they're the discipline that good Postgres teams have always used. The AI just makes them more important.

The shortest version: write down what your project assumes, in the repo, in plain English. The agent reads it. The next engineer reads it. Your future self reads it. Software gets better in the order it's written down.

Suparbase is an admin workspace for Supabase. Encrypted credentials, server-side proxy, RLS debugger, SQL playground, AI assistant with diff-confirmed writes. Free tier for solo projects.

Related articles