Drizzle ORM Derinlemesine: TypeScript'in En İyi ORM'i mi?
TypeScript ekosisteminde ORM seçimi her zaman tartışmalı bir konu olmuştur. Prisma'nın deklaratif şema yaklaşımı, TypeORM'un decorator tabanlı yapısı ve Knex'in query builder felsefesi — her birinin güçlü ve zayıf yanları var. Ancak 2023'ten itibaren sahneye çıkan Drizzle ORM, "SQL bilen geliştiriciler için ORM" mottosuyla bu denklemi tamamen değiştirdi.
Bu yazıda Drizzle ORM'i derinlemesine inceleyecek, gerçek dünya kullanım senaryolarıyla birlikte neden bu kadar popüler olduğunu anlayacağız.
Drizzle ORM Nedir?
Drizzle ORM, TypeScript-first bir ORM ve query builder'dır. En temel felsefesi "If you know SQL, you know Drizzle" (SQL biliyorsan, Drizzle'ı da bilirsin) cümlesiyle özetlenebilir. Diğer ORM'lerin aksine, SQL'i soyutlamak yerine TypeScript ile SQL yazma deneyimini birebir yansıtmayı hedefler.
Drizzle'ın Öne Çıkan Özellikleri
- Sıfır bağımlılık: Çalışma zamanında (runtime) herhangi bir ek bağımlılığa ihtiyaç duymaz
- SQL-benzeri söz dizimi: SQL bilgisi doğrudan transfer edilebilir
- Tam tip güvenliği: Şemadan sorgu sonucuna kadar uçtan uca TypeScript tipleri
- Serverless uyumlu: Edge runtime'larda (Cloudflare Workers, Vercel Edge) sorunsuz çalışır
- Küçük bundle boyutu: ~7.4KB gzipped — Prisma Client'ın bir kısmı kadar
- Birden fazla veritabanı desteği: PostgreSQL, MySQL, SQLite ve LibSQL
Kurulum ve İlk Yapılandırma
Drizzle'ı bir projede kullanmaya başlamak oldukça basittir. PostgreSQL ile bir örnek yapalım:
npm install drizzle-orm postgres
npm install -D drizzle-kitdrizzle-kit, şema yönetimi ve migration'lar için kullanılan CLI aracıdır. Çalışma zamanında değil, yalnızca geliştirme ortamında ihtiyaç duyulur.
Yapılandırma Dosyası
Proje kökünde bir drizzle.config.ts dosyası oluşturun:
import { defineConfig } from "drizzle-kit";
export default defineConfig({
schema: "./src/db/schema.ts",
out: "./drizzle",
dialect: "postgresql",
dbCredentials: {
url: process.env.DATABASE_URL!,
},
});Şema (Schema) Tanımlama
Drizzle'da şemalar, TypeScript dosyalarında fonksiyonlar aracılığıyla tanımlanır. Bu yaklaşım Prisma'nın .prisma dosyasından temel bir farklılık taşır — şemanız zaten TypeScript'tir, dolayısıyla IDE desteği, import/export ve koşullu mantık gibi dil özelliklerini doğrudan kullanabilirsiniz.
// src/db/schema.ts
import {
pgTable,
serial,
varchar,
text,
timestamp,
integer,
boolean,
pgEnum,
} from "drizzle-orm/pg-core";
import { relations } from "drizzle-orm";
// Enum tanımı
export const roleEnum = pgEnum("role", ["admin", "editor", "user"]);
// Users tablosu
export const users = pgTable("users", {
id: serial("id").primaryKey(),
name: varchar("name", { length: 255 }).notNull(),
email: varchar("email", { length: 255 }).notNull().unique(),
role: roleEnum("role").default("user").notNull(),
bio: text("bio"),
isActive: boolean("is_active").default(true).notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
});
// Posts tablosu
export const posts = pgTable("posts", {
id: serial("id").primaryKey(),
title: varchar("title", { length: 500 }).notNull(),
content: text("content").notNull(),
published: boolean("published").default(false).notNull(),
authorId: integer("author_id")
.references(() => users.id, { onDelete: "cascade" })
.notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
});
// İlişki tanımları
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
}));
export const postsRelations = relations(posts, ({ one }) => ({
author: one(users, {
fields: [posts.authorId],
references: [users.id],
}),
}));Dikkat ederseniz her kolon adı için hem TypeScript property adını (isActive) hem de veritabanı kolon adını (is_active) ayrı ayrı belirtebiliyorsunuz. Bu, naming convention farklılıklarını zarif bir şekilde çözer.
Veritabanı Bağlantısı
// src/db/index.ts
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import * as schema from "./schema";
const connectionString = process.env.DATABASE_URL!;
// Connection pool
const client = postgres(connectionString, {
max: 10,
idle_timeout: 20,
});
export const db = drizzle(client, { schema });Drizzle, birçok farklı PostgreSQL driver'ını destekler: postgres, node-postgres (pg), Neon Serverless, Supabase, Vercel Postgres ve daha fazlası.
Migration Yönetimi
Şemanızı tanımladıktan sonra migration dosyalarını oluşturmak tek komut:
npx drizzle-kit generateBu komut ./drizzle klasörü altında SQL migration dosyaları oluşturur. Migration'ları uygulamak için:
npx drizzle-kit migrateGeliştirme aşamasında hızlıca şemayı veritabanına yansıtmak isterseniz:
npx drizzle-kit pushpush komutu migration dosyası oluşturmadan doğrudan veritabanını günceller — prototipleme için idealdir ancak üretim ortamında generate + migrate akışı tercih edilmelidir.
Drizzle Studio
Drizzle'ın en sevilen özelliklerinden biri de yerleşik veritabanı tarayıcısıdır:
npx drizzle-kit studioBu komut tarayıcıda açılan bir GUI sunar. Tablolarınızı görüntüleyebilir, veri ekleyebilir, düzenleyebilir ve silebilirsiniz — herhangi bir ek araç kurmadan.
Sorgu Yazımı: İki Farklı API
Drizzle, iki farklı sorgu API'si sunar. Bu, onu diğer ORM'lerden ayıran en önemli özelliklerden biridir.
1. SQL-Like API (Select Builder)
SQL bilen geliştiriciler için son derece tanıdık bir söz dizimi:
import { eq, and, gt, like, desc, count, sql } from "drizzle-orm";
import { db } from "./db";
import { users, posts } from "./db/schema";
// Basit SELECT
const allUsers = await db.select().from(users);
// WHERE koşulu ile
const activeAdmins = await db
.select()
.from(users)
.where(and(eq(users.role, "admin"), eq(users.isActive, true)));
// Belirli kolonları seçme
const userEmails = await db
.select({
id: users.id,
email: users.email,
})
.from(users);
// JOIN sorgusu
const postsWithAuthors = await db
.select({
postTitle: posts.title,
authorName: users.name,
authorEmail: users.email,
})
.from(posts)
.innerJoin(users, eq(posts.authorId, users.id))
.where(eq(posts.published, true))
.orderBy(desc(posts.createdAt))
.limit(10);
// Aggregation
const postCountByUser = await db
.select({
authorId: posts.authorId,
authorName: users.name,
totalPosts: count(posts.id),
})
.from(posts)
.innerJoin(users, eq(posts.authorId, users.id))
.groupBy(posts.authorId, users.name);
// Raw SQL kullanımı
const result = await db.execute(
sql`SELECT * FROM users WHERE created_at > NOW() - INTERVAL '7 days'`
);Bu API'de her sorgu, yazılan SQL'in TypeScript karşılığı gibi okunur. Bir SQL sorgusunu Drizzle'a çevirmek (veya tam tersini yapmak) neredeyse mekanik bir süreçtir.
2. Relational Query API
Prisma benzeri, ilişki tabanlı sorgular için:
// Kullanıcıyı postlarıyla birlikte getir
const userWithPosts = await db.query.users.findFirst({
where: eq(users.id, 1),
with: {
posts: {
where: eq(posts.published, true),
orderBy: [desc(posts.createdAt)],
limit: 5,
},
},
});
// Tüm yayınlanmış postları yazarlarıyla birlikte getir
const publishedPosts = await db.query.posts.findMany({
where: eq(posts.published, true),
with: {
author: true,
},
columns: {
id: true,
title: true,
createdAt: true,
},
orderBy: [desc(posts.createdAt)],
limit: 20,
offset: 0,
});Bu API, relations() tanımlarını kullanarak çalışır ve Prisma'dan geçiş yapan geliştiriciler için tanıdık bir deneyim sunar.
INSERT, UPDATE ve DELETE İşlemleri
// Tekil ekleme
const newUser = await db
.insert(users)
.values({
name: "Ahmet Yılmaz",
email: "ahmet@example.com",
role: "editor",
})
.returning();
// Toplu ekleme
const newPosts = await db
.insert(posts)
.values([
{ title: "İlk Yazı", content: "Merhaba dünya!", authorId: 1 },
{ title: "İkinci Yazı", content: "Devam ediyoruz.", authorId: 1 },
])
.returning();
// Upsert (conflict handling)
await db
.insert(users)
.values({ name: "Ahmet", email: "ahmet@example.com" })
.onConflictDoUpdate({
target: users.email,
set: { name: "Ahmet Yılmaz" },
});
// Güncelleme
await db
.update(users)
.set({ isActive: false, updatedAt: new Date() })
.where(eq(users.id, 5));
// Silme
await db.delete(posts).where(eq(posts.authorId, 5));Tüm bu işlemlerde dönen sonuçlar tam tip güvenliğine sahiptir. .returning() kullandığınızda, dönen objenin tipi şemanızdan otomatik olarak türetilir.
Drizzle vs Prisma: Karşılaştırma
| Özellik | Drizzle ORM | Prisma |
|---|---|---|
| Şema tanımı | TypeScript dosyası | .prisma DSL |
| Bundle boyutu | ~7.4KB | ~2MB+ (Engine dahil) |
| Serverless uyumu | Mükemmel | Sorunlu (cold start) |
| SQL kontrolü | Tam kontrol | Sınırlı |
| Öğrenme eğrisi | SQL bilgisi gerektirir | Daha kolay başlangıç |
| Tip güvenliği | Uçtan uca | Uçtan uca |
| Raw SQL | Doğal entegrasyon | Mümkün ama ayrık |
| Migration | SQL dosyaları | Otomatik |
| Olgunluk | Daha genç | Daha olgun ekosistem |
Ne Zaman Drizzle?
- Serverless/Edge ortamlarında çalışıyorsanız (Cloudflare Workers, Vercel Edge)
- SQL üzerinde tam kontrol istiyorsanız
- Bundle boyutu kritikse
- Performans öncelikli projeleriniz varsa
- Karmaşık JOIN ve aggregation sorguları yazıyorsanız
Ne Zaman Prisma?
- Hızlı prototipleme yapıyorsanız
- Takımda SQL deneyimi sınırlıysa
- Prisma ekosisteminin araçlarına (Prisma Studio, Prisma Accelerate) ihtiyacınız varsa
- Kapsamlı dökümantasyon ve topluluk desteği arıyorsanız
Prepared Statements ve Performans
Drizzle, prepared statement desteğiyle tekrarlanan sorguların performansını artırır:
import { placeholder } from "drizzle-orm";
const getUserById = db
.select()
.from(users)
.where(eq(users.id, placeholder("id")))
.prepare("get_user_by_id");
// Kullanım — sorgu planı önbelleklenir
const user = await getUserById.execute({ id: 1 });
const anotherUser = await getUserById.execute({ id: 42 });Bu yaklaşım, özellikle yüksek trafikli API endpoint'lerinde ölçülebilir performans kazanımları sağlar. Veritabanı, sorgu planını bir kez hesaplar ve sonraki çağrılarda yeniden kullanır.
Transaction Desteği
await db.transaction(async (tx) => {
const [newUser] = await tx
.insert(users)
.values({ name: "Yeni Kullanıcı", email: "yeni@example.com" })
.returning();
await tx.insert(posts).values({
title: "Hoş Geldiniz!",
content: "İlk yazınız otomatik oluşturuldu.",
authorId: newUser.id,
});
// Hata fırlatırsanız transaction otomatik geri alınır
});Nested transaction'lar (savepoint) da desteklenmektedir.
Zod Entegrasyonu ile Validasyon
Drizzle, drizzle-zod paketi ile şemadan otomatik Zod validasyon şemaları türetebilir:
npm install drizzle-zodimport { createInsertSchema, createSelectSchema } from "drizzle-zod";
const insertUserSchema = createInsertSchema(users, {
email: (schema) => schema.email("Geçerli bir email adresi giriniz"),
name: (schema) => schema.min(2, "İsim en az 2 karakter olmalıdır"),
});
const selectUserSchema = createSelectSchema(users);
// API endpoint'inde kullanım
type NewUser = typeof insertUserSchema._type;
function validateUser(data: unknown) {
return insertUserSchema.parse(data);
}Bu entegrasyon, şema tanımınızı tek bir kaynak olarak kullanmanızı sağlar (Single Source of Truth). Veritabanı şeması değiştiğinde validasyon kuralları da otomatik olarak güncellenir.
Sonuç
Drizzle ORM, TypeScript ekosistemindeki ORM tartışmasına taze ve güçlü bir bakış açısı getiriyor. "SQL'i soyutlama, onu TypeScript ile güçlendir" felsefesi, özellikle SQL bilgisi olan geliştiriciler için son derece cazip bir yaklaşım.
Güçlü yönleri özetlemek gerekirse:
- Sıfır runtime bağımlılığı ve minimal bundle boyutu
- SQL-like ve Relational olmak üzere iki farklı sorgu API'si
- Serverless ve edge ortamlarında üstün performans
- Uçtan uca tip güvenliği
- Şemanın TypeScript olması sayesinde tam IDE desteği
Dikkat edilmesi gereken noktalar:
- Prisma'ya kıyasla daha genç bir ekosistem
- SQL bilgisi gerektiren öğrenme eğrisi
- Bazı ileri düzey özelliklerin (polymorphic relations gibi) henüz geliştirilme aşamasında olması
Peki, Drizzle gerçekten TypeScript'in en iyi ORM'i mi? Tek bir doğru cevap yok. Eğer SQL'e hakimseniz, performans ve bundle boyutu sizin için önemliyse ve özellikle serverless mimarilerde çalışıyorsanız, Drizzle şu anda en güçlü aday. Ancak hızlı prototipleme, geniş ekosistem ve daha düşük giriş bariyeri arıyorsanız Prisma hâlâ çok geçerli bir seçenek.
Benim tavsiyem: Bir sonraki projenizde Drizzle'ı deneyin. SQL bilginizi TypeScript ile birleştirmenin ne kadar doğal hissettirdiğini gördüğünüzde, muhtemelen geri dönmek istemeyeceksiniz.