ADR-002: Drizzle ORM over Prisma / TypeORM

Status: Accepted

Context

The project needs a database abstraction layer for PostgreSQL. The main options considered were:

ORMApproachNotes
PrismaSchema-first DSLCustom .prisma file, Prisma Client codegen, binary engine
TypeORMDecorator-basedHeavy, class-based, full runtime reflection
DrizzleTypeScript-firstSchema as TS code, SQL-like query builder, no runtime codegen
Raw pgNo abstractionFull control, but no type safety without manual interfaces

Requirements:

  • Types derived directly from the schema — no manual type definitions, no codegen that produces separate files
  • Migrations checked into source control and reviewable as plain SQL
  • Minimal runtime overhead
  • Works well with Bun (no native addons that require Node.js-specific build steps)
  • Query builder that is close to SQL so queries are predictable

Decision

Use Drizzle ORM with the postgres driver for all database access.

Key patterns enforced by convention:

// Schema defines the source of truth
export const users = pgTable('users', {
  id: uuid('id').primaryKey().defaultRandom(),
  email: varchar('email', { length: 255 }).notNull().unique(),
  // ...
});
 
// Types are always inferred — never written by hand
export type User = typeof users.$inferSelect;
export type NewUser = typeof users.$inferInsert;

Migration workflow:

  1. Edit or add a schema file in backend/db/schemas/
  2. bun run db:generate — Drizzle Kit generates a SQL migration file
  3. Review the SQL in backend/db/migrations/
  4. bun run db:migrate — applies to the database

Consequences

Positive

  • Types are always in sync with the schema — $inferSelect / $inferInsert eliminate the “type drift” problem
  • Migrations are plain SQL files: reviewable in PRs, replayable, not locked to a binary engine
  • No Prisma binary engine to download or compile — faster CI and cleaner Docker images
  • Query builder is SQL-shaped: db.select().from(users).where(eq(users.id, id)) reads like SQL
  • Zero runtime codegen: the schema file is just TypeScript imported at runtime
  • Works natively with Bun — no @prisma/engines or native addon compilation

Negative / Risks

  • Drizzle is newer than Prisma; the ecosystem of community tutorials and plugins is smaller
  • Migrations are append-only SQL files — developers must not edit them manually (regenerate instead)
  • Relations are defined separately from the table schema, which is a Drizzle convention that surprises Prisma users
  • No built-in seeding or factory utilities — backend/db/seed.ts is hand-rolled