Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Turso Client Http Laravel Package

darkterminal/turso-client-http

A lightweight HTTP client for Turso/libSQL built for PHP and Laravel. Query remote Turso databases over HTTP without native extensions, using a simple, expressive API for raw SQL, migrations, and everyday database operations.

View on GitHub
Deep Wiki
Context7

Getting Started

darkterminal/turso-client-http is a standalone, framework-agnostic PHP client for Turso (libSQL) that speaks the HTTP v2 pipeline API. It is not an Eloquent driver and not a PDO adapter — treat it as a thin HTTP gateway to a remote SQLite-compatible database.

Install and configure:

composer require darkterminal/turso-client-http

Add to .env (never commit the token):

TURSO_DATABASE_URL=libsql://your-db-yourorg.turso.io
TURSO_AUTH_TOKEN=eyJhbGciOi...

Where to look first: the README on GitHub. The public surface has changed between versions — the class is Darkterminal\TursoHttp\TursoClient (with a fluent DB-style builder in recent versions). Skim the README examples before writing a wrapper, so you match your installed version's method signatures.

First use case — smoke test from an Artisan command:

use Darkterminal\TursoHttp\TursoClient;

$client = new TursoClient(
    url:   config('services.turso.url'),
    token: config('services.turso.token'),
);

$result = $client->query('SELECT name FROM sqlite_master WHERE type = ?', ['table']);
dump($result->toArray());

The natural first integration is read-only reference data (feature flags, country lists, lookup tables) pulled from Turso at boot or cached in Redis — high-value, low-risk, and doesn't require you to fight the fact that this is not a Laravel database connection.

Implementation Patterns

Singleton via a service provider. One client instance per process; the underlying HTTP client connection is reused.

// config/services.php
'turso' => [
    'url'   => env('TURSO_DATABASE_URL'),
    'token' => env('TURSO_AUTH_TOKEN'),
],

// AppServiceProvider::register()
$this->app->singleton(TursoClient::class, fn () => new TursoClient(
    url:   config('services.turso.url'),
    token: config('services.turso.token'),
));

Repository layer. Do not let raw Turso calls leak into controllers. Wrap the client in a repository that returns domain objects, so you can later swap it for a local SQLite or read-cache implementation.

final class FeatureFlagRepository
{
    public function __construct(private TursoClient $client) {}

    public function enabled(string $key): bool
    {
        $row = $this->client->query(
            'SELECT enabled FROM feature_flags WHERE key = ? LIMIT 1',
            [$key]
        )->first();

        return (bool) ($row['enabled'] ?? false);
    }
}

Batch instead of looping. Because every call is an HTTP round trip, collapse N queries into one pipeline call. This is the single biggest performance lever:

// Bad: N round trips
foreach ($ids as $id) {
    $client->query('SELECT * FROM posts WHERE id = ?', [$id]);
}

// Good: one HTTP request
$client->batch(array_map(
    fn ($id) => ['SELECT * FROM posts WHERE id = ?', [$id]],
    $ids
));

Transactions for multi-write consistency. Wrap BEGIN/COMMIT statements in a single pipeline so they execute atomically on the edge:

$client->batch([
    ['BEGIN', []],
    ['INSERT INTO accounts (id, balance) VALUES (?, ?)', [$from, -100]],
    ['INSERT INTO accounts (id, balance) VALUES (?, ?)', [$to, 100]],
    ['COMMIT', []],
]);

Schema migrations. Turso is libSQL, so CREATE TABLE/ALTER TABLE works, but this package is not a migration runner. Recommended pattern: manage schema with Laravel's SQLite connection locally (DB::connection('sqlite')->statement(...)), then replay the same DDL through TursoClient::execute() in a dedicated Artisan command, or use the turso CLI in CI.

Cache the read path. Layer Laravel's cache in front of every read; remote HTTP reads are tens of milliseconds — fine for admin dashboards, too slow for hot paths without caching.

Cache::remember("flag:$key", now()->addMinutes(5), fn () => $this->repo->enabled($key));

Queued writes. Any non-trivial write should go through a job so a network hiccup on Turso doesn't fail the user's request.

Gotchas and Tips

  • Round-trip cost dominates. N+1 is catastrophic here compared to a local SQLite connection. Audit any loop that calls the client internally.
  • Typed value envelopes. The Turso pipeline API returns values as {"type": "text"|"integer"|"float"|"blob"|"null", "value": ...}. The package usually unwraps these, but verify — if you get ["type" => "integer", "value" => "42"] back, you're reading raw pipeline output. Normalize at the repository boundary.
  • HTTP 200 does not mean success. Turso can return HTTP 200 with per-statement error objects in the body. Check both the transport status and any per-result error key inside batch() responses.
  • Redirects on writes. Turso frequently 302s to the nearest replica. Guzzle does not follow redirects with the request body preserved by default in all configs — if writes silently vanish, configure allow_redirects explicitly or point at the primary URL for write traffic.
  • URL scheme matters. Use libsql:// (the client upgrades TLS); pasting the raw https://.../v2/pipeline endpoint from the dashboard can bypass the client's own path handling. Copy the URL from turso db show <name> --url.
  • Token scope and rotation. Use per-environment tokens with the narrowest scope; Turso tokens are JWTs with a exp. Rotate before expiry, and store per-environment in your secret manager, not .env in staging.
  • Batch size limits. Turso caps pipeline requests (~1 MB body / ~100 statements). Chunk large batches; a 413 or silent truncation usually means you exceeded this.
  • SQLite semantics leak through. No RETURNING in older libSQL builds, no native BOOLEAN (0/1 integers), no DATETIME type (TEXT in ISO-8601). Cast in your repository, not in views.
  • No schema introspection tooling. Blueprint, Schema, Eloquent relations — none of it works. If you need an ORM, look at the official libsql PHP extension or migrate to a Turso+Eloquent companion package; use this library for bounded read paths.
  • Debugging hook. Log the raw pipeline request/response (Log::debug('turso', ['body' => $payload])) behind a TURSO_DEBUG flag. It's the fastest way to distinguish auth failures, schema errors, and type mismatches.
  • Extension point — inject Guzzle. Most darkterminal clients accept an optional ClientInterface; pass a pre-configured Guzzle client to add tracing (webhook/otel middleware), retries with backoff, or request timeouts (connect_timeout: 2, timeout: 5).
  • Maintenance risk. 49 stars, single maintainer — pin the version in composer.json ("darkterminal/turso-client-http": "^x.y") and add a thin abstraction so a future swap to the official SDK is a one-file change.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log