# D1 Configuration ## wrangler.jsonc Setup ```jsonc { "name": "your-worker-name", "main": "src/index.ts", "compatibility_date": "2025-01-01", // Use current date for new projects "d1_databases": [ { "binding": "DB", // Env variable name "database_name": "your-db-name", // Human-readable name "database_id": "your-database-id", // UUID from dashboard/CLI "migrations_dir": "migrations" // Optional: default is "migrations" }, // Read replica (paid plans only) { "binding": "DB_REPLICA", "database_name": "your-db-name", "database_id": "your-database-id" // Same ID, different binding }, // Multiple databases { "binding": "ANALYTICS_DB", "database_name": "analytics-db", "database_id": "yyy-yyy-yyy" } ] } ``` ## TypeScript Types ```typescript interface Env { DB: D1Database; ANALYTICS_DB?: D1Database; } export default { async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise { const result = await env.DB.prepare('SELECT * FROM users').all(); return Response.json(result.results); } } ``` ## Migrations File structure: `migrations/0001_initial_schema.sql`, `0002_add_posts.sql`, etc. ### Example Migration ```sql -- migrations/0001_initial_schema.sql CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT UNIQUE NOT NULL, name TEXT NOT NULL, created_at TEXT DEFAULT CURRENT_TIMESTAMP, updated_at TEXT DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_users_email ON users(email); CREATE TABLE IF NOT EXISTS posts ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, title TEXT NOT NULL, content TEXT, published BOOLEAN DEFAULT 0, created_at TEXT DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); CREATE INDEX idx_posts_user_id ON posts(user_id); CREATE INDEX idx_posts_published ON posts(published); ``` ### Running Migrations ```bash # Create new migration file wrangler d1 migrations create add_users_table # Creates: migrations/0001_add_users_table.sql # Apply migrations wrangler d1 migrations apply --local # Apply to local DB wrangler d1 migrations apply --remote # Apply to production DB # List applied migrations wrangler d1 migrations list --remote # Direct SQL execution (bypasses migration tracking) wrangler d1 execute --remote --command="SELECT * FROM users" wrangler d1 execute --local --file=./schema.sql ``` **Migration tracking**: Wrangler creates `d1_migrations` table automatically to track applied migrations ## Indexing Strategy ```sql -- Index frequently queried columns CREATE INDEX idx_users_email ON users(email); -- Composite indexes for multi-column queries CREATE INDEX idx_posts_user_published ON posts(user_id, published); -- Covering indexes (include queried columns) CREATE INDEX idx_users_email_name ON users(email, name); -- Partial indexes for filtered queries CREATE INDEX idx_active_users ON users(email) WHERE active = 1; -- Check if query uses index EXPLAIN QUERY PLAN SELECT * FROM users WHERE email = ?; ``` ## Drizzle ORM ```typescript // drizzle.config.ts export default { schema: './src/schema.ts', out: './migrations', dialect: 'sqlite', driver: 'd1-http', dbCredentials: { accountId: process.env.CLOUDFLARE_ACCOUNT_ID!, databaseId: process.env.D1_DATABASE_ID!, token: process.env.CLOUDFLARE_API_TOKEN! } } satisfies Config; // schema.ts import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'; export const users = sqliteTable('users', { id: integer('id').primaryKey({ autoIncrement: true }), email: text('email').notNull().unique(), name: text('name').notNull() }); // worker.ts import { drizzle } from 'drizzle-orm/d1'; import { users } from './schema'; export default { async fetch(request: Request, env: Env) { const db = drizzle(env.DB); return Response.json(await db.select().from(users)); } } ``` ## Import & Export ```bash # Export full database (schema + data) wrangler d1 export --remote --output=./backup.sql # Export data only (no schema) wrangler d1 export --remote --no-schema --output=./data-only.sql # Export with foreign key constraints preserved # (Default: foreign keys are disabled during export for import compatibility) # Import SQL file wrangler d1 execute --remote --file=./backup.sql # Limitations # - BLOB data may not export correctly (use R2 for binary files) # - Very large exports (>1GB) may timeout (split into chunks) # - Import is NOT atomic (use batch() for transactional imports in Workers) ``` ## Plan Tiers | Feature | Free | Paid | |---------|------|------| | Database size | 500 MB | 10 GB | | Batch size | 1,000 statements | 10,000 statements | | Time Travel | 7 days | 30 days | | Read replicas | ❌ | ✅ | | Sessions API | ❌ | ✅ (up to 15 min) | | Pricing | Free | $5/mo + usage | **Usage pricing** (paid plans): $0.001 per 1K reads + $1 per 1M writes + $0.75/GB storage/month ## Local Development ```bash wrangler dev --persist-to=./.wrangler/state # Persist across restarts # Local DB: .wrangler/state/v3/d1/.sqlite sqlite3 .wrangler/state/v3/d1/.sqlite # Inspect # Local dev uses free tier limits by default ```