Skip to main content

Drizzle ORM — Schema Design & Best Practices

Drizzle ORM is a TypeScript-first ORM with SQL-like syntax, zero runtime overhead, and full type safety. It supports SQLite, PostgreSQL, MySQL, and more — each with driver-specific column types.

badmin

SQLite admin panel — users, roles, RBAC permissions.

Cruise Ship

PostgreSQL business system — bookings, finance, cabin allocation.

cyop

PostgreSQL AI platform — requirements, tasks, captions.

SQLite Schema (badmin)

SQLite is great for admin panels and prototypes — zero infra cost, local file DB:
SQLite stores timestamps as integers. Use { mode: "timestamp" } so Drizzle auto-converts Dateunixepoch(). The sql(unixepoch())` default gives you a server-side timestamp without app logic.

PostgreSQL Schema (Cruise Ship)

PostgreSQL gives you rich column types — numeric for money, jsonb for snapshots, text[] for arrays:

Key PostgreSQL Patterns

  1. CHECK constraints for enums — PostgreSQL doesn’t have native enums in Drizzle, so use check() with ANY (ARRAY[...]):
  1. Composite indexes — For multi-column queries, define indexes inline:
  1. Array columnstext("image_urls").array() for storing multiple URLs without a separate table:
  2. JSONB for snapshotsjsonb("item_snapshot").default([]).notNull() for revision history:

Many-to-Many Relationships (RBAC)

RBAC needs user ↔ role ↔ permission many-to-many tables:
Always add onDelete: "cascade" on many-to-many foreign keys. When a role or user is deleted, their associations should disappear automatically — no orphaned rows.

Repository Pattern

Separate data access from business logic using repository interfaces:
When loading a user for session checks, never include the password hash. Drizzle’s columns filter lets you exclude sensitive fields at the query level, not just in the service layer.

Migrations

Drizzle Kit generates SQL migrations from your schema:

Seeding

Seed the database with initial roles, permissions, and an admin user:

Best Practices

  1. SQLite for admin panels — Zero infra, local dev, good for small-to-medium row counts
  2. PostgreSQL for business data — Rich types (jsonb, arrays, CHECK constraints, numeric precision)
  3. CHECK constraints over app-level validation — Enforce enum values at the DB level
  4. Cascade deletes on M2MonDelete: "cascade" prevents orphaned association rows
  5. Exclude sensitive columnscolumns: { passwordHash: false } in session queries
  6. Repository pattern — Separate Drizzle queries from service/business logic
  7. Session versioning — Increment sessionVersion to force logout
  8. Seed with system roles — Always seed an admin role and initial permissions

References

Last modified on April 17, 2026