Drizzle ORM + Cloudflare D1: 99% kleineres Bundle als Prisma

Warum ORMs auf dem Edge ein Problem sind
Haben Sie jemals Prisma auf Cloudflare Workers verwendet und extrem langsame Cold-Start-Zeiten erlebt? Der Grund dafür ist einfach: Die Bundle-Größe von Prisma beträgt 1.6 MB. Obwohl das Skriptgrößenlimit von Workers 10 MB beträgt, verdampfen durch das Laden eines 1.6 MB großen ORMs Hunderte von Millisekunden beim Cold Start.
Drizzle ORM wurde nach einer völlig entgegengesetzten Philosophie entwickelt. Die Bundle-Größe beträgt 12.2 KB (min+gzip) – das ist 99.2% kleiner im Vergleich zu Prisma. Es gibt keinen Codegenerierungsschritt und keine Rust-Engine. Es umschließt SQL mit reinem TypeScript. Daher funktioniert es in Edge-Runtimes ideal.
In diesem Artikel wird Schritt für Schritt erklärt, wie Sie durch die Kombination von Drizzle ORM und Cloudflare D1 eine vollständig typsichere Edge-Datenbankarchitektur aufbauen. Sie erstellen eine REST-API mit Hono, verwalten Migrationen mit drizzle-kit und richten eine lokale Entwicklungsumgebung ein.
Drizzle ORM vs. Prisma: Edge-Umgebungsvergleich
Beim Vergleich der beiden ORMs in einer realen Produktionsumgebung wird der Unterschied deutlich.
| Kriterium | Drizzle ORM | Prisma 7+ |
|---|---|---|
| Bundle-Größe (min+gzip) | 12.2 KB | 1.6 MB |
| Auswirkung auf Cold Start | Minimal (~50-100ms) | Gering (~80-150ms) |
| Edge-Runtime-Unterstützung | First-Class | Unterstützt ab v7 |
| Codegenerierungsschritt | Keiner | Vorhanden (prisma generate) |
| Typinferenzmethode | Direkt über TypeScript | Generierter Client |
| SQL-Kontrollgrad | Hoch (SQL-first) | Mittel (Abstraktion) |
| Offizieller D1-Treiber | drizzle-orm/d1 |
Keiner (Community) |
| Lernkurve | Mittel | Gering |
Es trifft zu, dass Prisma 7 die Rust-Engine entfernt hat und auf reines TypeScript/WASM umgestiegen ist. Dennoch besteht bei der Bundle-Größe weiterhin ein 130-facher Unterschied. In einer Umgebung wie Workers, in der bei jeder Anfrage ein Cold Start auftreten kann, ist dieser Unterschied entscheidend.
Was ist Cloudflare D1?
D1 ist die serverlose SQLite-Datenbank von Cloudflare. Da der Zugriff direkt über Workers-Bindings erfolgt, entsteht kein zusätzlicher TCP-Verbindungs-Overhead. D1 unterstützt globale Lese-Replikate und erlaubt selbst im kostenlosen Tarif bis zu 5 Millionen Abfragen pro Tag.
Cloudflare Workers Anfrage
│
▼
D1-Binding (env.DB)
│ (IPC im selben Rechenzentrum)
▼
SQLite-Datenbank
(D1 Serverless-Instanz)
Der entscheidende Punkt ist, dass auf D1 über Bindings ohne HTTP-Anfragen zugegriffen wird. Die Latenz beträgt lediglich ein Zehntel einer externen Datenbankverbindung.
Projekt-Ersteinrichtung
1. Hono + Drizzle + D1 Scaffolding
# Hono Worker-Vorlage erstellen
npm create cloudflare@latest my-d1-app -- --template hono
cd my-d1-app
# Drizzle installieren
npm install drizzle-orm
npm install -D drizzle-kit
2. D1-Datenbank erstellen
# D1-Datenbank erstellen
npx wrangler d1 create my-app-db
# Ausgabebeispiel:
# [[d1_databases]]
# binding = "DB"
# database_name = "my-app-db"
# database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
3. wrangler.jsonc Konfiguration
// wrangler.jsonc
{
"name": "my-d1-app",
"main": "src/index.ts",
"compatibility_date": "2025-01-01",
"compatibility_flags": ["nodejs_compat"],
"d1_databases": [
{
"binding": "DB",
"database_name": "my-app-db",
"database_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"migrations_dir": "drizzle/migrations"
}
]
}
4. drizzle.config.ts Konfiguration
// drizzle.config.ts
import { defineConfig } from "drizzle-kit";
export default defineConfig({
dialect: "sqlite",
schema: "./src/db/schema.ts",
out: "./drizzle/migrations",
driver: "d1-http",
dbCredentials: {
accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
databaseId: process.env.CLOUDFLARE_DATABASE_ID!,
token: process.env.CLOUDFLARE_D1_TOKEN!,
},
});
Tipp für die lokale Entwicklung: Da lokal
wrangler d1 migrations applyverwendet wird, dienendriverunddbCredentialsindrizzle.config.tsausschließlich für Remote-Migrationen in der Produktionsumgebung.
Schema-Definition: TypeScript-First
Die Kernstärke von Drizzle liegt darin, dass Schemadefinition und Typinferenz in einem Schritt vereint sind. Es ist kein separater Schritt wie prisma generate erforderlich.
// src/db/schema.ts
import {
sqliteTable,
text,
integer,
real,
blob,
} from "drizzle-orm/sqlite-core";
import { sql } from "drizzle-orm";
// 유저 테이블
export const users = sqliteTable("users", {
id: integer("id").primaryKey({ autoIncrement: true }),
email: text("email").notNull().unique(),
name: text("name").notNull(),
role: text("role", { enum: ["admin", "user", "guest"] })
.notNull()
.default("user"),
createdAt: text("created_at")
.notNull()
.default(sql`(datetime('now'))`),
updatedAt: text("updated_at")
.notNull()
.default(sql`(datetime('now'))`),
});
// 포스트 테이블
export const posts = sqliteTable("posts", {
id: integer("id").primaryKey({ autoIncrement: true }),
title: text("title").notNull(),
slug: text("slug").notNull().unique(),
content: text("content").notNull(),
authorId: integer("author_id")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
published: integer("published", { mode: "boolean" }).default(false),
viewCount: integer("view_count").default(0),
publishedAt: text("published_at"),
createdAt: text("created_at")
.notNull()
.default(sql`(datetime('now'))`),
});
// 태그 테이블 (M:N 관계)
export const tags = sqliteTable("tags", {
id: integer("id").primaryKey({ autoIncrement: true }),
name: text("name").notNull().unique(),
slug: text("slug").notNull().unique(),
});
export const postTags = sqliteTable("post_tags", {
postId: integer("post_id")
.notNull()
.references(() => posts.id, { onDelete: "cascade" }),
tagId: integer("tag_id")
.notNull()
.references(() => tags.id, { onDelete: "cascade" }),
});
// 타입 추론 — 코드 생성 없이 즉시 사용 가능
export type User = typeof users.$inferSelect;
export type NewUser = typeof users.$inferInsert;
export type Post = typeof posts.$inferSelect;
export type NewPost = typeof posts.$inferInsert;
export type Tag = typeof tags.$inferSelect;
Der entscheidende Unterschied zu Prisma: Sie müssen prisma generate nicht ausführen. Die Schemadatei selbst ist die Quelle der TypeScript-Typen. Die automatische Vervollständigung der IDE funktioniert sofort.
Migrations-Workflow
SQL-Migrationsdateien generieren
npx drizzle-kit generate
Dieser Befehl generiert SQL-Dateien im Ordner drizzle/migrations/:
-- drizzle/migrations/0000_initial.sql
CREATE TABLE `users` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`email` text NOT NULL,
`name` text NOT NULL,
`role` text DEFAULT 'user' NOT NULL,
`created_at` text DEFAULT (datetime('now')) NOT NULL,
`updated_at` text DEFAULT (datetime('now')) NOT NULL
);
--> statement-breakpoint
CREATE UNIQUE INDEX `users_email_unique` ON `users` (`email`);
--> statement-breakpoint
CREATE TABLE `posts` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`title` text NOT NULL,
`slug` text NOT NULL,
`content` text NOT NULL,
`author_id` integer NOT NULL,
`published` integer DEFAULT false,
`view_count` integer DEFAULT 0,
`published_at` text,
`created_at` text DEFAULT (datetime('now')) NOT NULL,
FOREIGN KEY (`author_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
Migrationen anwenden
# Auf lokales D1 anwenden (Entwicklung)
npx wrangler d1 migrations apply DB --local
# Auf Produktions-D1 anwenden
npx wrangler d1 migrations apply DB --remote
Wrangler verwaltet den Ausführungsverlauf über die Tabelle d1_migrations. Sie müssen sich keine Sorgen über doppelte Ausführungen machen.
Beispiel für Schemaänderungen
# Nach Schemaänderung neue Migration generieren
npx drizzle-kit generate
# Generierte Migration überprüfen und anwenden
npx wrangler d1 migrations apply DB --local
Workers + Hono Integration
DB-Client-Initialisierung
// src/db/client.ts
import { drizzle } from "drizzle-orm/d1";
import * as schema from "./schema";
export type Env = {
DB: D1Database;
};
export function createDb(d1: D1Database) {
return drizzle(d1, { schema });
}
export type DrizzleDb = ReturnType<typeof createDb>;
Hono-App-Konfiguration
// src/index.ts
import { Hono } from "hono";
import { createDb, type Env } from "./db/client";
import { usersRouter } from "./routes/users";
import { postsRouter } from "./routes/posts";
const app = new Hono<{ Bindings: Env }>();
// DB-Middleware
app.use("*", async (c, next) => {
c.set("db", createDb(c.env.DB));
await next();
});
app.route("/api/users", usersRouter);
app.route("/api/posts", postsRouter);
app.get("/health", (c) => c.json({ status: "ok" }));
export default app;
Users-Router — Vollständiges CRUD
// src/routes/users.ts
import { Hono } from "hono";
import { eq, like, desc, count } from "drizzle-orm";
import { users, posts, type NewUser } from "../db/schema";
import type { DrizzleDb, Env } from "../db/client";
const usersRouter = new Hono<{
Bindings: Env;
Variables: { db: DrizzleDb };
}>();
// GET /api/users — 페이지네이션 + 검색
usersRouter.get("/", async (c) => {
const db = c.get("db");
const { page = "1", limit = "20", q } = c.req.query();
const pageNum = Math.max(1, parseInt(page));
const limitNum = Math.min(100, parseInt(limit));
const offset = (pageNum - 1) * limitNum;
// WHERE 조건 동적 구성
const whereClause = q ? like(users.name, `%${q}%`) : undefined;
const [userList, totalResult] = await Promise.all([
db
.select()
.from(users)
.where(whereClause)
.orderBy(desc(users.createdAt))
.limit(limitNum)
.offset(offset)
.all(),
db
.select({ count: count() })
.from(users)
.where(whereClause)
.get(),
]);
return c.json({
data: userList,
pagination: {
page: pageNum,
limit: limitNum,
total: totalResult?.count ?? 0,
hasNext: offset + limitNum < (totalResult?.count ?? 0),
},
});
});
// GET /api/users/:id — 유저 + 포스트 수 조회
usersRouter.get("/:id", async (c) => {
const db = c.get("db");
const id = parseInt(c.req.param("id"));
const user = await db
.select({
id: users.id,
email: users.email,
name: users.name,
role: users.role,
createdAt: users.createdAt,
postCount: count(posts.id),
})
.from(users)
.leftJoin(posts, eq(posts.authorId, users.id))
.where(eq(users.id, id))
.groupBy(users.id)
.get();
if (!user) {
return c.json({ error: "User not found" }, 404);
}
return c.json(user);
});
// POST /api/users — 유저 생성
usersRouter.post("/", async (c) => {
const db = c.get("db");
const body = await c.req.json<NewUser>();
// 이메일 중복 검사
const existing = await db
.select({ id: users.id })
.from(users)
.where(eq(users.email, body.email))
.get();
if (existing) {
return c.json({ error: "Email already exists" }, 409);
}
const [newUser] = await db
.insert(users)
.values({
email: body.email,
name: body.name,
role: body.role ?? "user",
})
.returning();
return c.json(newUser, 201);
});
// PATCH /api/users/:id — 부분 업데이트
usersRouter.patch("/:id", async (c) => {
const db = c.get("db");
const id = parseInt(c.req.param("id"));
const body = await c.req.json<Partial<NewUser>>();
const [updated] = await db
.update(users)
.set({
...body,
updatedAt: new Date().toISOString(),
})
.where(eq(users.id, id))
.returning();
if (!updated) {
return c.json({ error: "User not found" }, 404);
}
return c.json(updated);
});
// DELETE /api/users/:id
usersRouter.delete("/:id", async (c) => {
const db = c.get("db");
const id = parseInt(c.req.param("id"));
const [deleted] = await db
.delete(users)
.where(eq(users.id, id))
.returning({ id: users.id });
if (!deleted) {
return c.json({ error: "User not found" }, 404);
}
return c.json({ message: "Deleted", id: deleted.id });
});
export { usersRouter };
Fortgeschrittene Abfragen: Beziehungsabfragen
// src/routes/posts.ts — 관계 데이터 JOIN
import { Hono } from "hono";
import { eq, and, desc, inArray } from "drizzle-orm";
import { posts, users, tags, postTags } from "../db/schema";
import type { DrizzleDb, Env } from "../db/client";
const postsRouter = new Hono<{
Bindings: Env;
Variables: { db: DrizzleDb };
}>();
// GET /api/posts/:slug — 포스트 상세 (유저 + 태그 포함)
postsRouter.get("/:slug", async (c) => {
const db = c.get("db");
const slug = c.req.param("slug");
// 1. 포스트 + 작성자 조회
const post = await db
.select({
id: posts.id,
title: posts.title,
slug: posts.slug,
content: posts.content,
published: posts.published,
viewCount: posts.viewCount,
publishedAt: posts.publishedAt,
author: {
id: users.id,
name: users.name,
email: users.email,
},
})
.from(posts)
.innerJoin(users, eq(posts.authorId, users.id))
.where(and(eq(posts.slug, slug), eq(posts.published, true)))
.get();
if (!post) {
return c.json({ error: "Post not found" }, 404);
}
// 2. 태그 조회 (별도 쿼리 — D1은 복잡한 서브쿼리보다 단순 쿼리 2개가 빠름)
const postTagList = await db
.select({ name: tags.name, slug: tags.slug })
.from(tags)
.innerJoin(postTags, eq(postTags.tagId, tags.id))
.where(eq(postTags.postId, post.id))
.all();
// 3. 조회수 증가 (fire-and-forget)
c.executionCtx.waitUntil(
db
.update(posts)
.set({ viewCount: post.viewCount + 1 })
.where(eq(posts.id, post.id))
.run()
);
return c.json({ ...post, tags: postTagList });
});
export { postsRouter };
Das Inkrementieren von Aufrufzahlen mit c.executionCtx.waitUntil() ist ein wichtiges Entwurfsmuster für Workers. Die Datenbankaktualisierung wird auch nach dem Zurückgeben der Antwort im Hintergrund ausgeführt.
Lokale Entwicklung: Anbindung an lokales D1 SQLite
# Lokalen Entwicklungsserver starten
npx wrangler dev
# In einem separaten Terminal Migrationen anwenden
npx wrangler d1 migrations apply DB --local
Wrangler speichert den lokalen D1-Zustand als SQLite-Datei im Ordner .wrangler/state/v3/d1/. Selbst wenn der Entwicklungsserver neu gestartet wird, bleiben die Daten erhalten.
Lokale Datenbank direkt abfragen:
npx wrangler d1 execute DB --local --command "SELECT * FROM users LIMIT 5;"
Lokale Seed-Daten einfügen:
npx wrangler d1 execute DB --local --file ./drizzle/seed.sql
Transaktionen und Batch-Verarbeitung
Da D1 eine serverlose SQLite-Datenbank ist, unterstützt sie Transaktionen, die über Drizzle noch sicherer verwendet werden können.
// 트랜잭션: 유저 + 포스트 동시 생성
async function createUserWithPost(
db: DrizzleDb,
userData: NewUser,
postData: Omit<NewPost, "authorId">
) {
return await db.transaction(async (tx) => {
const [user] = await tx
.insert(users)
.values(userData)
.returning();
const [post] = await tx
.insert(posts)
.values({ ...postData, authorId: user.id })
.returning();
return { user, post };
});
}
Multi-Query-Optimierung mit der D1 Batch-API:
// D1 Batch API — 네트워크 왕복 1회로 여러 쿼리 실행
async function batchInsertPosts(db: DrizzleDb, postsData: NewPost[]) {
// Drizzle의 batch 헬퍼 (D1 특화)
const statements = postsData.map((post) =>
db.insert(posts).values(post)
);
// 단일 HTTP 요청으로 전체 배치 처리
await db.batch(statements);
}
db.batch() führt mehrere Abfragen mit einem einzigen Aufruf der HTTP-API von D1 aus. Wenn 1000 INSERT-Operationen einzeln ausgeführt werden, sind 1000 Netzwerk-Roundtrips erforderlich – bei der Batch-Verarbeitung nur ein einziger.
Kostenoptimierung: Betrieb innerhalb des kostenlosen D1-Tarifs
Grenzen des kostenlosen D1-Tarifs und Optimierungsmethoden mit Drizzle:
| Kriterium | Kostenlos | Kostenpflichtig (Workers Paid) |
|---|---|---|
| Leseanfragen | 5 Mio. pro Tag | Unbegrenzt |
| Schreibanfragen | 100.000 pro Tag | Unbegrenzt |
| Speicherkapazität | 5 GB | 25 GB |
| Anzahl der Datenbanken | 10 | Unbegrenzt |
Drizzle-Muster zur Reduzierung der Anzahl von Abfragen:
// ❌ N+1 쿼리 문제
const postList = await db.select().from(posts).all();
for (const post of postList) {
const author = await db
.select()
.from(users)
.where(eq(users.id, post.authorId))
.get(); // 포스트 수만큼 쿼리 발생
}
// ✅ JOIN으로 1번에 해결
const postListWithAuthor = await db
.select({
postId: posts.id,
title: posts.title,
authorName: users.name,
})
.from(posts)
.innerJoin(users, eq(posts.authorId, users.id))
.all();
// ✅ 자주 읽는 데이터 → KV 캐싱으로 D1 읽기 요청 절감
export type CacheEnv = {
DB: D1Database;
KV: KVNamespace;
};
async function getPostWithCache(
db: DrizzleDb,
kv: KVNamespace,
slug: string
) {
const cacheKey = `post:${slug}`;
// 캐시 히트
const cached = await kv.get(cacheKey, "json");
if (cached) return cached;
// 캐시 미스 → D1 조회 후 캐싱
const post = await db
.select()
.from(posts)
.where(eq(posts.slug, slug))
.get();
if (post) {
// 5분 TTL 캐싱
await kv.put(cacheKey, JSON.stringify(post), { expirationTtl: 300 });
}
return post;
}
Der Einzelpreis für D1-Leseanfragen beträgt $0.001 pro 1 Million Abfragen. Wenn Sie häufig gelesene Daten im KV zwischenspeichern, können Sie die D1-Kosten um mehr als 90% senken.
Typsicherer API-Client: Kombination mit Hono RPC
Wenn Sie die Typen von Drizzle über Hono RPC bis zum Frontend weiterleiten, erreichen Sie echte End-to-End-Typsicherheit.
// src/index.ts — Hono RPC 타입 내보내기
import { Hono } from "hono";
import type { User, Post } from "./db/schema";
const app = new Hono<{ Bindings: Env }>();
const routes = app
.get("/api/users", async (c) => {
// ... 구현
return c.json({ data: [] as User[] });
})
.post("/api/users", async (c) => {
// ... 구현
return c.json({} as User, 201);
});
export type AppType = typeof routes;
export default app;
// 프론트엔드 (React / Next.js)
import { hc } from "hono/client";
import type { AppType } from "../worker/src/index";
const client = hc<AppType>("https://my-app.workers.dev");
// 완전한 타입 추론 — D1 스키마에서 프론트엔드까지 이어짐
const response = await client.api.users.$get();
const { data } = await response.json();
// data는 User[] 타입 — IDE 자동 완성 100% 작동
Typen werden automatisch weitergeleitet: Schemadefinition → Drizzle-Typen → Hono-Antwort → RPC-Client. Sie implementieren End-to-End-Typsicherheit rein mit TypeScript, ohne prisma generate, tRPC-Konfiguration oder GraphQL-Schemata.
CI/CD: GitHub Actions Deployment-Pipeline
# .github/workflows/deploy.yml
name: Deploy to Cloudflare Workers
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: TypeScript 타입 검사
run: npx tsc --noEmit
- name: D1 마이그레이션 적용 (프로덕션)
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: npx wrangler d1 migrations apply DB --remote
- name: Workers 배포
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
run: npx wrangler deploy
Die Reihenfolge Migration → Deployment ist entscheidend. Sie müssen die Migration zuerst anwenden, damit der neue Workers-Code auf das neue Schema verweisen kann.
Drizzle Studio: Datenbankvisualisierung ohne GUI
# Drizzle Studio mit Anbindung an lokales D1 ausführen
npx drizzle-kit studio
Wenn Sie https://local.drizzle.studio im Browser öffnen, können Sie D1-Daten über eine grafische Benutzeroberfläche anzeigen und bearbeiten. Wenn Sie an Prisma Studio gewöhnt sind, ist es genau dieselbe Erfahrung.
Performance-Benchmark: Reale D1-Latenz
Tatsächliche Messwerte bei der Verwendung von D1 in Workers (basierend auf der offiziellen Cloudflare-Dokumentation):
| Abfragetyp | Durchschnittliche Latenz |
|---|---|
| Einfaches SELECT (Index) | 1-5ms |
| JOIN-Abfrage | 5-15ms |
| INSERT | 5-10ms |
| Komplexe Aggregationsabfrage | 10-50ms |
| Batch-INSERT (100 Einträge) | 10-20ms |
Im Vergleich zu externen PostgreSQL-Datenbanken (Neon, Supabase usw.) erfolgt der D1-Binding-Aufruf innerhalb von Workers ohne TCP-Verbindung, wodurch die Latenz um das 3- bis 5-Fache geringer ist. Wenn Ihr Ziel ein TTFB unter 100 ms ist, ist D1 die beste Wahl.
D1 vs. andere Edge-Datenbanken: Auswahlkriterien
| Szenario | Empfehlung |
|---|---|
| Cloudflare All-in-One Stack | D1 + Drizzle |
| Multi-Cloud (Vercel + CF gemischt) | Turso + Drizzle |
| PostgreSQL-Funktionen benötigt (JSONB, Volltext) | Neon + Drizzle |
| Echtzeit-Synchronisierung benötigt | Supabase + Drizzle |
| Komplexe Analyseabfragen | Externe PostgreSQL |
Wenn Sie sich ausschließlich im Cloudflare-Ökosystem befinden, ist D1 mit Abstand die beste Lösung. Wenn Sie Multi-Cloud benötigen, ist Turso auf libSQL-Basis portabel und nutzt dieselbe Drizzle-API wie D1.
Migrations-Checkliste
Prüfpunkte beim Umstieg von Prisma auf Drizzle:
- Konvertierung von
schema.prismazusrc/db/schema.ts - Entfernung des Skripts
prisma generate - Ersetzung der
PrismaClient-Instanz durchdrizzle(env.DB) - Umwandlung von Prismas
findMany,findUniquein Drizzlesselect().from()-Muster - Umwandlung von Prismas
include(Beziehungsabfragen) in DrizzlesinnerJoin/leftJoin - Ersetzung von
prisma migrate devdurchdrizzle-kit generate+wrangler d1 migrations apply
Der Umstieg mag aufwendig erscheinen, aber sobald Sie mit Drizzle vertraut sind, fühlt sich die direkte Kontrolle über SQL viel intuitiver an. Es basiert auf der Philosophie, dass ein ORM SQL nicht verbirgt, sondern SQL in TypeScript ausdrückt.
Fazit
Die Kombination aus Drizzle ORM und Cloudflare D1 ist eine der effizientesten Optionen für den Edge-Full-Stack im Jahr 2026.
- Bundle-Größe von 12.2 KB: Praktisch keine Auswirkung auf den Cold Start von Workers
- Typinferenz ohne Codegenerierung: Das gesamte Typsystem wird über eine einzige Schemadatei konfiguriert
- D1-Bindings: Zugriff mit extrem geringer Latenz über IPC ohne externe Datenbankverbindung
- Hono RPC-Anbindung: Automatische Typenweiterleitung vom Datenbank-Schema bis zum Frontend
Wenn das 1.6 MB große Bundle von Prisma in Ihrer Workers-Umgebung zu Problemen geführt hat, ist der Umstieg auf Drizzle nicht nur eine Frage der Bundle-Größe. Es ist auch ein philosophischer Wandel: Anstatt ein ORM als magische Blackbox zu verwenden, drücken Sie SQL in TypeScript aus.
Verwandter Artikel: Im Leitfaden Hono RPC & Cloudflare Workers: TanStack Query v5 Typsicherheit finden Sie ein vollständiges Full-Stack-Beispiel, das die Drizzle-API mit Hono RPC kombiniert.