D1 Database API
Ders 6: D1 Database API
Section titled “Ders 6: D1 Database API”Öğrenme Hedefleri
Section titled “Öğrenme Hedefleri”Bu dersi tamamladıktan sonra:
- D1 Database API’nin temel metodlarını kullanabileceksiniz
- prepare(), batch(), exec() metodlarını ne zaman kullanacağınızı öğreneceksiniz
- Workers içinde D1’e nasıl erişeceğinizi kavrayacaksınız
- Error handling ve retry stratejilerini uygulayabileceksiniz
İçerik İçindekiler
Section titled “İçerik İçindekiler”- D1 Binding’e Erişim
- prepare() Metodu
- batch() Metodu
- exec() Metodu
- Error Handling
- Performans İpuçları
D1 Binding’e Erişim
Section titled “D1 Binding’e Erişim”Binding Tanımlama
Section titled “Binding Tanımlama”wrangler.toml dosyasında D1 binding tanımlanır:
[[d1_databases]]binding = "DB"database_name = "my-database"database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"Worker’da Erişim
Section titled “Worker’da Erişim”Worker kodunda D1’e env nesnesi üzerinden erişilir:
export interface Env { DB: D1Database;}
export default { async fetch(request: Request, env: Env, ctx: ExecutionContext) { // D1'ye eriş const db = env.DB;
// Veritabanı işlemleri yap const { results } = await db.prepare("SELECT * FROM users").all();
return Response.json(results); },};TypeScript Desteği
Section titled “TypeScript Desteği”TypeScript kullanıyorsanız, tip tanımlarını ekleyin:
interface Env { DB: D1Database;}
// Veya wrangler types komutu ile otomatik oluşturun// npm run cf-typegenprepare() Metodu
Section titled “prepare() Metodu”Temel Kullanım
Section titled “Temel Kullanım”prepare() metodu, SQL sorgularını hazırlamak için kullanılır:
const db = env.DB;
// Sorguyu hazırlaconst stmt = db.prepare("SELECT * FROM users WHERE username = ?");
// Parametreleri bağlaconst boundStmt = stmt.bind("ahmet");
// Sorguyu çalıştırconst { results } = await boundStmt.all();Zincirleme (Chaining)
Section titled “Zincirleme (Chaining)”Metodlar zincirlenebilir:
// Kısa yazımconst { results } = await db .prepare("SELECT * FROM users WHERE username = ?") .bind("ahmet") .all();Parametre Binding
Section titled “Parametre Binding”Anonim Parametreler (?)
Section titled “Anonim Parametreler (?)”const { results } = await db .prepare("SELECT * FROM users WHERE email = ? AND username = ?") .bind("ahmet@example.com", "ahmet") .all();Sıralı Parametreler (?1, ?2, …)
Section titled “Sıralı Parametreler (?1, ?2, …)”const { results } = await db .prepare("SELECT * FROM users WHERE email = ?2 AND username = ?1") .bind("ahmet", "ahmet@example.com") .all();batch() Metodu
Section titled “batch() Metodu”Temel Kullanım
Section titled “Temel Kullanım”batch() metodu, çoklu sorguları tek bir çağrıda çalıştırır:
const db = env.DB;
// Çoklu sorgu hazırlaconst stmts = [ db.prepare("SELECT * FROM users WHERE id = ?").bind(1), db.prepare("SELECT * FROM posts WHERE user_id = ?").bind(1), db.prepare("SELECT COUNT(*) as count FROM posts WHERE user_id = ?").bind(1),];
// Tüm sorguları çalıştırconst results = await db.batch(stmts);
// Sonuçları kullanconsole.log(results[0].results); // Usersconsole.log(results[1].results); // Postsconsole.log(results[2].results); // CountPerformans Avantajı
Section titled “Performans Avantajı”Batch işlemler, ayrı ayrı sorgulamaktan daha performanslıdır:
// ❌ Yavaş: Ayrı ayrı sorgularconst user = await db.prepare("SELECT * FROM users WHERE id = ?").bind(1).first();const posts = await db.prepare("SELECT * FROM posts WHERE user_id = ?").bind(1).all();const count = await db.prepare("SELECT COUNT(*) FROM posts WHERE user_id = ?").bind(1).first();
// ✅ Hızlı: Batch işlemconst results = await db.batch([ db.prepare("SELECT * FROM users WHERE id = ?").bind(1), db.prepare("SELECT * FROM posts WHERE user_id = ?").bind(1), db.prepare("SELECT COUNT(*) as count FROM posts WHERE user_id = ?").bind(1),]);Transaction Garantisi
Section titled “Transaction Garantisi”Batch işlemleri transaction garantisi sunar:
const results = await db.batch([ db.prepare("INSERT INTO users (username) VALUES (?)").bind("newuser"), db.prepare("INSERT INTO posts (user_id, title) VALUES (?, ?)").bind(1, "First Post"),]);
// Her iki işlem de başarılı olur veya ikisi de başarısız olurexec() Metodu
Section titled “exec() Metodu”Temel Kullanım
Section titled “Temel Kullanım”exec() metodu, SQL komutlarını doğrudan çalıştırır:
const db = env.DB;
// Tek komut çalıştırconst result = await db.exec("CREATE TABLE IF NOT EXISTS test (id INTEGER)");
console.log(result); // { count: 1, duration: 15 }Çoklu Komut
Section titled “Çoklu Komut”Birden fazla SQL komutunu newline ile ayırarak çalıştırabilirsiniz:
const sql = ` CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY, username TEXT );
CREATE TABLE IF NOT EXISTS posts ( id INTEGER PRIMARY KEY, user_id INTEGER, title TEXT );
INSERT INTO users (id, username) VALUES (1, 'test');`;
const result = await db.exec(sql);console.log(result); // { count: 3, duration: 45 }Kullanım Alanları
Section titled “Kullanım Alanları”exec() metodu şunlar için uygundur:
- ✅ DDL işlemleri (CREATE, ALTER, DROP)
- ✅ Schema migration’ları
- ✅ Toplu veri işlemleri
- ❌ Parametreli sorgular (prepare() kullanın)
- ❌ Production sorguları (prepared statements kullanın)
Error Handling
Section titled “Error Handling”Try-Catch Kullanımı
Section titled “Try-Catch Kullanımı”export default { async fetch(request: Request, env: Env, ctx: ExecutionContext) { try { const { results } = await env.DB .prepare("SELECT * FROM users WHERE username = ?") .bind("ahmet") .all();
return Response.json(results); } catch (error) { console.error("Database error:", error);
return Response.json({ error: "Database query failed", message: error.message }, { status: 500 }); } },};Yaygın Hatalar
Section titled “Yaygın Hatalar”D1_EXEC_ERROR
Section titled “D1_EXEC_ERROR”// SQL syntax hatasıtry { await db.exec("SELECT * FORM users"); // "FORM" yerine "FROM" olmalı} catch (error) { console.error(error.message); // "D1_EXEC_ERROR: Error in line 1: SELECT * FORM users..."}D1_TYPE_ERROR
Section titled “D1_TYPE_ERROR”// Tip uyumsuzluğutry { // undefined yerine null kullanılmalı await db.prepare("INSERT INTO users (username) VALUES (?)").bind(undefined).run();} catch (error) { console.error(error.message); // "D1_TYPE_ERROR: Unsupported type for parameter..."}Retry Stratejisi
Section titled “Retry Stratejisi”async function queryWithRetry<T>( query: () => Promise<T>, maxRetries = 3): Promise<T> { let lastError: Error;
for (let i = 0; i < maxRetries; i++) { try { return await query(); } catch (error) { lastError = error;
// Retry edilebilir hataları kontrol et const isRetryable = error.message.includes("Network connection lost") || error.message.includes("storage caused object to be reset");
if (!isRetryable || i === maxRetries - 1) { throw error; }
// Exponential backoff const delay = Math.pow(2, i) * 100; await new Promise(resolve => setTimeout(resolve, delay)); } }
throw lastError;}
// Kullanımconst { results } = await queryWithRetry(async () => { return await db.prepare("SELECT * FROM users").all();});Performans İpuçları
Section titled “Performans İpuçları”1. Prepared Statements Kullanın
Section titled “1. Prepared Statements Kullanın”// ✅ İYİ: Prepared statementconst stmt = db.prepare("SELECT * FROM users WHERE username = ?");const user1 = await stmt.bind("user1").first();const user2 = await stmt.bind("user2").first();
// ❌ KÖTÜ: Her seferinde yeni statementconst user1 = await db.prepare("SELECT * FROM users WHERE username = ?").bind("user1").first();const user2 = await db.prepare("SELECT * FROM users WHERE username = ?").bind("user2").first();2. Batch İşlemlerini Kullanın
Section titled “2. Batch İşlemlerini Kullanın”// ✅ İYİ: Batch işlemconst results = await db.batch([ db.prepare("SELECT * FROM users WHERE id = ?").bind(1), db.prepare("SELECT * FROM posts WHERE user_id = ?").bind(1),]);
// ❌ KÖTÜ: Ayrı sorgularconst users = await db.prepare("SELECT * FROM users WHERE id = ?").bind(1).all();const posts = await db.prepare("SELECT * FROM posts WHERE user_id = ?").bind(1).all();3. Gereksiz Veri Çekmeyin
Section titled “3. Gereksiz Veri Çekmeyin”// ✅ İYİ: Sadece gerekli sütunlarconst { results } = await db .prepare("SELECT username, email FROM users WHERE id = ?") .bind(userId) .all();
// ❌ KÖTÜ: Tüm sütunlarconst { results } = await db .prepare("SELECT * FROM users WHERE id = ?") .bind(userId) .all();4. LIMIT Kullanın
Section titled “4. LIMIT Kullanın”// ✅ İYİ: Limit ile sınırlamaconst { results } = await db .prepare("SELECT * FROM posts WHERE published = 1 LIMIT 100") .all();
// ❌ KÖTÜ: Tüm veriyi çekconst { results } = await db .prepare("SELECT * FROM posts WHERE published = 1") .all();Pratik Örnekler
Section titled “Pratik Örnekler”Örnek 1: User Repository
Section titled “Örnek 1: User Repository”export interface User { id: number; email: string; username: string; first_name: string | null; last_name: string | null; created_at: string;}
export class UserRepository { constructor(private db: D1Database) {}
async findById(id: number): Promise<User | null> { const result = await this.db .prepare("SELECT * FROM users WHERE id = ?") .bind(id) .first<User>();
return result || null; }
async findByEmail(email: string): Promise<User | null> { const result = await this.db .prepare("SELECT * FROM users WHERE email = ?") .bind(email) .first<User>();
return result || null; }
async findAll(limit = 100, offset = 0): Promise<User[]> { const { results } = await this.db .prepare("SELECT * FROM users ORDER BY created_at DESC LIMIT ? OFFSET ?") .bind(limit, offset) .all<User>();
return results; }
async create(data: Omit<User, 'id' | 'created_at'>): Promise<User> { const result = await this.db .prepare( "INSERT INTO users (email, username, first_name, last_name) VALUES (?, ?, ?, ?)" ) .bind(data.email, data.username, data.first_name, data.last_name) .run();
const newUser = await this.findById(result.meta.last_row_id); if (!newUser) { throw new Error("Failed to create user"); }
return newUser; }
async update(id: number, data: Partial<Omit<User, 'id' | 'created_at'>>): Promise<User | null> { const fields = Object.keys(data); const values = Object.values(data);
if (fields.length === 0) { return this.findById(id); }
const setClause = fields.map(field => `${field} = ?`).join(", "); const query = `UPDATE users SET ${setClause} WHERE id = ?`;
await this.db .prepare(query) .bind(...values, id) .run();
return this.findById(id); }
async delete(id: number): Promise<boolean> { const result = await this.db .prepare("DELETE FROM users WHERE id = ?") .bind(id) .run();
return (result.meta.changes || 0) > 0; }}Örnek 2: Batch ile Veri İşleme
Section titled “Örnek 2: Batch ile Veri İşleme”export class DataProcessor { constructor(private db: D1Database) {}
async bulkInsertUsers(users: Array<{ email: string; username: string; first_name: string; last_name: string; }>): Promise<void> { const batchSize = 100;
for (let i = 0; i < users.length; i += batchSize) { const batch = users.slice(i, i + batchSize);
const statements = batch.map(user => this.db.prepare( "INSERT INTO users (email, username, first_name, last_name) VALUES (?, ?, ?, ?)" ).bind(user.email, user.username, user.first_name, user.last_name) );
await this.db.batch(statements); } }
async getUserWithPosts(userId: number): Promise<{ user: User | null; posts: Post[]; postCount: number; } | null> { const results = await this.db.batch([ this.db.prepare("SELECT * FROM users WHERE id = ?").bind(userId), this.db.prepare("SELECT * FROM posts WHERE user_id = ?").bind(userId), this.db.prepare("SELECT COUNT(*) as count FROM posts WHERE user_id = ?").bind(userId), ]);
const user = results[0].results[0] as User | undefined; if (!user) return null;
return { user, posts: results[1].results as Post[], postCount: (results[2].results[0] as { count: number }).count, }; }}Örnek 3: Cache Katmanı ile D1
Section titled “Örnek 3: Cache Katmanı ile D1”export class CachedUserService { private cache = new Map<string, { data: any, expiry: number }>();
constructor( private db: D1Database, private cacheTTL = 60000 // 1 dakika ) {}
async getUserById(id: number): Promise<User | null> { const cacheKey = `user:${id}`; const cached = this.cache.get(cacheKey);
// Cache kontrolü if (cached && cached.expiry > Date.now()) { return cached.data; }
// Veritabanından getir const user = await this.db .prepare("SELECT * FROM users WHERE id = ?") .bind(id) .first<User>();
if (!user) return null;
// Cache'e ekle this.cache.set(cacheKey, { data: user, expiry: Date.now() + this.cacheTTL });
return user; }
async invalidateUser(id: number): Promise<void> { const cacheKey = `user:${id}`; this.cache.delete(cacheKey); }}İyi Pratikler
Section titled “İyi Pratikler”✅ Doğru Kullanım
Section titled “✅ Doğru Kullanım”// ✅ Prepared statements ile tekrar kullanılabilir stmtconst getUserStmt = db.prepare("SELECT * FROM users WHERE id = ?");const user1 = await getUserStmt.bind(1).first();const user2 = await getUserStmt.bind(2).first();
// ✅ Batch işlemlericonst results = await db.batch([ db.prepare("SELECT * FROM users WHERE id = ?").bind(1), db.prepare("SELECT * FROM posts WHERE user_id = ?").bind(1),]);
// ✅ Parametre bindingconst { results } = await db .prepare("SELECT * FROM users WHERE username = ? AND email = ?") .bind(username, email) .all();
// ✅ Error handlingtry { await db.prepare("INSERT INTO users ...").run();} catch (error) { // Hata yönetimi}❌ Yanlış Kullanım
Section titled “❌ Yanlış Kullanım”// ❌ SQL injection riski (asla kullanmayın!)const username = req.query.username;const { results } = await db .prepare(`SELECT * FROM users WHERE username = '${username}'`) .all();
// ❌ Gereksiz veri çekmeconst { results } = await db.prepare("SELECT * FROM users").all();
// ❌ Timeout riski (çok büyük batch)const statements = Array(10000).fill( db.prepare("INSERT INTO users ..."));await db.batch(statements); // Çok büyük batch!Bu derste aşağıdaki konuları öğrendiniz:
✅ D1 Database API’nin temel metodları (prepare, batch, exec) ✅ Workers içinde D1’e erişim ✅ Prepared statements ve parameter binding ✅ Batch işlemleri ve performans avantajları ✅ Error handling ve retry stratejileri ✅ Pratik repository pattern uygulamaları
Sonraki Ders
Section titled “Sonraki Ders”Bir sonraki dersimizde “Prepared Statements” başlığı altında:
- bind(), run(), raw(), first() metodları detaylı şekilde
- Return objects ve meta data
- Best practices ve performance optimization konularını inceleyeceğiz.
Kaynaklar
Section titled “Kaynaklar”Alıştırma Soruları
Section titled “Alıştırma Soruları”- prepare() ve exec() metotları arasındaki fark nedir?
- Batch işlemleri neden daha performanslıdır?
- SQL injection’den korunmak için ne yapmalıyız?
- Retry stratejisi neden önemlidir?
- Cache katmanı ile D1 nasıl entegre edilir?
Ders Süresi: 75 dakika Zorluk Seviyesi: Orta Ön Koşullar: Ders 5: Wrangler ile D1 Kullanımı