EMZETT.
Login

Drizzle ORM

Kurz: Eine TypeScript-Bibliothek, mit der man Datenbank-Tabellen als Code definiert (statt reinem SQL) und daraus automatisch das passende Datenbankschema erzeugen/aktualisieren lässt.

Genauer: Die Tabellen werden in einer Schema-Datei (z. B. db/schema.ts) über pgTable(...) deklariert. npx drizzle-kit push vergleicht diese Definition mit der echten Datenbank und gleicht sie an. drizzle.config.ts sagt Drizzle, wo Schema und Datenbank liegen.

Kontext bei uns: Schema-Änderungen (z. B. neue Spalten für Software-Downloads) wurden zunächst nur per drizzle-kit push lokal getestet — was einmal dazu führte, dass die Produktions-DB nicht mitzog und die Admin-Seite abstürzte. Seitdem wird zu jeder Schema-Änderung zusätzlich eine SQL-Migrationsdatei mitgeliefert.

Im Detail

push vs. generate/migrate

Drizzle unterscheidet zwei Arbeitsweisen, die leicht verwechselt werden: drizzle-kit push gleicht das Schema direkt live gegen die Datenbank ab (schnell, aber ohne nachvollziehbare Historie — praktisch für lokale Entwicklung und schnelles Prototyping), während drizzle-kit generate eine echte, versionierte SQL-Migrationsdatei erzeugt, die man committet und über drizzle-kit migrate kontrolliert gegen jede Umgebung (Staging, Produktion) ausführt. Nur der zweite Weg hinterlässt eine nachvollziehbare Historie aller Schema-Änderungen im Repo — jede Migrationsdatei ist eine eigenständige .sql-Datei mit fortlaufender Nummer, die exakt zeigt, welche ALTER TABLE/CREATE TABLE-Anweisungen wann ausgeführt wurden. Das ist auch aus Compliance-/Audit-Sicht relevant: Bei push gibt es keinen Nachweis, WANN sich das Schema wie verändert hat, bei generate+migrate schon.

Schema-Definition

Ein einfaches Tabellen-Schema sieht z. B. so aus:

export const users = pgTable("users", {
  id: uuid("id").primaryKey().defaultRandom(),
  email: text("email").notNull().unique(),
  createdAt: timestamp("created_at").defaultNow(),
});
 
export const posts = pgTable("posts", {
  id: uuid("id").primaryKey().defaultRandom(),
  authorId: uuid("author_id").references(() => users.id, { onDelete: "cascade" }),
  title: text("title").notNull(),
});
 
// Beziehungen für typsichere JOIN-Queries (relational query API)
export const postsRelations = relations(posts, ({ one }) => ({
  author: one(users, { fields: [posts.authorId], references: [users.id] }),
}));

Das relations()-Konstrukt ist rein für TypeScript-Typinferenz und die relationale Query-API gedacht (db.query.posts.findMany({ with: { author: true } })) — es erzeugt selbst KEINE Foreign-Key-Constraints in der Datenbank, das macht ausschließlich .references() in der Tabellendefinition.

Abgrenzung zu Prisma und rohem SQL

Der Query-Builder ist bewusst nah an echtem SQL gehalten (im Gegensatz zu stärker abstrahierten ORMs wie Prisma, das eine eigene Query-Sprache und einen separaten Codegenerierungsschritt verwendet) — db.select().from(users).where(eq(users.email, "...")) liest sich fast wie die SQL-Query selbst, was Debugging erleichtert, weil man leichter nachvollziehen kann, welches SQL am Ende tatsächlich ausgeführt wird (Drizzle bietet dafür auch .toSQL(), das die generierte Query samt Parametern ausgibt). Prisma bringt dafür mit Prisma Studio ein ausgereifteres visuelles Admin-Interface mit, während Drizzles Pendant (Drizzle Studio) schlanker ausfällt. Ein praktischer Unterschied im Alltag: Drizzle hat keinen eigenen Codegenerierungs-Build-Schritt (das Schema IST bereits normaler TypeScript-Code), während Prisma nach jeder Schema-Änderung prisma generate braucht, um den typsicheren Client neu zu erzeugen — das macht Drizzles Entwicklungsschleife bei häufigen Schema-Änderungen tendenziell schneller.

Typische Stolperfallen

Ein häufiger Fehler ist, push in Produktion zu verwenden, obwohl es primär für lokale Entwicklung gedacht ist — ohne Migrationshistorie besteht die Gefahr, dass push bei komplexeren Änderungen (z. B. eine Spalte umbenennen) das als “Spalte löschen + neue Spalte anlegen” interpretiert und dabei bestehende Daten in der Spalte verliert, statt sie umzubenennen. drizzle-kit generate erkennt solche Umbenennungen interaktiv nach und fragt gezielt nach, ob es sich um ein Rename oder ein echtes Drop+Create handelt.

Siehe auch: Neon