ntanduy/cloudflare-d1-database
最新稳定版本:v0.8.0
Composer 安装命令:
composer require ntanduy/cloudflare-d1-database
包简介
Cloudflare D1 database driver for Laravel — full Eloquent & Query Builder support.
关键字:
README 文档
README
Use Cloudflare D1 as a native Laravel database driver — full Eloquent ORM, Query Builder, and Migration support.
🎯 Requirements
- PHP: >= 8.2
- Laravel: 10.x, 11.x, 12.x, or 13.x
✨ Features
- Full Laravel Integration — Eloquent ORM, Query Builder, Migrations, Seeding
- Two Connection Drivers — REST API (zero infrastructure) or Worker (low latency)
- Batch Queries — Execute multiple statements in a single HTTP round-trip
- Bulk Insert — Insert hundreds of rows in a single atomic batch call
- Sessions / Read Replication — Leverage D1 global read replicas for lower-latency reads (Worker driver)
- Auto Read/Write Splitting — Automatic routing of SELECTs to replicas and writes to primary (Worker driver)
- Schema Dump — Export your D1 database via
php artisan d1:schema-dump - Database Info — Inspect your D1 database with
php artisan d1:info - Circuit Breaker — Fail fast on sustained outages instead of blocking on retries
- Automatic Retries — Exponential backoff with jitter for 5xx/429 errors
- Query Logging — Optional callback for monitoring and debugging
- Health Check — Built-in
php artisan d1:healthto verify connection and measure latency
🚀 Installation
composer require ntanduy/cloudflare-d1-database
👏 Usage
Step 1: Publish Configuration
php artisan vendor:publish --tag="d1-config"
This creates config/d1-database.php with all available options.
Step 2: Choose a Driver
This package supports two drivers to connect Laravel with Cloudflare D1:
| Driver | How it works | Latency | Setup |
|---|---|---|---|
| REST (default) | Calls Cloudflare D1 REST API directly | ~100-500ms/query | API Token only |
| Worker | Routes queries through your own Cloudflare Worker | ~10-50ms/query | Requires deploying a Worker |
Driver 1: REST API (Default)
The simplest setup — no extra infrastructure needed. Queries are sent to Cloudflare's REST API.
Add to your .env:
CF_D1_API_TOKEN=your_api_token CF_D1_ACCOUNT_ID=your_account_id CF_D1_DATABASE_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
How to get these values:
- API Token — Go to Cloudflare Dashboard → API Tokens → Create Token → use the "Edit Cloudflare D1" template
- Account ID — Found on your Cloudflare Dashboard overview page (right sidebar)
- Database ID — Go to Workers & Pages → D1 → click your database → copy the Database ID
That's it! Your Laravel app can now use D1.
Driver 2: Worker (Low Latency)
For production apps that need lower latency, deploy a Cloudflare Worker as a proxy between Laravel and D1.
Add to your .env:
CF_D1_DRIVER=worker CF_D1_WORKER_URL=https://your-d1-worker.your-subdomain.workers.dev CF_D1_WORKER_SECRET=a-strong-shared-secret
Deploy the Worker
A ready-to-deploy Worker template is included in the Worker/ directory. To deploy:
cd Worker
npm install
npx wrangler secret put WORKER_SECRET
npm run deploy
Before deploying, update wrangler.jsonc with your D1 database binding:
name = "ntanduy-d1-worker" main = "src/index.ts" compatibility_date = "2026-03-10" [[d1_databases]] binding = "DB" database_name = "your-database-name" database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
Important: Set
WORKER_SECRETusingnpx wrangler secret put WORKER_SECRET— never put secrets inwrangler.jsonc. This secret must match theCF_D1_WORKER_SECRETin your Laravel.env.
Worker Endpoints
The Worker exposes these endpoints:
| Endpoint | Method | Auth | Description |
|---|---|---|---|
/health |
GET | ❌ | Health check |
/query |
POST | ✅ Bearer | Execute a single SQL query |
/batch |
POST | ✅ Bearer | Execute multiple statements atomically |
/exec |
POST | ✅ Bearer | Execute raw DDL/migration SQL |
/raw |
POST | ✅ Bearer | Execute a query and return raw array-of-arrays |
Step 3: Set as Default Connection
To use D1 as the default database, add to your .env:
DB_CONNECTION=d1
Step 4: Verify Connection
Run the built-in health check to verify your setup:
php artisan d1:health
D1 Health Check
Connection : d1
Driver : worker
+-------------------------+---------+------------------------------------------+
| Check | Status | Detail |
+-------------------------+---------+------------------------------------------+
| worker_url configured | ✓ OK | https://d1-proxy.name.workers.dev |
| worker_secret configured| ✓ OK | ******cret |
| Query test passed | ✓ OK | SELECT 1 as ok |
| End-to-end latency | ✓ OK | 24 ms |
+-------------------------+---------+------------------------------------------+
Overall: HEALTHY ✓
Step 5: Run Migrations
php artisan migrate --database=d1
📖 Examples
Eloquent ORM
use App\Models\Post; // Create $post = Post::create([ 'title' => 'Hello from D1', 'body' => 'This is stored in Cloudflare D1!', ]); // Read $posts = Post::where('published', true)->orderBy('created_at', 'desc')->get(); // Update $post->update(['title' => 'Updated Title']); // Delete $post->delete();
Query Builder
use Illuminate\Support\Facades\DB; // Select $users = DB::connection('d1')->table('users') ->where('active', true) ->limit(10) ->get(); // Insert DB::connection('d1')->table('users')->insert([ 'name' => 'John Doe', 'email' => 'john@example.com', ]); // Raw queries $results = DB::connection('d1')->select('SELECT * FROM users WHERE id = ?', [1]);
Query Logger
Monitor queries for debugging or performance analysis:
use Ntanduy\CFD1\D1\D1Connection; /** @var D1Connection $connection */ $connection = DB::connection('d1'); $connection->d1()->setQueryLogger(function ( string $query, array $params, float $timeMs, bool $success, ?array $error ) { if (! $success) { Log::error("D1 query failed: {$query}", [ 'params' => $params, 'error' => $error, 'time_ms' => $timeMs, ]); } });
Runtime Driver Detection
use Ntanduy\CFD1\D1\D1Connection; /** @var D1Connection $connection */ $connection = DB::connection('d1'); $connection->getDriver(); // 'rest' or 'worker' $connection->isWorkerDriver(); // true or false
Batch Queries
Execute multiple SQL statements in a single HTTP round-trip. On the Worker driver, this uses D1's native batch() for atomic execution.
use Ntanduy\CFD1\D1\D1Connection; /** @var D1Connection $connection */ $connection = DB::connection('d1'); $results = $connection->batch([ ['sql' => 'SELECT * FROM users WHERE id = ?', 'params' => [1]], ['sql' => 'UPDATE stats SET views = views + 1 WHERE id = ?', 'params' => [5]], ['sql' => 'SELECT COUNT(*) as total FROM posts'], ]); $user = $results[0]; // Result set from first statement $stats = $results[1]; // Result set from second statement $count = $results[2]; // Result set from third statement
If any statement fails, a D1BatchException is thrown with the index of the failing statement:
use Ntanduy\CFD1\D1\Exceptions\D1BatchException; try { $results = $connection->batch($statements); } catch (D1BatchException $e) { // $e->getMessage() includes the failing statement index }
Driver Feature Matrix
| Feature | REST | Worker |
|---|---|---|
| Bulk Insert | ✅ | ✅ |
| Sessions / Read Replication | ❌ Not supported | ✅ Full support |
| Auto Read/Write Splitting | ❌ Not supported | ✅ Full support |
| Schema Dump | ✅ | ✅ (via REST credentials) |
Database Info (d1:info) |
✅ Full metadata | ✅ Query test + REST metadata |
| Batch Queries | ✅ | ✅ |
| Circuit Breaker | ✅ | ✅ |
| Automatic Retries | ✅ | ✅ |
Bulk Insert
Insert multiple rows efficiently using D1 batch execution — one HTTP round-trip, atomic:
use Ntanduy\CFD1\D1\D1Connection; /** @var D1Connection $connection */ $connection = DB::connection('d1'); $connection->bulkInsert('users', [ ['name' => 'Alice', 'email' => 'alice@example.com'], ['name' => 'Bob', 'email' => 'bob@example.com'], ['name' => 'Charlie', 'email' => 'charlie@example.com'], ]);
- Each row becomes a parameterized INSERT (SQL injection safe)
- All rows are sent as a D1 batch (atomic — if any fails, none are applied)
- Rows exceeding D1's 100-statement batch limit are automatically chunked
- Works with both REST and Worker drivers
Sessions / Read Replication (Worker Driver Only)
D1 supports global read replication — read queries can be served by nearby replicas for lower latency. The Sessions API ensures sequential consistency across queries.
Important: Sessions are only available with the Worker driver. The REST API does not support D1 Sessions — this is a Cloudflare platform limitation.
Enable via Config
Add to your .env:
CF_D1_SESSION_ENABLED=true CF_D1_SESSION_MODE=first-unconstrained # or 'first-primary'
This automatically enables sessions for all queries on the Worker driver.
Enable Programmatically
use Ntanduy\CFD1\D1\D1Connection; /** @var D1Connection $connection */ $connection = DB::connection('d1'); // Start a session — first query goes to any instance (fastest) $connection->withSession('first-unconstrained'); // Or start with the latest data from primary $connection->withSession('first-primary'); // Execute queries — bookmarks are tracked automatically $users = DB::table('users')->get(); $posts = DB::table('posts')->get(); // Get the current bookmark (for passing to another request/session) $bookmark = $connection->getBookmark(); // Start a new session from a previous bookmark $connection->withSession($bookmark); // End the session when done $connection->endSession();
Session Modes
| Mode | First Query | Use When |
|---|---|---|
first-unconstrained |
Any instance (primary or replica) | Lowest latency, eventual consistency OK |
first-primary |
Primary database | Need the latest data for first query |
<bookmark> |
At least as fresh as the bookmark | Continuing from a previous session |
How It Works
- PHP sends a
sessionparameter with each query to the Worker - Worker calls
env.DB.withSession(param)to create a D1 session - Worker returns a
bookmarkin the response - PHP stores the bookmark and uses it for the next query
- This ensures sequential consistency across HTTP calls
Worker Template
The Worker template in Worker/ already includes session support. If you're upgrading from a previous version, redeploy the Worker:
cd Worker && npm run deploy
Auto Read/Write Splitting (Worker Driver Only)
Automatically route SELECT queries to D1 replicas and INSERT/UPDATE/DELETE to the primary — zero code changes required.
// config/database.php 'd1' => [ 'driver' => 'd1', 'd1_driver' => 'worker', 'worker_url' => env('CF_D1_WORKER_URL'), 'worker_secret' => env('CF_D1_WORKER_SECRET'), 'read' => [ 'session' => ['mode' => 'first-unconstrained'], ], 'write' => [ 'session' => ['mode' => 'first-primary'], ], 'sticky' => true, // After write, reads use primary for consistency ],
Once configured, Laravel handles everything:
// Automatically goes to replica (fast, nearby) $users = User::all(); // Automatically goes to primary User::create(['name' => 'Alice', 'email' => 'alice@example.com']); // With sticky=true, this read goes to primary (sees the new user) $user = User::where('email', 'alice@example.com')->first();
sticky(default:true) — after a write, subsequent reads in the same request use the write connector's bookmark for sequential consistency- Works alongside manual
withSession()— R/W splitting handles the base routing, you can still use sessions for fine-grained control - REST driver ignores
read/writeconfig — no sessions support, all queries go to primary
Database Info
Inspect your D1 database metadata and connection status:
php artisan d1:info
Displays database name, UUID, size, table count, read replication mode, R/W splitting status, circuit breaker state, and runs a query test.
# Specify a connection
php artisan d1:info --connection=d1
Uses the D1 REST API for metadata. Worker-only users see table count and query test but need REST credentials for full metadata.
Schema Dump
Export your D1 database schema (and optionally data) as a SQL file:
php artisan d1:schema-dump
This uses the D1 export REST API with polling mode. The dump is saved to database/schema/{connection}-schema.sql.
Options
# Schema only (no data) php artisan d1:schema-dump --no-data # Custom output path php artisan d1:schema-dump --path=./backup.sql # Delete migration files after dumping (same as native schema:dump --prune) php artisan d1:schema-dump --prune # Specify connection name php artisan d1:schema-dump --connection=d1
Note:
d1:schema-dumpalways uses the REST API for export, even when the Worker driver is your primary connection. Worker-only users must also setCF_D1_API_TOKEN,CF_D1_ACCOUNT_ID, andCF_D1_DATABASE_IDin their.envfor the dump command to work.
Circuit Breaker
Prevents cascading failures when Cloudflare Workers experience cold starts or sustained outages. Instead of blocking for 30s+ on retries, the circuit breaker fails fast after consecutive failures.
States:
CLOSED → requests pass through normally
↓ (threshold consecutive failures)
OPEN → requests rejected immediately, no HTTP call
↓ (after cooldown seconds)
HALF_OPEN → one probe request allowed through
↓ success → CLOSED | failure → OPEN
Enable in your config (config/database.php or config/d1-database.php):
'd1' => [ // ... other options ... 'circuit_breaker' => [ 'enabled' => env('CF_D1_CB_ENABLED', false), 'threshold' => env('CF_D1_CB_THRESHOLD', 5), // failures before opening 'cooldown' => env('CF_D1_CB_COOLDOWN', 30), // seconds before probe 'cache_driver' => env('CF_D1_CB_CACHE_DRIVER', 'file'), ], ],
Handle the exception:
use Ntanduy\CFD1\D1\Exceptions\CircuitBreakerOpenException; try { $users = DB::connection('d1')->table('users')->get(); } catch (CircuitBreakerOpenException $e) { // Circuit is open — fail fast, use fallback or return cached data }
Note: Use
fileorredisas thecache_driver. Avoiddatabaseto prevent a dependency loop when D1 itself is down.
Retry & Backoff
The driver automatically retries failed requests with exponential backoff and jitter:
- Retried: 5xx server errors, 429 rate limiting, connection timeouts
- Not retried: 4xx client errors (400, 401, 403, 404, etc.)
CF_D1_RETRIES=2 # Max retry attempts (default: 2) CF_D1_RETRY_DELAY=100 # Base delay in ms (default: 100) CF_D1_TIMEOUT=10 # Request timeout in seconds (default: 10) CF_D1_CONNECT_TIMEOUT=5 # Connection timeout in seconds (default: 5)
Backoff formula: delay × 2^(attempt-1) + random jitter (0-100ms)
| Attempt | Base Delay (100ms) |
|---|---|
| 1 | ~100-200ms |
| 2 | ~200-300ms |
⚙️ Configuration Reference
Manual Setup (Alternative)
Instead of publishing the config, you can add the connection directly to config/database.php:
'connections' => [ 'd1' => [ 'driver' => 'd1', 'd1_driver' => env('CF_D1_DRIVER', 'rest'), // 'rest' or 'worker' 'prefix' => '', 'database' => env('CF_D1_DATABASE_ID', ''), // REST driver credentials 'api' => 'https://api.cloudflare.com/client/v4', 'auth' => [ 'token' => env('CF_D1_API_TOKEN', ''), 'account_id' => env('CF_D1_ACCOUNT_ID', ''), ], // Worker driver credentials 'worker_url' => env('CF_D1_WORKER_URL', ''), 'worker_secret' => env('CF_D1_WORKER_SECRET', ''), // Performance tuning 'timeout' => env('CF_D1_TIMEOUT', 10), 'connect_timeout' => env('CF_D1_CONNECT_TIMEOUT', 5), 'retries' => env('CF_D1_RETRIES', 2), 'retry_delay' => env('CF_D1_RETRY_DELAY', 100), // Sessions / Read Replication (Worker driver only) 'session' => [ 'enabled' => env('CF_D1_SESSION_ENABLED', false), 'mode' => env('CF_D1_SESSION_MODE', 'first-unconstrained'), ], // Circuit breaker (optional) 'circuit_breaker' => [ 'enabled' => env('CF_D1_CB_ENABLED', false), 'threshold' => env('CF_D1_CB_THRESHOLD', 5), 'cooldown' => env('CF_D1_CB_COOLDOWN', 30), 'cache_driver' => env('CF_D1_CB_CACHE_DRIVER', 'file'), ], ], ],
Options Reference
| Option | Default | Description |
|---|---|---|
d1_driver |
rest |
Connection driver: rest (Cloudflare REST API) or worker (custom Worker) |
database |
— | Your Cloudflare D1 Database ID |
api |
https://api.cloudflare.com/client/v4 |
Cloudflare API base URL (REST driver only) |
auth.token |
— | Cloudflare API Token (REST driver only) |
auth.account_id |
— | Cloudflare Account ID (REST driver only) |
worker_url |
— | Your Worker URL (Worker driver only) |
worker_secret |
— | Shared secret for Worker auth (Worker driver only) |
timeout |
10 |
HTTP request timeout in seconds |
connect_timeout |
5 |
HTTP connection timeout in seconds |
retries |
2 |
Max retry attempts on 5xx/429 errors |
retry_delay |
100 |
Base delay between retries in milliseconds |
session.enabled |
false |
Enable D1 sessions for read replication (Worker driver only) |
session.mode |
first-unconstrained |
Session mode: first-primary or first-unconstrained |
circuit_breaker.enabled |
false |
Enable circuit breaker for fail-fast behavior |
circuit_breaker.threshold |
5 |
Consecutive failures before opening the circuit |
circuit_breaker.cooldown |
30 |
Seconds before allowing a probe request |
circuit_breaker.cache_driver |
file |
Laravel cache driver for circuit state (file, redis) |
Environment Variables
# Driver selection CF_D1_DRIVER=rest # 'rest' or 'worker' # REST driver CF_D1_API_TOKEN=your_api_token CF_D1_ACCOUNT_ID=your_account_id CF_D1_DATABASE_ID=your_database_id # Worker driver CF_D1_WORKER_URL=https://your-worker.workers.dev CF_D1_WORKER_SECRET=your_shared_secret # Performance tuning (optional) CF_D1_TIMEOUT=10 CF_D1_CONNECT_TIMEOUT=5 CF_D1_RETRIES=2 CF_D1_RETRY_DELAY=100 # Sessions / Read Replication (Worker driver only) CF_D1_SESSION_ENABLED=false CF_D1_SESSION_MODE=first-unconstrained # Circuit breaker (optional) CF_D1_CB_ENABLED=false CF_D1_CB_THRESHOLD=5 CF_D1_CB_COOLDOWN=30 CF_D1_CB_CACHE_DRIVER=file
⚠️ Limitations
-
No real transactions — D1 is stateless over HTTP and doesn't support
BEGIN/COMMIT/ROLLBACK. The driver makes these methods no-ops so Laravel internals (auth, sessions, middleware) work without crashing.-
DB::transaction(Closure)will execute the closure, but provides no atomicity — each query runs immediately and cannot be rolled back on failure. -
DB::transaction(Closure, attempts: N)retries the closure on any exception, but without real deadlock detection or isolation. -
For atomic multi-statement execution, use
batch()which leverages D1's native batch API:$connection->batch([ ['sql' => 'INSERT INTO orders ...', 'params' => [...]], ['sql' => 'UPDATE inventory ...', 'params' => [...]], ]);
-
-
REST API latency — Each query is an HTTP request (~100-500ms). Use the Worker driver for lower latency (~10-50ms).
-
Sessions / Read Replication — Worker driver only — The D1 Sessions API is only available via the Worker Binding. The REST API does not support sessions; all queries go to the primary database. This is a Cloudflare platform limitation.
-
Schema dump requires REST credentials —
d1:schema-dumpuses the D1 export REST API. Even Worker-only users must setCF_D1_API_TOKEN,CF_D1_ACCOUNT_ID, andCF_D1_DATABASE_ID. -
Export blocks queries — During export, D1 may be unavailable for queries (Cloudflare limitation for large databases).
-
Bulk insert batch limit — D1 batch supports max 100 statements.
bulkInsert()automatically chunks larger datasets but each chunk is a separate HTTP call. -
No streaming — Large result sets are loaded entirely into memory.
🌱 Testing
PHP Tests
vendor/bin/pest
Worker Tests (Vitest)
cd Worker npm ci npm test
Local Development with Worker
Start the built-in Worker to test against a local D1 instance:
cd Worker
npm ci
npm run start
🤝 Contributing
Please see CONTRIBUTING for details.
🔒 Security
If you discover any security related issues, please email contact@ntanduy.com instead of using the issue tracker.
🎉 Credits
统计信息
- 总下载量: 6.1k
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 24
- 点击次数: 2
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2024-08-22