Drizzle ORM mit Claude Code: TypeScript-First Datenbankzugriff 2026

Drizzle ORM ist die aufstrebende Alternative zu Prisma — leichtgewichtig, SQL-nah, vollständig typisiert. Keine Code-Generierung, kein Runtime-Overhead, Edge-kompatibel. Claude Code kennt Drizzle in- und auswendig: Schema-Definition, Migrations, Relations, komplexe Queries.

Drizzle vs Prisma — Wann was wählen?

KriteriumDrizzle ORMPrisma
Bundle-Size~35kb~20MB (+ Engine)
Edge Runtime✅ Cloudflare Workers, Vercel Edge❌ Node.js only
SQL-NäheSQL-ähnliche APIAbstraktion über SQL
Code-GenerierungKeine nötigprisma generate Pflicht
Type Safety100% TypeScript-native100% via Generated Types
Migrationsdrizzle-kit push/generateprisma migrate
ÖkosystemWachsendReif, große Community
Claude Code Empfehlung: Drizzle für Edge-Deployments (Cloudflare Workers, Vercel Edge) und wenn du SQL-Kontrolle willst. Prisma für klassische Node.js-Apps mit großem Team und bewährten Workflows.

Schema-Definition mit Drizzle

SchemaTypeScript-First Schema mit Claude Code

# Prompt: "Drizzle-Schema für SaaS: Users, Organizations, Projects, Tags (M:N)" // schema.ts — Claude Code generiert: import { pgTable, text, timestamp, boolean, pgEnum } from 'drizzle-orm/pg-core'; import { relations } from 'drizzle-orm'; // Enums export const planEnum = pgEnum('plan', ['free', 'pro', 'enterprise']); export const roleEnum = pgEnum('role', ['owner', 'admin', 'member']); // Users Table export const users = pgTable('users', { id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()), email: text('email').notNull().unique(), name: text('name'), createdAt: timestamp('created_at').defaultNow().notNull(), deletedAt: timestamp('deleted_at'), // Soft Delete }); // Organizations Table export const organizations = pgTable('organizations', { id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()), name: text('name').notNull(), slug: text('slug').notNull().unique(), plan: planEnum('plan').default('free').notNull(), createdAt: timestamp('created_at').defaultNow().notNull(), deletedAt: timestamp('deleted_at'), }); // Membership: Composite PK export const orgMemberships = pgTable('org_memberships', { userId: text('user_id').notNull().references(() => users.id), orgId: text('org_id').notNull().references(() => organizations.id), role: roleEnum('role').default('member').notNull(), }, (t) => ({ pk: primaryKey({ columns: [t.userId, t.orgId] }) })); // Projects export const projects = pgTable('projects', { id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()), name: text('name').notNull(), orgId: text('org_id').notNull().references(() => organizations.id), createdAt: timestamp('created_at').defaultNow().notNull(), deletedAt: timestamp('deleted_at'), }); // Tags + M:N über ProjectTags export const tags = pgTable('tags', { id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()), name: text('name').notNull(), }); export const projectTags = pgTable('project_tags', { projectId: text('project_id').notNull().references(() => projects.id), tagId: text('tag_id').notNull().references(() => tags.id), }, (t) => ({ pk: primaryKey({ columns: [t.projectId, t.tagId] }) })); // Relations (für typed Joins) export const usersRelations = relations(users, ({ many }) => ({ memberships: many(orgMemberships), })); export const projectsRelations = relations(projects, ({ one, many }) => ({ org: one(organizations, { fields: [projects.orgId], references: [organizations.id] }), tags: many(projectTags), }));

Drizzle Kit: Migrations

MigrationSchema-First Migrations mit drizzle-kit

# drizzle.config.ts import { defineConfig } from 'drizzle-kit'; export default defineConfig({ schema: './src/db/schema.ts', out: './drizzle', // Migration-Dateien dialect: 'postgresql', dbCredentials: { url: process.env.DATABASE_URL!, }, }); # Development: Schema pushen (ohne Migration-Files) npx drizzle-kit push # Production: Migration-Files generieren + anwenden npx drizzle-kit generate # Erstellt SQL in /drizzle/ npx drizzle-kit migrate # Wendet Migrations an # Studio: visuelles DB-UI (ähnlich Prisma Studio) npx drizzle-kit studio
Dev vs. Production: push ist für Development ideal (schnell, kein Migration-File). generate + migrate für Production — damit bleibt die Migration-History erhalten und ist versioniert.

Queries: SQL-nah aber typisiert

QueriesFiltering, Joins, Aggregation

// db.ts — Drizzle Client initialisieren import { drizzle } from 'drizzle-orm/postgres-js'; import postgres from 'postgres'; import * as schema from './schema'; const client = postgres(process.env.DATABASE_URL!); export const db = drizzle(client, { schema }); // Cloudflare Workers: anderer Adapter import { drizzle } from 'drizzle-orm/neon-http'; import { neon } from '@neondatabase/serverless'; const sql = neon(process.env.DATABASE_URL!); export const db = drizzle(sql, { schema });
# Prompt: "Lade aktive Projects einer Org mit Tags, gefiltert + paginiert" import { eq, isNull, ilike, and } from 'drizzle-orm'; // Query Builder API (typisiert) const results = await db.query.projects.findMany({ where: and( eq(projects.orgId, orgId), isNull(projects.deletedAt), // Soft-Delete Filter ilike(projects.name, `%${search}%`) // Case-insensitive Suche ), with: { tags: { with: { tag: true } // M:N via Zwischentabelle }, org: true }, limit: 20, offset: (page - 1) * 20, orderBy: (p, { desc }) => [desc(p.createdAt)], }); // SQL-ähnliche API (mehr Kontrolle) const raw = await db .select({ id: projects.id, name: projects.name, tagCount: sql`count(${projectTags.tagId})`, }) .from(projects) .leftJoin(projectTags, eq(projects.id, projectTags.projectId)) .where(and(eq(projects.orgId, orgId), isNull(projects.deletedAt))) .groupBy(projects.id) .orderBy(desc(projects.createdAt));

Transactions

# Prompt: "User + Organization atomar erstellen" async function createUserWithOrg(email: string, name: string, orgName: string) { return db.transaction(async (tx) => { // tx = transaktionaler Drizzle-Client const [user] = await tx.insert(users) .values({ email, name }) .returning(); const [org] = await tx.insert(organizations) .values({ name: orgName, slug: slugify(orgName) + '-' + nanoid(4), }) .returning(); await tx.insert(orgMemberships).values({ userId: user.id, orgId: org.id, role: 'owner', }); return { user, org }; // Bei Fehler: automatischer Rollback! }); }

Edge-Deployment: Drizzle auf Cloudflare Workers

EdgeCloudflare Workers + Neon Serverless

// worker.ts — Drizzle auf Cloudflare Workers import { Hono } from 'hono'; import { drizzle } from 'drizzle-orm/neon-http'; import { neon } from '@neondatabase/serverless'; import { users } from './schema'; const app = new Hono<{ Bindings: { DATABASE_URL: string } }>(); app.get('/users', async (c) => { const sql = neon(c.env.DATABASE_URL); const db = drizzle(sql); const allUsers = await db.select().from(users); return c.json(allUsers); }); export default app;
Warum Drizzle für Edge? Prisma hat einen nativen Node.js Query Engine (~20MB). Drizzle ist pure TypeScript, kein Binary — deshalb Edge-kompatibel. Mit Neon Serverless (HTTP-basiert, kein TCP) läuft es auf Cloudflare Workers ohne Workarounds.

Performance und Best Practices

PerformanceQuery-Optimierung mit Claude Code

# Prompt: "Optimiere diese Drizzle-Queries für Production" // ❌ SCHLECHT: N Queries für N Projects const projectsWithTags = await Promise.all( projects.map(async (p) => ({ ...p, tags: await db.select().from(projectTags).where(eq(projectTags.projectId, p.id)) })) ); // ✅ GUT: Einzelne Query mit .with() const projectsWithTags = await db.query.projects.findMany({ with: { tags: { with: { tag: true } } }, }); // Prepared Statements für häufige Queries: const getUserByEmail = db .select() .from(users) .where(eq(users.email, sql.placeholder('email'))) .prepare('getUserByEmail'); // Später: kompiliert, kein SQL-Parsing-Overhead const user = await getUserByEmail.execute({ email: 'user@example.com' }); // Index via Schema hinzufügen: export const projectsIndexes = pgTable('projects', { // ... columns }, (table) => ({ orgDeletedIdx: index('projects_org_deleted_idx').on(table.orgId, table.deletedAt), }));
Drizzle Prompt-Tipp: "Analysiere diese Drizzle-Queries auf N+1-Probleme, Missing Indexes und fehlende Prepared Statements. Zeige die optimierte Version mit Erklärung." Claude Code kennt Drizzle-spezifische Patterns und schlägt `.with()` statt N+1-Loops vor.

Drizzle + Edge-Modul im Kurs

Im Claude Code Mastery Kurs: vollständiges Drizzle-Modul mit Schema-Design, Migrations, Relations, Transactions und Edge-Deployment auf Cloudflare Workers — inkl. Vergleich mit Prisma und Performance-Optimierung.

14 Tage kostenlos testen →