erimeilis/laravel-cloudflare-d1
Adds Cloudflare D1 as a native Laravel database connection, letting your app run queries against D1's serverless SQLite at the edge. Configure credentials, then use Eloquent and the query builder as usual to build edge-friendly apps.
Architecture fit. This package converts Cloudflare D1 into a native Laravel connection driver, which means it slots into the framework's existing abstractions — config/database.php, DB::connection(), Eloquent models, Schema — rather than asking the team to learn a new data-access API. That is the strategically interesting part: the integration cost is low enough that the real question becomes whether D1 fits the workload, not whether we can afford to find out. The fit is narrow but real. D1 is a serverless SQLite database reached over Cloudflare's HTTP API with no PDO underneath, so it suits read-heavy, bounded, latency-sensitive surfaces (edge APIs, internal dashboards, scheduled aggregations, prototypes, small multi-tenant reads) and is a poor fit for transactional OLTP cores. The decisive architectural fact for planning is that every query is an HTTPS round trip and D1 bills on rows read/written — that reshapes both latency budgets and unit economics, and it should be modelled before any roadmap commitment.
Integration feasibility. High for a pilot, low for a migration. Installation is a composer require plus a connection array; a first-hour smoke test (connectivity, DDL, Eloquent read/write, bulk insert, transaction semantics) is a half-day of engineering. The package is MIT-licensed with a 1.x line, so forking is viable and there is no licensing blocker. Against that, the migration story is explicitly a diff generator, not a converter: SQLite's type affinity, ALTER limitations, absent native DECIMAL/DATETIME/ENUM/JSON types, collation and LIKE semantics, and unverified foreign-key enforcement all require hand-audit. Any estimate that assumes automated porting is wrong by a wide margin.
Technical risk. Ranked:
Grammar parent class gained a constructor between Laravel 11 and 12. That is a warning sign: the driver is coupled to framework internals that move between majors, and failures will surface silently after an unrelated composer update. Pin the package and the Laravel minor, and treat every upgrade as requiring a real-D1 integration run.DB::transaction() maps to D1 batch atomicity, not a PDO transaction — no row locks, no SELECT ... FOR UPDATE, no long-lived session, and ambiguous commit state on partial batch failure. Every existing transaction call site needs a per-site decision.Key questions. Is this a platform-direction bet on Cloudflare, or a single-service experiment — and who owns that call? What is our measured p95 latency budget and query-per-request ceiling for the candidate service? Who owns pinning, vendoring/forking, and upstream tracking, and is that person on call for it? What is the documented cutover-back-to-MySQL procedure? Does anything in our dependency tree, test harness, or observability stack assume a PDO handle? What is our per-row cost model at expected read/write volume, versus current spend? What is the rollback trigger in numbers?
Stack fit. Laravel 10–12 on PHP 8.1+ with Composer. The only hard requirement is a Cloudflare account, a D1 database, and a D1-scoped API token — plus the acceptance that PDO-dependent tooling will break. Confirm the exact connection-array key spelling against the README before writing config; the array keys are the contract, not the env var names, and a misconfiguration fails quietly.
Migration path. Run D1 alongside MySQL rather than flipping DB_CONNECTION. Scope the connection per model (protected $connection = 'd1', or a D1Model base class) so rollback is a one-line revert instead of a coordinated env change. Keep D1 migrations in a separate path (database/migrations/d1) so the two schemas evolve independently and can be diffed. For greenfield work, skip the tooling entirely and write the schema by hand. For existing schemas, treat tooling output as a candidate to be audited: types, defaults, indexes, foreign keys, JSON columns, enums, generated columns, charset/collation. Write migrations assuming SQLite's ALTER limits — create-new → copy → drop → rename — and make them idempotent, because you will re-run them during a failed deploy.
Compatibility. Two compatibility axes. Framework: the Laravel 11 vs 12 grammar divergence means the package version and Laravel minor move together; budget a real-D1 integration suite in CI from day one and gate framework upgrades on it. Dialect: declare $casts on every model rather than trusting column types; store dates as text, booleans as integers, money as integer minor units; test LIKE case-sensitivity explicitly; plan an external index or indexed LIKE since there is no full-text search. In-memory SQLite is not a valid stand-in — pragmas, limits, and failure behaviour differ.
Sequencing. Five gates, each with an exit criterion:
Build the extension points early because they pay for themselves immediately: a decorated HTTP client for retry with jittered backoff on 429/5xx, statement timing, and round-trip counting; a middleware warning above a query threshold; a D1Model base class; and an exception handler that distinguishes retryable throttling, transient 5xx, and non-retryable dialect/statement bugs.
Maintenance. Adoption effectively means owning the driver. With one maintainer, 14 stars, zero dependents, and a 1.x release line, plan to vendor or keep a private fork from day one and track upstream commits, rather than depending on upstream responsiveness. Assign a named owner for pinning, fork maintenance, and framework-upgrade validation. Keep the grammar and connection classes on the short list of files the team can patch in a hurry — the MIT license makes that viable, and the Laravel 11/12 episode suggests it may be necessary.
Support. Support burden shifts from DBA work to application-layer work. There is no console to inspect, so runbook entries must cover wrangler d1 execute and the Cloudflare dashboard for out-of-band inspection, plus rendered-SQL-plus-bindings logging in the app. On-call needs the three failure classes separated in the exception handler — otherwise everything collapses to "database error" and triage stalls. dump()/dd() cost a round trip each; discourage them in hot paths.
Scaling. Scaling characteristics differ from what the team is used to. Throughput is bounded by round trips and D1's query-duration and payload limits, not by connection pool size. Chunk bulk inserts and long update/delete statements against real row shapes and bound-parameter ceilings — the "20x faster bulk inserts" claim is about D1's batch endpoint reducing round trips, so treat it as a hypothesis to benchmark, not a planning input. Cap queue-worker concurrency and run a deliberate Octane test; no persistent connections means a connection storm is a realistic production failure. Read-side scaling benefits from Cloudflare's distribution; write-side does not, and deep OFFSET pagination should be replaced with keyset pagination. Cost scales with rows read/written, so Eloquent hygiene becomes budget hygiene: eager-load, select() only needed columns, cache reference data.
Failure modes. Four to design against. (a) Round-trip inflation — a 40-query page works in MySQL and is unusable here; instrument round trips per request, not just milliseconds. (b) Ambiguous writes — partial batch failure under batch-atomicity semantics means writes must be idempotent and retry-safe; multi-step workflows needing rollback of non-database side effects need an idempotency key and a status column, not a transaction. (c) Silent correctness drift — dialect mismatches returning wrong results, and unenforced foreign keys discovered months later; verify pragma behaviour rather than assuming it. (d) Upgrade-induced breakage — a composer update silently breaking the driver; the real-D1 integration suite is the control.
Ramp-up. Low for developers — the API surface is ordinary Eloquent and query builder, so the learning curve is about operational instincts rather than syntax. Budget roughly a week of team time to internalise the counter-intuitive parts: round trips as the primary metric, transaction semantics, type affinity and casts, and the absence of interactive debugging. The pilot team should produce three artefacts before expansion: a query-cost dashboard, a rollback runbook, and a dialect gotchas page. Treat the current evidence as sufficient for a time-boxed, bounded pilot and insufficient for a platform-wide commitment — the spike is cheap enough that the defensible move is to buy the answer rather than debate it.
How can I help you explore Laravel packages today?