tursodatabase/turso-driver-laravel
Laravel database driver for Turso (libSQL) that lets your app use Turso's SQLite-compatible embedded and remote databases through Laravel's native DB layer. Add a connection and keep using Eloquent, migrations, and the query builder with edge-friendly, replicated SQLite storage.
Install the package and its native dependency.
composer require tursodatabase/turso-driver-laravel
This package is a Laravel database driver that talks to libSQL/Turso using native bindings. Confirm your PHP runtime has the required libSQL extension/binding available (check the README's requirements section — this is the step most likely to fail silently, with a "could not find driver" or class-not-found error).
Add the connection to config/database.php.
'turso' => [
'driver' => 'turso',
'url' => env('TURSO_DATABASE_URL'), // libsql://... or file:...
'auth_token' => env('TURSO_AUTH_TOKEN'), // omit for local files
],
Put TURSO_DATABASE_URL and TURSO_AUTH_TOKEN in .env. Use a file: URL (e.g. file:database/local.sqlite) for local development and an libsql:// / https:// URL plus token for remote Turso databases.
Point Laravel at it.
DB_CONNECTION=turso
The service provider is auto-discovered; no manual registration should be needed. Verify with php artisan tinker → DB::connection()->getPdo().
Run your schema.
php artisan migrate --seed
The driver speaks the SQLite dialect, so standard Laravel migrations work — but only those that map cleanly to SQLite (see Gotchas).
First real use case: a new Laravel app that wants SQLite's simplicity with a managed, replicable backend, or an existing SQLite app migrating to Turso for edge/read-replica access. Start by running your existing migrations against a file: database locally, then flip TURSO_DATABASE_URL to the remote URL to validate the network path.
Where to look first: the package README's connection config example and its requirements/extension section, then your config/database.php. If anything fails, check php -m for the native extension before debugging Laravel config.
Use the database layer exactly as you would with SQLite. Eloquent, the query builder, factories, seeders, and DB::transaction() all work unchanged. The driver only changes the transport and grammar resolution; application code shouldn't branch on turso.
Connection-per-environment is the main config pattern.
'turso' => [
'driver' => 'turso',
'url' => env('TURSO_DATABASE_URL', 'file:database/local.sqlite'),
'auth_token' => env('TURSO_AUTH_TOKEN'),
],
Keep a single turso connection and swap the URL/token per environment. Avoid hardcoding remote URLs in code; treat the token as a secret and never commit it.
Batch your writes. Because remote Turso goes over the network, N+1 inserts are far more expensive than with a local MySQL/SQLite socket. Use DB::table(...)->insert($rows) with arrays, upsert(), and chunked Model::insert() instead of per-row save() loops. Wrap multi-statement writes in a single transaction to collapse round trips:
DB::transaction(function () use ($rows) {
Order::insert($rows); // one round trip
DB::table('outbox')->insert($events);
});
Read replicas / embedded replicas. For read-heavy workloads, the value of Turso is replication. If the driver exposes an embedded-replica URL option, point reads at the replica and writes at the primary (either via a second connection entry or by URL selection at boot). Keep the read path tolerant of slightly stale data.
Use it for the standard Laravel database-backed subsystems, cautiously. database cache, sessions, and queue drivers will function, but they generate constant small writes. On a remote Turso database this becomes latency-sensitive; for queues, prefer a dedicated driver or batch job processing. It's a great fit for low-traffic apps and prototypes, less so for high-throughput job queues.
Testing pattern: run the test suite against a file: database (or :memory: if supported by the driver) rather than the remote URL. Use RefreshDatabase with a local file and a dedicated phpunit.xml env override:
<env name="DB_CONNECTION" value="turso"/>
<env name="TURSO_DATABASE_URL" value="file:database/testing.sqlite"/>
Never point CI at a shared remote database with RefreshDatabase — it will migrate down your real data.
Migrations as ordinary Laravel migrations. Use Schema::create, $table->id(), string, text, json, timestamp, etc. Prefer simple column types; verify anything exotic against SQLite's type affinity before relying on it.
Native extension is the #1 failure point. "could not find driver" or a missing class almost always means the libSQL binding/extension isn't installed for the PHP binary you're running (CLI vs FPM differ). Check php -m and the package's requirements before touching Laravel config.
You inherit every SQLite limitation. No native ENUM, no FULLTEXT unless compiled in, limited ALTER TABLE (SQLite supports add-column and a narrow set of alters; modifying/dropping columns relies on Laravel's table-rebuild strategy and can fail on complex tables). Plan schema changes early; don't assume MySQL-style migrations will port.
Foreign keys may be off by default. SQLite requires PRAGMA foreign_keys = ON per connection. If you rely on FK cascades, verify the driver enables it; otherwise add it to your connection bootstrap (DB::statement('PRAGMA foreign_keys = ON') in a service provider or after-connect hook if the driver supports one).
Type affinity, not strict typing. SQLite will happily store a string in an integer column. Validate and cast in your models; don't depend on the DB to reject bad types. Use $casts on Eloquent models aggressively.
Concurrency model is single-writer. libSQL/SQLite serializes writes. High-concurrency write workloads (job queues, counters, high-traffic web writes) will contend and surface as lock/busy errors or latency. Use transactions for multi-statement writes, keep them short, and add retry logic around SQLSTATE busy errors.
Network latency changes your query patterns. Queries that were cheap locally become measurable round trips. Watch DB::listen() in a service provider or enable query logging to spot N+1s; the database queue and cache drivers are the usual offenders.
Migration safety on remote databases. migrate:fresh and RefreshDatabase are destructive. Gate them behind environment checks and never let CI hit a shared remote URL.
Token and URL handling. The auth token is a bearer secret — keep it in .env/secrets manager, not in config committed to git. Some Turso URLs require the libsql:// scheme while others accept https://; match the scheme the driver expects or you'll get opaque connection errors.
Debugging tips. Use DB::listen() and Log::debug for query timings; php artisan tinker to test the connection in isolation; test against a file: database to separate "driver broken" from "network/token broken." If a specific query fails, run it through sqlite3 locally — the SQL dialect mismatch will usually reveal itself immediately.
Extension points. If you need to tweak grammar or connection behavior, the driver typically registers a connection resolver and grammar via the service provider. You can subclass them and rebind in AppServiceProvider::register() — useful for custom PRAGMAs or retry behavior.
Maturity caution. This is a community SDK with modest adoption (~98 stars). Pin the version in composer.json, review release notes on upgrade, and have a fallback plan (standard SQLite file driver locally, or plain libSQL client for remote) if the driver lags Laravel releases. Test major Laravel upgrades against it before deploying.
How can I help you explore Laravel packages today?