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 migrateas an explicit release step, neverpush - 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