typsichere Datenbankzugriffe direkt aus TypeScript-Schema, ohne Codegenerierung
Kein separater Generierungsschritt, kein Client-Rebuild nach jeder Schemaänderung: Drizzle leitet Query-Typen live aus dem TypeScript-Schema ab.
Inhaltsverzeichnis
- 1. Warum Drizzle anders ist als Prisma und Co.
- 2. Schema-Definition als TypeScript-Code
- 3. Typinferenz: von Schema zu Query-Ergebnis
- 4. Migrations mit drizzle-kit
- 5. Relationale Queries mit db.query
- 6. Type-safe Filter und where-Bedingungen
- 7. Drizzle mit verschiedenen Datenbanktreibern
- 8. Testing-Strategien mit Drizzle
- 9. Drizzle vs. Prisma vs. Kysely im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum Drizzle anders ist als Prisma und Co.
Die meisten TypeScript-ORMs mit starker Typsicherheit, allen voran Prisma, verlangen einen expliziten Codegenerierungsschritt: aus einer separaten Schema-Datei wird ein Client generiert, der anschließend importiert wird. Ändert sich das Schema, muss dieser Schritt erneut laufen, bevor die Typen im Editor korrekt sind.
Drizzle verzichtet komplett auf diesen Schritt. Das Schema wird direkt als TypeScript-Code geschrieben, und die Query-Typen werden über TypeScripts eigene Typinferenz aus genau diesem Code abgeleitet, ohne dass ein externer Codegenerator dazwischen sitzt.
Der praktische Effekt: eine Schemaänderung ist sofort im Editor sichtbar, es gibt keinen vergessenen generate-Befehl, der zu veralteten Typen führt, und der gesamte Build-Prozess enthält einen Schritt weniger, was besonders in CI-Pipelines Zeit spart.
Dieser Verzicht auf Codegenerierung ist keine reine Komfortfrage, sondern wirkt sich auch auf die Fehlerklasse aus, die in der Praxis am häufigsten Probleme verursacht: veraltete, aus dem generierten Client stammende Typen, die stillschweigend nicht mehr zur tatsächlichen Datenbankstruktur passen, weil ein Generierungsschritt im Deploy-Prozess vergessen wurde.
2. Schema-Definition als TypeScript-Code
Ein Drizzle-Schema besteht aus normalen exportierten Funktionsaufrufen wie pgTable, die Spalten und deren Typen deklarativ beschreiben. Es gibt keine eigene Schema-Sprache und keinen separaten Parser, das Schema ist zur Laufzeit ganz normaler, ausführbarer JavaScript-Code.
Diese Nähe zu reinem TypeScript bedeutet auch, dass sich Schema-Definitionen genauso wie jeder andere Code refaktorieren, in Module aufteilen und mit gewöhnlichen TypeScript-Tools wie ESLint oder Prettier behandeln lassen, ohne Spezial-Tooling für eine eigene Schema-Sprache.
Beziehungen zwischen Tabellen werden über Foreign-Key-Referenzen direkt an der Spaltendefinition deklariert, was den Aufbau eines Schemas für jeden lesbar macht, der bereits SQL-DDL kennt, ohne eine zusätzliche Abstraktionsebene lernen zu müssen.
import { pgTable, serial, text, integer, timestamp } from "drizzle-orm/pg-core";
export const users = pgTable("users", {
id: serial("id").primaryKey(),
email: text("email").notNull().unique(),
createdAt: timestamp("created_at").defaultNow().notNull(),
});
export const posts = pgTable("posts", {
id: serial("id").primaryKey(),
title: text("title").notNull(),
authorId: integer("author_id").notNull().references(() => users.id),
});
3. Typinferenz: von Schema zu Query-Ergebnis
Jede Query, die über den Drizzle-Client ausgeführt wird, hat einen vollständig inferierten Rückgabetyp, der exakt den ausgewählten Spalten entspricht. Wählt eine Query nur zwei von fünf Spalten aus, enthält der Rückgabetyp auch nur genau diese zwei Felder, kein any, keine manuelle Typannotation.
Diese Präzision entsteht durch generische Typparameter, die durch die Kette der aufgerufenen Methoden (select, from, where, leftJoin) hindurchgereicht werden. TypeScripts Compiler löst den finalen Typ vollständig zur Kompilierzeit auf, es gibt keine Laufzeitreflektion.
Für Entwickler, die von einem klassischen ORM mit Codegenerierung kommen, wirkt dieses Verhalten anfangs ungewohnt präzise: der Editor zeigt schon während des Tippens einer Query den exakt richtigen Ergebnistyp, lange bevor die Query jemals tatsächlich ausgeführt wird.
Für häufig genutzte Formen lassen sich mit InferSelectModel und InferInsertModel auch eigenständige Typen ableiten, etwa für DTOs in einer API-Schicht, ohne die Feldliste manuell doppelt pflegen zu müssen.
import { eq } from "drizzle-orm";
import type { InferSelectModel } from "drizzle-orm";
// Rückgabetyp wird vollständig aus der Query abgeleitet:
const result = await db
.select({ id: users.id, email: users.email })
.from(users)
.where(eq(users.id, 1));
// typeof result[number] === { id: number; email: string }
type User = InferSelectModel<typeof users>;
4. Migrations mit drizzle-kit
Das begleitende CLI-Tool drizzle-kit vergleicht das aktuelle TypeScript-Schema mit dem Zustand der Datenbank und generiert daraus SQL-Migrationsdateien. Diese Dateien sind reines, lesbares SQL und liegen im Repository, keine binäre oder proprietäre Zwischenform.
Der Befehl drizzle-kit generate erzeugt eine neue Migrationsdatei basierend auf der Differenz zum letzten bekannten Schemastand, während drizzle-kit migrate ausstehende Migrationen tatsächlich gegen die Zieldatenbank ausführt.
Weil die generierten SQL-Dateien lesbar und versionierbar sind, lassen sie sich vor dem Ausführen in einem Code-Review prüfen, was bei automatisch generierten ORM-Migrationen anderer Tools nicht immer in dieser Klarheit möglich ist.
npx drizzle-kit generate # erzeugt SQL-Migrationsdatei aus Schema-Diff
npx drizzle-kit migrate # führt ausstehende Migrationen aus
npx drizzle-kit studio # öffnet eine lokale Datenbank-GUI
5. Relationale Queries mit db.query
Neben dem SQL-nahen Query-Builder bietet Drizzle mit db.query eine deklarative API für relationale Abfragen, die verschachtelte Objekte statt flacher Join-Ergebnisse zurückgibt, ähnlich dem Komfort, den viele von Prisma kennen.
Die dafür nötigen Relationsdefinitionen werden separat vom Tabellenschema über die relations-Funktion deklariert, was Schema und Beziehungslogik sauber trennt und beide Teile unabhängig voneinander lesbar hält.
Der zurückgegebene Typ einer db.query-Abfrage mit eingebetteten Relationen ist wieder vollständig inferiert, inklusive korrekt typisierter, optional vorhandener verschachtelter Arrays bei One-to-Many-Beziehungen.
import { relations } from "drizzle-orm";
export const postsRelations = relations(posts, ({ one }) => ({
author: one(users, { fields: [posts.authorId], references: [users.id] }),
}));
const result = await db.query.posts.findMany({
with: { author: true },
});
// result[number].author ist vollständig typisiert, kein any
6. Type-safe Filter und where-Bedingungen
Filterfunktionen wie eq, gt, inArray oder and sind generisch über die jeweilige Spalte typisiert, sodass ein Vergleich zwischen einer Text-Spalte und einem numerischen Literal bereits zur Kompilierzeit als Fehler auffällt, statt erst als Datenbankfehler zur Laufzeit.
Diese Typsicherheit erstreckt sich auch auf zusammengesetzte Bedingungen: and(eq(users.id, 1), gt(posts.createdAt, someDate)) kombiniert Bedingungen über verschiedene Tabellen, wobei jede einzelne Teilbedingung weiterhin gegen die korrekte Spaltendefinition geprüft wird.
Rohes SQL bleibt trotzdem jederzeit zugänglich, über die sql-Template-Funktion, für Fälle, in denen der Query-Builder eine Datenbankfunktion nicht abbildet, wobei sich auch dort der Rückgabetyp explizit annotieren lässt.
7. Drizzle mit verschiedenen Datenbanktreibern
Drizzle ist treiberagnostisch aufgebaut: derselbe Query-Builder funktioniert mit PostgreSQL, MySQL und SQLite, jeweils über einen dünnen Adapter zum tatsächlichen Treiber, etwa node-postgres, mysql2 oder better-sqlite3.
Die Kernbibliothek bleibt dadurch klein, weil sie keine eigene Netzwerkschicht implementiert, sondern auf etablierte, bereits vorhandene Treiber aufsetzt. Das reduziert die Angriffsfläche und hält Drizzle nah an der jeweiligen nativen Treiber-Performance.
Für Edge-Umgebungen wie Cloudflare Workers oder Vercel Edge Functions gibt es zusätzlich HTTP-basierte Treiber-Adapter, etwa für Neon oder Turso, was Drizzle auch außerhalb klassischer Node.js-Serverumgebungen einsetzbar macht.
Ein Wechsel des zugrunde liegenden Treibers, etwa von node-postgres zu einem HTTP-basierten Adapter beim Umzug auf eine Edge-Umgebung, erfordert dadurch meist nur eine Änderung der Verbindungsinitialisierung, während Schema und Query-Code unverändert bleiben können.
8. Testing-Strategien mit Drizzle
Für Integrationstests eignet sich eine echte, isolierte Testdatenbank pro Testlauf am besten, weil Drizzles generierte SQL-Migrationen sich unverändert gegen eine frische SQLite- oder Postgres-Instanz anwenden lassen, ohne Mock-Schicht zwischen Test und echtem SQL.
Bei SQLite im In-Memory-Modus lassen sich komplette Testsuiten in Millisekunden gegen eine reale Datenbank ausführen, was Mocking des Query-Builders meist unnötig macht und gleichzeitig echtes SQL-Verhalten statt eines Mock-Verhaltens prüft.
Für reine Unit-Tests von Business-Logik, die Drizzle-Queries kapselt, empfiehlt sich Dependency Injection der Datenbankinstanz, sodass in Tests eine Testdatenbank statt der Produktionsverbindung eingesetzt werden kann, ohne die eigentliche Query-Logik zu verändern.
In CI-Umgebungen lässt sich eine SQLite-Testdatenbank zudem problemlos parallel pro Test-Worker instanziieren, was Testsuiten mit hoher Parallelisierung deutlich beschleunigt, ohne dass sich mehrere Testläufe gegenseitig durch geteilten Datenbankzustand beeinflussen können.
9. Drizzle vs. Prisma vs. Kysely im Vergleich
Alle drei Werkzeuge verfolgen typsichere Datenbankzugriffe in TypeScript, unterscheiden sich aber deutlich in Philosophie und Abstraktionsgrad: Prisma mit eigenem Schema und Codegenerierung, Kysely als reiner SQL-Query-Builder ohne Schema-Definition-Layer, Drizzle dazwischen mit TypeScript-nativem Schema ohne Codegenerierung.
Die folgende Tabelle stellt die wichtigsten Unterschiede gegenüber.
| Merkmal | Drizzle | Prisma | Kysely |
|---|---|---|---|
| Schema-Format | TypeScript-Code | Eigene .prisma-Sprache | Manuell definierte Interfaces |
| Codegenerierung nötig | Nein | Ja, bei jeder Schemaänderung | Nein |
| Query-Stil | SQL-nah, Builder-Pattern | Fluent API, abstrahiert von SQL | Reiner SQL-Builder |
| Bundle-Größe | Klein | Größer (Rust-Engine im Hintergrund) | Sehr klein |
| Relationale Queries | db.query API zusätzlich zu Joins | Eingebaut, sehr komfortabel | Manuell über Joins |
Mironsoft
TypeScript-Migration, Typsicherheit und Team-Onboarding
JavaScript-Codebasis ohne Typsicherheit, aber keine Zeit für eine Rundum-Migration?
Wir migrieren bestehende JavaScript-Projekte schrittweise zu TypeScript, richten strikte Compiler-Einstellungen sauber ein und bringen Teams mit Code-Reviews und Style-Guides auf denselben Typsicherheits-Stand.
Migrations-Fahrplan
Schrittweise JS-zu-TS-Migration ohne Big-Bang-Risiko planen und umsetzen.
Strict-Mode-Einführung
tsconfig.json, ESLint-Regeln und CI-Checks für dauerhafte Typsicherheit aufsetzen.
Team-Onboarding
Entwickler mit Workshops und Code-Reviews in TypeScript-Best-Practices einarbeiten.
10. Zusammenfassung
Drizzle ORM
Kernidee
Schema ist TypeScript-Code, Query-Typen entstehen durch Inferenz, keine Codegenerierung.
Migrations
drizzle-kit generiert lesbares, versionierbares SQL aus dem Schema-Diff.
Treiber
PostgreSQL, MySQL, SQLite und Edge-HTTP-Treiber über dünne Adapter.
Abgrenzung
Näher an SQL als Prisma, komfortabler bei Relationen als Kysely.