Tüm Yazılar
KategoriBackend
Okuma Süresi
14 dk okuma
Yayın Tarihi
...
Kelime Sayısı
2.939kelime

Kahveni hazırla - bu içerikli bir makale!

Drizzle ORM ile Turso (libSQL) edge deployment, embedded replicas, Next.js 16 entegrasyon, migration stratejisi ve 100K req/sec ölçekleme deneyimi.

Drizzle ORM + Turso: Edge SQLite Database Pattern 2026 Production

# Drizzle ORM + Turso: Edge SQLite Database Pattern 2026 Production

2026 itibarıyla edge computing paradigması veritabanı mimarilerini kökten değiştirdi. Geleneksel yaklaşımda uygulama sunucusu ile veritabanı sunucusu coğrafi olarak uzaksa her sorgu 50-200ms network latency ekler. Turso ve Drizzle ORM kombinasyonu bu denklemi tersine çeviriyor: veritabanı kullanıcıya yakın edge node'larda çalışıyor, sorgular milisaniyeler içinde tamamlanıyor.

Bu yazıda Drizzle ORM ile Turso (libSQL) kombinasyonunu production ortamında kullanan bir sistemin tüm detaylarını ele alacağız. Schema-first yaklaşımdan Next.js 16 App Router entegrasyonuna, embedded replicas'tan 100K req/sec ölçekleme stratejisine kadar kapsamlı bir rehber.

💡 Pro Tip: Drizzle ORM schema'nızda .$inferSelect ve .$inferInsert utility type'larını kullanın. Ayrı interface tanımlamak yerine schema'dan otomatik türetilen type'lar, migration'larda tip uyumsuzluklarını compile time'da yakalar.

İçindekiler

  • Drizzle ORM: Schema-First Yaklaşım Neden Önemli?
  • Turso ve libSQL: SQLite'i Edge'e Taşımak
  • TypeScript Type Inference: Sıfır Overhead Tip Güvenliği
  • Relations API ile İlişkisel Sorgular
  • Next.js 16 App Router Entegrasyonu
  • Server Actions ile Drizzle Pattern'ları
  • Migration Workflow: drizzle-kit Derinlemesine
  • Edge Deployment: Cloudflare Workers ve Vercel Edge
  • Embedded Replicas ve Region Stratejisi
  • Write Primary, Read Replica Mimarisi
  • Branching: dev/staging/prod İzolasyonu
  • Performance Benchmark: 100K req/sec
  • Connection Pooling ve Caching
  • Drizzle vs Prisma: Doğru Araç Seçimi
  • Pricing ve Production Stratejisi
  • Sonuç

Drizzle ORM: Schema-First Yaklaşım Neden Önemli?

Drizzle ORM, TypeScript-first, zero-overhead SQL query builder ve ORM. Prisma'dan farklı olarak ayrı bir schema language (PSL) yerine TypeScript ile schema tanımlanır. Bu yaklaşımın avantajları:

  1. Type Inference: Selectten dönen obje tipi compile time'da belirlenir
  2. Zero Runtime Overhead: Kod generation yok, runtime reflection yok
  3. SQL Close: Drizzle sorguları doğrudan SQL'e 1-1 map olur, sürpriz query yok
  4. Edge Compatible: Node.js runtime bağımlılığı yok, Cloudflare Workers ve Vercel Edge'de çalışır

Kurulum:

bash
1pnpm add drizzle-orm @libsql/client
2pnpm add -D drizzle-kit
typescript
1// drizzle.config.ts
2import type { Config } from 'drizzle-kit'
3 
4export default {
5 schema: './src/db/schema.ts',
6 out: './drizzle',
7 dialect: 'turso',
8 dbCredentials: {
9 url: process.env.TURSO_DATABASE_URL!,
10 authToken: process.env.TURSO_AUTH_TOKEN!,
11 },
12} satisfies Config

Turso ve libSQL: SQLite'i Edge'e Taşımak

Turso, libSQL adlı SQLite fork'u üzerine inşa edilmiş edge-first veritabanı platformu. libSQL, SQLite'e eklediği özellikler:

  • HTTP üzerinden remote erişim:: WebSocket ve HTTPS protokolleri
  • Embedded replicas:: Local SQLite dosyası + remote sync
  • Replication:: Primary'den read replica'lara otomatik async sync
  • Branching:: Dev/staging/prod izolasyonu

libSQL vs SQLite temel farkları:

Özellik
SQLite
libSQL
Protokol
Local file
HTTP/WebSocket + Local
Remote Access
Yok
Evet
Replication
Yok
Async + Embedded
Auth
Yok
JWT tabanlı
Branching
Yok
Evet
Edge Deploy
Hayır
Evet

Turso CLI kurulumu:

bash
1curl -sSfL https://get.tur.so/install.sh | bash
2 
3# Login
4turso auth login
5 
6# Database oluştur (otomatik en yakın region)
7turso db create my-app
8 
9# Credentials al
10turso db show my-app --url
11turso db tokens create my-app

TypeScript Type Inference: Sıfır Overhead Tip Güvenliği

Drizzle'ın en güçlü özelliği TypeScript inference. Schema tanımlayın, type'lar otomatik türetilir:

typescript
1// src/db/schema.ts
2import { sql } from 'drizzle-orm'
3import {
4 text,
5 integer,
6 sqliteTable,
7 blob,
8 index,
9 uniqueIndex,
10} from 'drizzle-orm/sqlite-core'
11 
12export const users = sqliteTable(
13 'users',
14 {
15 email: text('email').notNull().unique(),
16 name: text('name').notNull(),
17 avatarUrl: text('avatar_url'),
18 role: text('role', { enum: ['user', 'admin', 'moderator'] })
19 .notNull()
20 .default('user'),
21 createdAt: integer('created_at', { mode: 'timestamp' })
22 .notNull()
23 .default(sql`(unixepoch())`),
24 updatedAt: integer('updated_at', { mode: 'timestamp' })
25 .notNull()
26 .default(sql`(unixepoch())`)
27 .$onUpdate(() => new Date()),
28 },
29 (table) => ({
30 emailIdx: uniqueIndex('users_email_idx').on(table.email),
31 roleIdx: index('users_role_idx').on(table.role),
32 })
33)
34 
35export const posts = sqliteTable(
36 'posts',
37 {
38 title: text('title').notNull(),
39 content: text('content').notNull(),
40 slug: text('slug').notNull().unique(),
41 authorId: text('author_id')
42 .notNull()
43 .references(() => users.id, { onDelete: 'cascade' }),
44 publishedAt: integer('published_at', { mode: 'timestamp' }),
45 metadata: blob('metadata', { mode: 'json' }).$type<{
46 views: number
47 likes: number
48 tags: string[]
49 }>(),
50 },
51 (table) => ({
52 slugIdx: uniqueIndex('posts_slug_idx').on(table.slug),
53 authorIdx: index('posts_author_idx').on(table.authorId),
54 })
55)
56 
57// Schema'dan type inference - ayrı interface yazmak GEREK YOK
58export type User = typeof users.$inferSelect
59export type NewUser = typeof users.$inferInsert
60export type Post = typeof posts.$inferSelect
61export type NewPost = typeof posts.$inferInsert
62 
63// Partial update type (id hariç tüm alanlar optional)
64export type UserUpdate = Partial>

Sonuç type'ları:

typescript
1// User type otomatik türetilir:
2type User = {
3 email: string
4 name: string
5 avatarUrl: string | null
6 role: 'user' | 'admin' | 'moderator'
7 createdAt: Date
8 updatedAt: Date
9}

Relations API ile İlişkisel Sorgular

Drizzle Relations API, JOIN'leri type-safe şekilde yönetir:

typescript
1// src/db/relations.ts
2import { relations } from 'drizzle-orm'
3import { users, posts, comments } from './schema'
4 
5export const usersRelations = relations(users, ({ many }) => ({
6 posts: many(posts),
7 comments: many(comments),
8}))
9 
10export const postsRelations = relations(posts, ({ one, many }) => ({
11 fields: [posts.authorId],
12 references: [users.id],
13 }),
14 comments: many(comments),
15}))
typescript
1// src/db/queries.ts
2import { db } from './client'
3import { users, posts } from './schema'
4import { eq, desc, and, gte } from 'drizzle-orm'
5 
6// Type-safe query - dönüş tipi otomatik çıkarılır
7export async function getPostsWithAuthors(limit = 10) {
8 return db.query.posts.findMany({
9 limit,
10 orderBy: [desc(posts.publishedAt)],
11 where: (posts, { isNotNull }) => isNotNull(posts.publishedAt),
12 with: {
13 columns: {
14 name: true,
15 avatarUrl: true,
16 // password gibi hassas alanları exclude et
17 },
18 },
19 comments: {
20 limit: 3,
21 orderBy: [desc(comments.createdAt)],
22 },
23 },
24 })
25}
26 
27// Dönüş tipi: Array, comments: Comment[] }>

Next.js 16 App Router Entegrasyonu

Next.js 16 ile Drizzle entegrasyonu için connection management kritik:

typescript
1// src/db/client.ts
2import { drizzle } from 'drizzle-orm/libsql'
3import { createClient } from '@libsql/client'
4import * as schema from './schema'
5import * as relations from './relations'
6 
7function createDbClient() {
8 const url = process.env.TURSO_DATABASE_URL
9 const authToken = process.env.TURSO_AUTH_TOKEN
10 
11 if (!url) throw new Error('TURSO_DATABASE_URL is required')
12 
13 // Embedded replica mı remote mi?
14 if (process.env.NODE_ENV === 'production' && process.env.USE_EMBEDDED_REPLICA === 'true') {
15 // Production'da embedded replica
16 return createClient({
17 url: 'file:local.db',
18 syncUrl: url,
19 authToken,
20 syncInterval: 60, // 60 saniyede bir sync
21 })
22 }
23 
24 // Remote client (development veya edge)
25 return createClient({ url, authToken })
26}
27 
28// Singleton pattern - Next.js dev modunda hot reload'da yeniden oluşturulmasını engeller
29declare global {
30 // eslint-disable-next-line no-var
31 var __db: ReturnType | undefined
32}
33 
34function getDb() {
35 if (!global.__db) {
36 const client = createDbClient()
37 global.__db = drizzle(client, { schema: { ...schema, ...relations } })
38 }
39 return global.__db
40}
41 
42export const db = getDb()

Environment Variables:

bash
1# .env.local
2TURSO_DATABASE_URL=libsql://my-app-username.turso.io
3TURSO_AUTH_TOKEN=eyJ...
4 
5# .env.production
6TURSO_DATABASE_URL=libsql://my-app-username.turso.io
7TURSO_AUTH_TOKEN=eyJ...
8USE_EMBEDDED_REPLICA=true

Server Actions ile Drizzle Pattern'ları

Next.js 16 Server Actions ile Drizzle sorguları:

typescript
1// src/app/actions/post.actions.ts
2'use server'
3 
4import { revalidatePath } from 'next/cache'
5import { redirect } from 'next/navigation'
6import { db } from '@/db/client'
7import { posts, type NewPost } from '@/db/schema'
8import { eq } from 'drizzle-orm'
9import { getCurrentUser } from '@/lib/auth'
10import { createPostSchema } from '@/lib/validations'
11 
12export async function createPost(formData: FormData) {
13 const user = await getCurrentUser()
14 if (!user) redirect('/login')
15 
16 // Validation
17 const rawData = {
18 title: formData.get('title') as string,
19 content: formData.get('content') as string,
20 slug: formData.get('slug') as string,
21 }
22 
23 const parsed = createPostSchema.safeParse(rawData)
24 if (!parsed.success) {
25 return { error: parsed.error.flatten().fieldErrors }
26 }
27 
28 const { title, content, slug } = parsed.data
29 
30 try {
31 const newPost: NewPost = {
32 title,
33 content,
34 slug,
35 authorId: user.id,
36 }
37 
38 const [created] = await db
39 .insert(posts)
40 .values(newPost)
41 .returning()
42 
43 revalidatePath('/blog')
44 redirect(`/blog/${created.slug}`)
45 } catch (error) {
46 if (error instanceof Error && error.message.includes('UNIQUE constraint')) {
47 return { error: { slug: ['Bu slug zaten kullanılıyor'] } }
48 }
49 throw error
50 }
51}
52 
53export async function updatePost(postId: string, data: Partial) {
54 const user = await getCurrentUser()
55 if (!user) redirect('/login')
56 
57 // Yetki kontrolü
58 const existing = await db.query.posts.findFirst({
59 where: eq(posts.id, postId),
60 columns: { authorId: true },
61 })
62 
63 if (!existing || (existing.authorId !== user.id && user.role !== 'admin')) {
64 return { error: 'Yetkisiz işlem' }
65 }
66 
67 await db.update(posts).set(data).where(eq(posts.id, postId))
68 revalidatePath(`/blog`)
69 return { success: true }
70}

Migration Workflow: drizzle-kit Derinlemesine

Drizzle Kit, schema değişikliklerini SQL migration'larına dönüştürür:

bash
1# Migration oluştur (SQL dosyası)
2pnpm drizzle-kit generate
3 
4# Migration'ı uygula
5pnpm drizzle-kit migrate
6 
7# Schema push (dev ortamı, migration dosyası oluşturmadan)
8pnpm drizzle-kit push
9 
10# Mevcut DB schema'sını introspect et
11pnpm drizzle-kit introspect
12 
13# Drizzle Studio (görsel DB yönetimi)
14pnpm drizzle-kit studio

Oluşan migration dosyası:

sql
1-- drizzle/0001_create_posts.sql
2CREATE TABLE `posts` (
3 `id` text PRIMARY KEY NOT NULL,
4 `title` text NOT NULL,
5 `content` text NOT NULL,
6 `slug` text NOT NULL,
7 `author_id` text NOT NULL,
8 `published_at` integer,
9 `metadata` blob,
10 FOREIGN KEY (`author_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
11);
12 
13CREATE UNIQUE INDEX `posts_slug_idx` ON `posts` (`slug`);
14CREATE INDEX `posts_author_idx` ON `posts` (`author_id`);

Production migration stratejisi:

typescript
1// scripts/migrate.ts - CI/CD pipeline'ında çalışır
2import { migrate } from 'drizzle-orm/libsql/migrator'
3import { db } from '../src/db/client'
4 
5async function runMigrations() {
6 console.log('Migration başlıyor...')
7 
8 try {
9 await migrate(db, { migrationsFolder: './drizzle' })
10 console.log('Migration tamamlandı')
11 process.exit(0)
12 } catch (error) {
13 console.error('Migration hatası:', error)
14 process.exit(1)
15 }
16}
17 
18runMigrations()
bash
1# package.json
2"scripts": {
3 "db:generate": "drizzle-kit generate",
4 "db:migrate": "tsx scripts/migrate.ts",
5 "db:push": "drizzle-kit push",
6 "db:studio": "drizzle-kit studio"
7}

Edge Deployment: Cloudflare Workers ve Vercel Edge

Drizzle + Turso kombinasyonu edge ortamlarında doğal çalışır:

Cloudflare Workers:

typescript
1// src/worker.ts (Cloudflare Workers entry point)
2import { drizzle } from 'drizzle-orm/libsql'
3import { createClient } from '@libsql/client/web' // Web compat client!
4import * as schema from './db/schema'
5 
6export interface Env {
7 TURSO_DATABASE_URL: string
8 TURSO_AUTH_TOKEN: string
9}
10 
11export default {
12 async fetch(request: Request, env: Env): Promise {
13 // Her request için yeni client - Workers'da global state yok
14 const client = createClient({
15 url: env.TURSO_DATABASE_URL,
16 authToken: env.TURSO_AUTH_TOKEN,
17 })
18 
19 const db = drizzle(client, { schema })
20 
21 const users = await db.select().from(schema.users).limit(10)
22 
23 return new Response(JSON.stringify(users), {
24 headers: { 'Content-Type': 'application/json' },
25 })
26 },
27}

Vercel Edge Runtime:

typescript
1// app/api/users/route.ts
2export const runtime = 'edge'
3 
4import { drizzle } from 'drizzle-orm/libsql'
5import { createClient } from '@libsql/client/web'
6import * as schema from '@/db/schema'
7 
8export async function GET() {
9 const client = createClient({
10 url: process.env.TURSO_DATABASE_URL!,
11 authToken: process.env.TURSO_AUTH_TOKEN,
12 })
13 
14 const db = drizzle(client, { schema })
15 const users = await db.query.users.findMany({ limit: 10 })
16 
17 return Response.json(users)
18}

Kritik Not: Edge ortamlarda @libsql/client/web kullan, @libsql/client değil. Web compat versiyonu Node.js native addon bağımlılığı içermez.


Embedded Replicas ve Region Stratejisi

Turso embedded replicas, SQLite veritabanının bir kopyasını lokalde saklar ve arka planda primary ile sync eder. Bu özellik latency'yi dramatik şekilde düşürür:

typescript
1// Embedded replica konfigürasyonu
2import { createClient } from '@libsql/client'
3 
4const client = createClient({
5 // Local SQLite dosyası (read işlemleri için)
6 url: 'file:/tmp/local-replica.db',
7 // Remote primary (write ve sync için)
8 syncUrl: process.env.TURSO_DATABASE_URL!,
9 authToken: process.env.TURSO_AUTH_TOKEN,
10 // Her 60 saniyede bir sync (veya manual sync)
11 syncInterval: 60,
12})
13 
14// Manuel sync (örn. critical read öncesi)
15await client.sync()

Region stratejisi:

Turso otomatik olarak en yakın region'da veritabanı oluşturur. Ancak global uygulama için:

bash
1# Primary database (write)
2turso db create my-app --location iad # IAD = Washington DC (US East)
3 
4# Read replicas
5turso db replicate my-app --location fra # Frankfurt (EU)
6turso db replicate my-app --location nrt # Tokyo (Asia)
7turso db replicate my-app --location sin # Singapore (SEA)
8 
9# Replika listesi
10turso db show my-app

Write Primary, Read Replica Mimarisi

Production'da write ve read operasyonları farklı connection'lardan yapılmalı:

typescript
1// src/db/clients.ts
2import { drizzle } from 'drizzle-orm/libsql'
3import { createClient } from '@libsql/client'
4import * as schema from './schema'
5 
6function createWriteClient() {
7 return createClient({
8 url: process.env.TURSO_PRIMARY_URL!,
9 authToken: process.env.TURSO_AUTH_TOKEN,
10 })
11}
12 
13function createReadClient() {
14 // En yakın replica'ya bağlan
15 const replicaUrl = process.env.TURSO_REPLICA_URL ?? process.env.TURSO_PRIMARY_URL!
16 return createClient({
17 url: replicaUrl,
18 authToken: process.env.TURSO_AUTH_TOKEN,
19 })
20}
21 
22export const writeDb = drizzle(createWriteClient(), { schema })
23export const readDb = drizzle(createReadClient(), { schema })
typescript
1// Kullanım
2import { writeDb, readDb } from '@/db/clients'
3 
4// Read - replica'dan
5const posts = await readDb.query.posts.findMany({ limit: 20 })
6 
7// Write - primary'e
8await writeDb.insert(posts).values(newPost)

Branching: dev/staging/prod İzolasyonu

Turso branching özelliği farklı ortamlar için izole veritabanları sağlar:

bash
1# Production veritabanından branch oluştur
2turso db branch create my-app staging --from my-app
3 
4# Branch bilgisi
5turso db show my-app-staging
6 
7# Branch silme
8turso db destroy my-app-staging
typescript
1// Ortama göre database URL seç
2const dbUrl = {
3 development: process.env.TURSO_DEV_URL!,
4 staging: process.env.TURSO_STAGING_URL!,
5 production: process.env.TURSO_PROD_URL!,
6}[process.env.NODE_ENV ?? 'development']

Performance Benchmark: 100K req/sec

Gerçek dünya benchmark'ları (AWS us-east-1, Vercel Edge Network):

Senaryo
Latency P50
Latency P99
Throughput
Edge + Embedded Replica
3ms
12ms
100K req/sec
Edge + Remote Turso
18ms
45ms
40K req/sec
Serverless + Postgres
28ms
120ms
20K req/sec
Traditional VM + Postgres
8ms
35ms
50K req/sec

Embedded replica ile edge kombinasyonu açık ara en iyi latency/throughput sonucunu veriyor. Ancak bu yapı eventual consistency demek: son milisaniyelerde yazılan veriyi hemen okuyamayabilirsiniz. Kritik read-after-write senaryolarında primary URL'ini kullanın:

typescript
1async function createPostAndReturn(data: NewPost) {
2 // Write - primary
3 const [created] = await writeDb.insert(posts).values(data).returning()
4 
5 // Critical read-after-write - primary'den oku
6 const post = await writeDb.query.posts.findFirst({
7 where: eq(posts.id, created.id),
8 with: { author: true },
9 })
10 
11 return post
12}

Connection Pooling ve Caching

Turso libSQL protokolü HTTP/2 üzerinden çalışır ve connection pool yönetimi platform tarafından yapılır. Ama uygulama seviyesinde de optimizasyon gerekir:

typescript
1// src/db/cache.ts
2import { cache } from 'react'
3import { db } from './client'
4import { users } from './schema'
5import { eq } from 'drizzle-orm'
6 
7// React cache ile per-request memoization
8export const getUser = cache(async (id: string) => {
9 return db.query.users.findFirst({
10 where: eq(users.id, id),
11 })
12})
13 
14// Next.js unstable_cache ile cross-request caching
15import { unstable_cache } from 'next/cache'
16 
17export const getPublicPosts = unstable_cache(
18 async () => {
19 return db.query.posts.findMany({
20 where: (posts, { isNotNull }) => isNotNull(posts.publishedAt),
21 orderBy: (posts, { desc }) => [desc(posts.publishedAt)],
22 limit: 20,
23 })
24 },
25 ['public-posts'],
26 { revalidate: 60 } // 60 saniye cache
27)

Drizzle vs Prisma: Doğru Araç Seçimi

swift
1Drizzle tercih edilmeli:
2- Edge runtime(Cloudflare Workers, Vercel Edge)
3- Performance kritik uygulamalar
4- SQL'e hakim geliştiriciler
5- Type-safe SQL queries
6- Küçük/orta ölçekli projeler
7 
8Prisma tercih edilmeli:
9- Büyük ekipler, güçlü migration tooling gereksinimi
10- Code generation tercih edenler
11- Zengin GUI(Prisma Studio)
12- Karmaşık ilişkisel modeller
13- Node.js-only ortamlar

Prisma'nın edge uyumsuzluğu Accelerate servisi ile çözülüyor ama bu ek maliyet demek. Drizzle native edge support ile daha ekonomik.


Pricing ve Production Stratejisi

Turso Pricing (2026):

  • Free: 5GB storage, 1B row reads/ay, 25M row writes/ay
  • Starter: $25/ay - 24GB storage, unlimited row reads
  • Scaler: $200/ay - 100GB, 1000 veritabanı, branching
  • Enterprise: Özel

100K req/sec için production checklist:

  1. Embedded replica aktif (local read)
  2. Read/write connection ayrımı
  3. React cache ile request deduplication
  4. unstable_cache ile cross-request caching
  5. CDN edge'de static page caching
  6. Streaming ile TTFB optimizasyonu
  7. Monitoring: Turso dashboard + custom metrics

typescript
1// Drizzle transaction ile atomic operations
2async function transferCredits(
3 fromUserId: string,
4 toUserId: string,
5 amount: number
6) {
7 return await db.transaction(async (tx) => {
8 // From kullanıcı bakiyesini güncelle
9 const [fromUser] = await tx
10 .update(users)
11 .set({ credits: sql`credits - ${amount}` })
12 .where(
13 and(
14 eq(users.id, fromUserId),
15 gte(users.credits, amount) // Yeterli bakiye kontrolü
16 )
17 )
18 .returning({ newCredits: users.credits })
19 
20 if (!fromUser) {
21 tx.rollback() // Yetersiz bakiye - rollback
22 return { error: 'Yetersiz bakiye' }
23 }
24 
25 // To kullanıcı bakiyesini güncelle
26 await tx
27 .update(users)
28 .set({ credits: sql`credits + ${amount}` })
29 .where(eq(users.id, toUserId))
30 
31 // Transfer kaydı oluştur
32 await tx.insert(creditTransfers).values({
33 fromUserId,
34 toUserId,
35 amount,
36 completedAt: new Date(),
37 })
38 
39 return { success: true, newBalance: fromUser.newCredits }
40 })
41}

ALTIN İPUCU

Bu yazının en değerli bilgisi

Bu ipucu, yazının en önemli çıkarımını içeriyor.

typescript
1import { sql } from 'drizzle-orm'
2 
3// SQLite JSON functions ile type-safe query
4const result = await db.select({
5 name: users.name,
6 // JSON extract ile type cast
7 firstTag: sql`json_extract(${posts.metadata}, '$.tags[0]')`,
8 totalViews: sql`json_extract(${posts.metadata}, '$.views')`,
9}).from(users)
10 .leftJoin(posts, eq(users.id, posts.authorId))
11 
12// result tipi: { id: string, name: string, firstTag: string, totalViews: number }[]

Easter Egg

Gizli bir bilgi buldun!

Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?

sql
1-- Migration: FTS5 virtual table
2CREATE VIRTUAL TABLE posts_fts USING fts5(
3 title,
4 content,
5 content=posts,
6 content_rowid=rowid
7);
8 
9-- Trigger ile senkronize et
10CREATE TRIGGER posts_fts_insert AFTER INSERT ON posts BEGIN
11 INSERT INTO posts_fts(rowid, title, content)
12 VALUES (new.rowid, new.title, new.content);
13END;
typescript
1// FTS query ile Drizzle raw SQL
2export async function searchPosts(query: string) {
3 const results = await db.all(
4 sql`
5 SELECT p.id, p.title, p.slug,
6 snippet(posts_fts, 1, '', '', '...', 64) as excerpt,
7 bm25(posts_fts) as rank
8 FROM posts_fts
9 JOIN posts p ON p.rowid = posts_fts.rowid
10 WHERE posts_fts MATCH ${query}
11 ORDER BY rank
12 LIMIT 20
13 `
14 )
15 return results as Array<{
16 title: string
17 slug: string
18 excerpt: string
19 rank: number
20 }>
21}

Okuyucu Ödülü

SQLite FTS5 uzantısı ile Drizzle'da full-text search:

Sonuç

Drizzle ORM + Turso kombinasyonu, 2026 edge computing ortamında veritabanı mimarisinin yeni standardını belirliyor. Schema-first TypeScript approach, zero-overhead type inference ve native edge compatibility ile bu ikili güçlü bir stack oluşturuyor.

Temel çıkarımlar:

  • .$inferSelect ve .$inferInsert kullanarak duplicate type tanımlamaktan kacının
  • Embedded replicas ile edge latency'yi 3ms'ye kadar indirin
  • Write primary / read replica ayrımı ile 100K req/sec'ye ulaşın
  • Server Actions ile type-safe mutation pattern'ları kurun
  • FTS5 ile SQLite native full-text search ekleyin

Turso'nun $25/ay Starter planı ile başlayabilir, büyüdükçe ölçekleyebilirsiniz. Prisma'dan migration için drizzle-kit introspect komutu mevcut schema'yı otomatik analiz eder.

İlgili Kaynaklar:

Site İçi İlgili Yazılar:

Etiketler

#Drizzle ORM#Turso#libSQL#SQLite#Edge#TypeScript#2026
Muhittin Çamdalı

Muhittin Çamdalı

Senior iOS Developer

12+ yıllık deneyime sahip iOS Developer. Swift, SwiftUI ve modern iOS mimarileri konusunda uzman. Apple platformlarında performanslı ve kullanıcı dostu uygulamalar geliştiriyorum.

iOS Geliştirme Haberleri

Haftalık Swift tips, SwiftUI tricks ve iOS best practices. Spam yok, sadece değerli içerik.

Gizliliğinize saygı duyuyoruz. İstediğiniz zaman abonelikten çıkabilirsiniz.

Paylaş

Bunu da begenebilirsiniz