Skip to content

D1 Database API

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
  1. D1 Binding’e Erişim
  2. prepare() Metodu
  3. batch() Metodu
  4. exec() Metodu
  5. Error Handling
  6. Performans İpuçları

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 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 kullanıyorsanız, tip tanımlarını ekleyin:

worker-configuration.d.ts
interface Env {
DB: D1Database;
}
// Veya wrangler types komutu ile otomatik oluşturun
// npm run cf-typegen

prepare() metodu, SQL sorgularını hazırlamak için kullanılır:

const db = env.DB;
// Sorguyu hazırla
const stmt = db.prepare("SELECT * FROM users WHERE username = ?");
// Parametreleri bağla
const boundStmt = stmt.bind("ahmet");
// Sorguyu çalıştır
const { results } = await boundStmt.all();

Metodlar zincirlenebilir:

// Kısa yazım
const { results } = await db
.prepare("SELECT * FROM users WHERE username = ?")
.bind("ahmet")
.all();
const { results } = await db
.prepare("SELECT * FROM users WHERE email = ? AND username = ?")
.bind("ahmet@example.com", "ahmet")
.all();
const { results } = await db
.prepare("SELECT * FROM users WHERE email = ?2 AND username = ?1")
.bind("ahmet", "ahmet@example.com")
.all();

batch() metodu, çoklu sorguları tek bir çağrıda çalıştırır:

const db = env.DB;
// Çoklu sorgu hazırla
const 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ır
const results = await db.batch(stmts);
// Sonuçları kullan
console.log(results[0].results); // Users
console.log(results[1].results); // Posts
console.log(results[2].results); // Count

Batch işlemler, ayrı ayrı sorgulamaktan daha performanslıdır:

// ❌ Yavaş: Ayrı ayrı sorgular
const 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şlem
const 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),
]);

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 olur

exec() metodu, SQL komutlarını doğrudan çalıştırır:

const db = env.DB;
// Tek komut çalıştır
const result = await db.exec("CREATE TABLE IF NOT EXISTS test (id INTEGER)");
console.log(result); // { count: 1, duration: 15 }

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 }

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)

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 });
}
},
};
// 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..."
}
// Tip uyumsuzluğu
try {
// 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..."
}
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ım
const { results } = await queryWithRetry(async () => {
return await db.prepare("SELECT * FROM users").all();
});

// ✅ İYİ: Prepared statement
const 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 statement
const 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();
// ✅ İYİ: Batch işlem
const 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ı sorgular
const 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();
// ✅ İYİ: Sadece gerekli sütunlar
const { results } = await db
.prepare("SELECT username, email FROM users WHERE id = ?")
.bind(userId)
.all();
// ❌ KÖTÜ: Tüm sütunlar
const { results } = await db
.prepare("SELECT * FROM users WHERE id = ?")
.bind(userId)
.all();
// ✅ İYİ: Limit ile sınırlama
const { results } = await db
.prepare("SELECT * FROM posts WHERE published = 1 LIMIT 100")
.all();
// ❌ KÖTÜ: Tüm veriyi çek
const { results } = await db
.prepare("SELECT * FROM posts WHERE published = 1")
.all();

src/repositories/user.repository.ts
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;
}
}
src/services/data-processor.ts
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,
};
}
}
src/services/cached-user-service.ts
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);
}
}

// ✅ Prepared statements ile tekrar kullanılabilir stmt
const getUserStmt = db.prepare("SELECT * FROM users WHERE id = ?");
const user1 = await getUserStmt.bind(1).first();
const user2 = await getUserStmt.bind(2).first();
// ✅ Batch işlemleri
const results = await db.batch([
db.prepare("SELECT * FROM users WHERE id = ?").bind(1),
db.prepare("SELECT * FROM posts WHERE user_id = ?").bind(1),
]);
// ✅ Parametre binding
const { results } = await db
.prepare("SELECT * FROM users WHERE username = ? AND email = ?")
.bind(username, email)
.all();
// ✅ Error handling
try {
await db.prepare("INSERT INTO users ...").run();
} catch (error) {
// Hata yönetimi
}
// ❌ 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 çekme
const { 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ı

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.
  1. prepare() ve exec() metotları arasındaki fark nedir?
  2. Batch işlemleri neden daha performanslıdır?
  3. SQL injection’den korunmak için ne yapmalıyız?
  4. Retry stratejisi neden önemlidir?
  5. 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ı