# D1 API Reference ## Prepared Statements (Required for Security) ```typescript // ❌ NEVER: Direct string interpolation (SQL injection risk) const result = await env.DB.prepare(`SELECT * FROM users WHERE id = ${userId}`).all(); // ✅ CORRECT: Prepared statements with bind() const result = await env.DB.prepare('SELECT * FROM users WHERE id = ?').bind(userId).all(); // Multiple parameters const result = await env.DB.prepare('SELECT * FROM users WHERE email = ? AND active = ?').bind(email, true).all(); ``` ## Query Execution Methods ```typescript // .all() - Returns all rows const { results, success, meta } = await env.DB.prepare('SELECT * FROM users WHERE active = ?').bind(true).all(); // results: Array of row objects; success: boolean // meta: { duration: number, rows_read: number, rows_written: number } // .first() - Returns first row or null const user = await env.DB.prepare('SELECT * FROM users WHERE id = ?').bind(userId).first(); // .first(columnName) - Returns single column value const email = await env.DB.prepare('SELECT email FROM users WHERE id = ?').bind(userId).first('email'); // Returns string | number | null // .run() - For INSERT/UPDATE/DELETE (no row data returned) const result = await env.DB.prepare('UPDATE users SET last_login = ? WHERE id = ?').bind(Date.now(), userId).run(); // result.meta: { duration, rows_read, rows_written, last_row_id, changes } // .raw() - Returns array of arrays (efficient for large datasets) const rawResults = await env.DB.prepare('SELECT id, name FROM users').raw(); // [[1, 'Alice'], [2, 'Bob']] ``` ## Batch Operations ```typescript // Execute multiple queries in single round trip (atomic transaction) const results = await env.DB.batch([ env.DB.prepare('SELECT * FROM users WHERE id = ?').bind(1), env.DB.prepare('SELECT * FROM posts WHERE author_id = ?').bind(1), env.DB.prepare('UPDATE users SET last_access = ? WHERE id = ?').bind(Date.now(), 1) ]); // results is array: [result1, result2, result3] // Batch with same prepared statement, different params const userIds = [1, 2, 3]; const stmt = env.DB.prepare('SELECT * FROM users WHERE id = ?'); const results = await env.DB.batch(userIds.map(id => stmt.bind(id))); ``` ## Transactions (via batch) ```typescript // D1 executes batch() as atomic transaction - all succeed or all fail const results = await env.DB.batch([ env.DB.prepare('INSERT INTO accounts (id, balance) VALUES (?, ?)').bind(1, 100), env.DB.prepare('INSERT INTO accounts (id, balance) VALUES (?, ?)').bind(2, 200), env.DB.prepare('UPDATE accounts SET balance = balance - ? WHERE id = ?').bind(50, 1), env.DB.prepare('UPDATE accounts SET balance = balance + ? WHERE id = ?').bind(50, 2) ]); ``` ## Sessions API (Paid Plans) Long-running sessions for operations exceeding 30s timeout (up to 15 min). ```typescript const session = env.DB.withSession({ timeout: 600 }); // 10 min (1-900s) try { await session.prepare('CREATE INDEX idx_large ON big_table(column)').run(); await session.prepare('ANALYZE').run(); } finally { session.close(); // CRITICAL: always close to prevent leaks } ``` **Use cases**: Migrations, ANALYZE, large index creation, bulk transformations ## Read Replication (Paid Plans) Routes queries to nearest replica for lower latency. Writes always go to primary. ```typescript interface Env { DB: D1Database; // Primary (writes) DB_REPLICA: D1Database; // Replica (reads) } // Reads: use replica const user = await env.DB_REPLICA.prepare('SELECT * FROM users WHERE id = ?').bind(userId).first(); // Writes: use primary await env.DB.prepare('UPDATE users SET last_login = ? WHERE id = ?').bind(Date.now(), userId).run(); // Read-after-write: use primary for consistency (replication lag <100ms-2s) await env.DB.prepare('INSERT INTO posts (title) VALUES (?)').bind(title).run(); const post = await env.DB.prepare('SELECT * FROM posts WHERE title = ?').bind(title).first(); // Primary ``` ## Error Handling ```typescript async function getUser(userId: number, env: Env): Promise { try { const result = await env.DB.prepare('SELECT * FROM users WHERE id = ?').bind(userId).all(); if (!result.success) return new Response('Database error', { status: 500 }); if (result.results.length === 0) return new Response('User not found', { status: 404 }); return Response.json(result.results[0]); } catch (error) { return new Response('Internal error', { status: 500 }); } } // Constraint violations try { await env.DB.prepare('INSERT INTO users (email, name) VALUES (?, ?)').bind(email, name).run(); } catch (error) { if (error.message?.includes('UNIQUE constraint failed')) return new Response('Email exists', { status: 409 }); throw error; } ``` ## REST API (HTTP) Access Access D1 from external services (non-Worker contexts) using Cloudflare API. ```typescript // Single query const response = await fetch( `https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/d1/database/${DATABASE_ID}/query`, { method: 'POST', headers: { 'Authorization': `Bearer ${CLOUDFLARE_API_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ sql: 'SELECT * FROM users WHERE id = ?', params: [userId] }) } ); const { result, success, errors } = await response.json(); // result: [{ results: [...], success: true, meta: {...} }] // Batch queries via HTTP const response = await fetch( `https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/d1/database/${DATABASE_ID}/query`, { method: 'POST', headers: { 'Authorization': `Bearer ${CLOUDFLARE_API_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify([ { sql: 'SELECT * FROM users WHERE id = ?', params: [1] }, { sql: 'SELECT * FROM posts WHERE author_id = ?', params: [1] } ]) } ); ``` **Use cases**: Server-side scripts, CI/CD migrations, administrative tools, non-Worker integrations ## Testing & Debugging ```typescript // Vitest with unstable_dev import { unstable_dev } from 'wrangler'; describe('D1', () => { let worker: Awaited>; beforeAll(async () => { worker = await unstable_dev('src/index.ts'); }); afterAll(async () => { await worker.stop(); }); it('queries users', async () => { expect((await worker.fetch('/users')).status).toBe(200); }); }); // Debug query performance const result = await env.DB.prepare('SELECT * FROM users').all(); console.log('Duration:', result.meta.duration, 'ms'); // Query plan analysis const plan = await env.DB.prepare('EXPLAIN QUERY PLAN SELECT * FROM users WHERE email = ?').bind(email).all(); ``` ```bash # Inspect local database sqlite3 .wrangler/state/v3/d1/.sqlite .tables; .schema users; PRAGMA table_info(users); ```