TypeScript mit Drizzle ORM: typsichere Datenbankzugriffe ohne Codegenerierung
AI generated
type
TypeScript
Drizzle ORM
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.

10 Min. Lesezeit TypeScript 5.x Datenbank

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.

11. FAQ: Drizzle ORM

1Braucht Drizzle einen Build- oder Generierungsschritt vor der Nutzung?
Nein, das Schema ist direkt ausführbarer TypeScript-Code, Query-Typen werden zur Kompilierzeit durch normale TypeScript-Inferenz abgeleitet. Ein separater Codegenerator wie bei Prisma ist nicht nötig.
2Unterstützt Drizzle Transaktionen?
Ja, über db.transaction, das einen Callback mit einer transaktionsgebundenen Datenbankinstanz übergibt. Bei einem Fehler innerhalb des Callbacks wird automatisch ein Rollback ausgeführt.
3Kann ich mit Drizzle rohes SQL ausführen, wenn der Query-Builder nicht ausreicht?
Ja, über die sql-Template-Funktion lässt sich beliebiges SQL einbetten, wobei Platzhalter weiterhin sicher parametrisiert werden und der Rückgabetyp explizit annotiert werden kann.
4Wie migriere ich ein bestehendes Prisma-Projekt zu Drizzle?
Es gibt kein offizielles automatisches Migrationswerkzeug zwischen den Schema-Formaten, die Tabellenstruktur muss manuell als Drizzle-Schema nachgebildet werden. drizzle-kit kann anschließend aus einer bestehenden Datenbank ein Ausgangs-Schema introspektieren.
5Ist Drizzle für Serverless- und Edge-Umgebungen geeignet?
Ja, insbesondere durch HTTP-basierte Treiber für Anbieter wie Neon oder Turso, die ohne dauerhafte TCP-Verbindung auskommen und sich für Cloudflare Workers oder Vercel Edge Functions eignen.
6Wie werden Many-to-Many-Beziehungen in Drizzle modelliert?
Über eine explizite Zwischentabelle mit zwei Foreign Keys, genau wie in reinem SQL. Die relations-Funktion beschreibt anschließend beide Seiten der Beziehung für den komfortablen db.query-Zugriff.
7Generiert drizzle-kit die Migrationsdateien automatisch beim Start der Anwendung?
Nein, Migrationsgenerierung und -ausführung sind bewusst getrennte, manuell ausgelöste CLI-Befehle, um versehentliche Schemaänderungen in Produktion zu vermeiden.
8Kann ich Drizzle-Schemas auf mehrere Dateien aufteilen?
Ja, da das Schema aus normalen TypeScript-Exporten besteht, lässt es sich wie jeder andere Code modularisieren und über import/export zusammenführen, ohne Einschränkungen durch eine eigene Schema-Sprache.
9Wie gut ist die Editor-Unterstützung bei komplexen Joins?
Sehr gut, weil jede Methode im Query-Builder generische Typparameter weitergibt und der Editor bei jedem Zwischenschritt den exakten aktuellen Ergebnistyp per Hover anzeigt, auch bei mehreren verketteten Joins.
10Lohnt sich Drizzle für ein kleines Projekt mit wenigen Tabellen?
Ja, der Einstiegsaufwand ist gering, da kein Codegenerierungsschritt konfiguriert werden muss und das Schema sofort als lesbarer TypeScript-Code vorliegt, auch für nur zwei oder drei Tabellen.