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?
| Kriterium | Drizzle ORM | Prisma |
|---|---|---|
| Bundle-Size | ~35kb | ~20MB (+ Engine) |
| Edge Runtime | ✅ Cloudflare Workers, Vercel Edge | ❌ Node.js only |
| SQL-Nähe | SQL-ähnliche API | Abstraktion über SQL |
| Code-Generierung | Keine nötig | prisma generate Pflicht |
| Type Safety | 100% TypeScript-native | 100% via Generated Types |
| Migrations | drizzle-kit push/generate | prisma migrate |
| Ökosystem | Wachsend | Reif, 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 →