Test, inspect, and ship

Change drivers and ship safely

Separate portable Drizzle habits from database-specific schema, transaction, connection, and migration decisions before deploying.

Drizzle supports many databases, but switching is more than changing one import. Some of what you learned in this course transfers as-is. Some of it is SQLite-specific, and you need to know which is which before you deploy.

What transfers

The habits. Filter, order, limit. Ownership in the where. returning() instead of a second query. Constraints in the schema, reviewed migrations, tests against a real database. The query builder syntax too: select, insert, eq, and, desc look the same everywhere.

What changes with PostgreSQL

The schema imports come from drizzle-orm/pg-core, not sqlite-core. sqliteTable becomes pgTable. int().primaryKey({ autoIncrement: true }) becomes something like serial().primaryKey(), and there’s a real timestamp() type instead of storing dates as text.

The connection is a network client or pool, for example drizzle-orm/node-postgres, with a connection string in DATABASE_URL. That driver is asynchronous, so the transaction callback is async and you await inside it, unlike Node’s SQLite. And Drizzle Kit needs dialect: 'postgresql' and writes PostgreSQL SQL in the migration files.

What changes with Cloudflare D1

D1 is SQLite, so the schema stays on sqlite-core and sqliteTable. But the client is drizzle-orm/d1, created from a Worker binding rather than a filename, and multi-statement writes use db.batch() instead of a callback transaction. Migrations are applied with Wrangler as part of the deploy. The local workflow doesn’t map one to one.

Keep the seams visible

Two habits make a driver change a bounded job instead of a rewrite.

Create the client in one place, at the edge of the app, and pass it into functions rather than importing it everywhere. Our src/db/index.ts is that place. Tests already pass their own db in, so query code doesn’t care where the client came from.

Keep query functions small and named after what they do: createUserWithWelcomeNote, listNotesForUser. When the transaction API changes, you rewrite one function and its test, not every call site.

Shipping checklist

Before the first production deploy, and every one after:

  • run drizzle-kit migrate as an explicit release step, never push
  • read the generated SQL of every migration in the release
  • back up the database before a migration that alters existing tables
  • watch error rates and query latency right after the deploy
  • know how you’d roll back, and try it once on a copy

The last point is the one people skip. A rollback plan you’ve never run is a hope, not a plan.

Pick one real application you’d build. Choose between Node SQLite, PostgreSQL, and D1 based on concurrency, hosting, backups, and how migrations run there. Sketch the two files that would change, src/db/index.ts and src/db/schema.ts, and write down the deploy and rollback steps.

Lesson completed