Skip to content
One static binary · any language · up to HTTP/3

SQLite, served.

quicSQL turns a SQLite database — a plain file, in-memory, or an encrypted vault — into an authenticated network service any language can query: a dead-simple JSON API, and the libSQL protocol your existing SDKs already speak.

quicSQL

Your language already has a client

quicSQL serves the libSQL Hrana protocol natively — the official SDKs for TypeScript, Python, PHP, Ruby, Rust, Swift, and Elixir connect by URL alone, interactive transactions included. Verified in CI against the real SDKs.

curl is a client

The native JSON API is one endpoint: POST /db/query with {sql, args} or an all-or-nothing statements batch. If it can send an HTTP request, it can query quicSQL — no SDK required.

Serves what nothing else can

An encrypted + compressed vfs/vault container is only safe under a single owner. quicSQL is that owner: it opens the vault once and multiplexes every client through it — encryption at rest, shared over the network.

Every transport, one handler

HTTP/1.1, cleartext h2c, HTTP/2 over TLS, HTTP/3 over QUIC, and Unix domain sockets — the identical handler on every wire, so semantics never change with the transport.

Batteries included

Six authentication methods — bearer, HTTP-basic, mTLS, ed25519 challenge/response, Unix peer credentials, or none — per-database capability grants, a /_admin control plane, audit log, OpenMetrics, rate limits, and query timeouts.

Read-only means read-only

A read-only principal runs on a connection in PRAGMA query_only plus a write-denying authorizer — enforced by the engine, not by parsing SQL. You cannot talk your way past it.

One static binary

Pure Go, CGo-free, built on gosqlite. Cross-compiles with plain GOOS=… go build, ships as a distroless container or a single file you scp anywhere. No C toolchain, no runtime dependencies, ever.

ORMs ride along, unchanged

Drizzle and Prisma (TypeScript), SQLAlchemy (Python), Ecto (Elixir), and LiteORM (Go) all run over the wire as-is — their libSQL drivers already speak quicSQL’s protocol.

First-class Go, embeddable

A native client for every transport and auth method, a database/sql driver where only the DSN changes, and serverd.Run(cfg, log) to embed the whole server in-process.

Up and running in a minute

One YAML file: listeners per transport, databases per line. With no principals configured the server runs in open mode — bind to loopback, then add auth when you need it.

# quicsql.yaml
server:
  data_dir: ./data
secrets:
  - {name: keys, type: file, dir: ./data/keys}
tls:
  dev: {mode: self_signed, hosts: [localhost, 127.0.0.1]}
listeners:
  - {name: h1,   transport: h1, address: 127.0.0.1:7775}
  - {name: h3,   transport: h3, address: 127.0.0.1:7778, tls: dev}
databases:
  - {name: users, backend: file, path: users.db, mode: rwc,
     pragmas_preset: recommended}
  - name: orders                  # encrypted + compressed at rest
    backend: vault
    path: orders.vault
    vault: {compression: default, cipher: adiantum, key: keys:orders}

Run it, and the native JSON endpoint is thin enough for curl:

$ quicsql --config quicsql.yaml

$ curl -s http://127.0.0.1:7775/users/query \
    -d '{"sql":"SELECT name FROM users WHERE id = ?","args":[7]}'

The same handler is listening on HTTP/3 — and on h2c, HTTP/2, and Unix sockets if you add those listeners. Endpoints per database: /query, /v2|v3/pipeline, /v3/cursor, /export, /changeset/*, /blob/*; server-wide: /_health, /_metrics, /_admin/*.

Talk to it from anything

The official libSQL SDK connects by URL alone — transactions, batches, and all. (Keep the trailing slash: quicSQL namespaces databases by path.)

import { createClient } from "@libsql/client";

const db = createClient({
  url: "http://127.0.0.1:7775/users/", // trailing slash!
  authToken: "your-token",
});

const rs = await db.execute("SELECT name FROM users WHERE id = ?", [7]);

const tx = await db.transaction("write");
await tx.execute({ sql: "UPDATE accounts SET balance = balance - ? WHERE id = ?", args: [100, 1] });
await tx.execute({ sql: "UPDATE accounts SET balance = balance + ? WHERE id = ?", args: [100, 2] });
await tx.commit();

Drizzle and Prisma work too →

How it compares

CapabilityquicSQLlibSQL sqldrqlitews4sql
Works with the existing libSQL SDKs (TS, Python, PHP, Ruby, Rust, …)
Simple JSON-over-HTTP API
Pure Go, CGo-free, one static binary✗ (Rust)✗ (CGo driver)✗ (CGo driver)
HTTP/3 (QUIC) transport
Unix socket + peer-credential auth
Built-in auth (mTLS, bearer, ed25519, password)partialpartial
Per-database authz, read-only enforced in-enginepartial
Encrypted + compressed database, served live✓ (vfs/vault)encryption only
Runtime control plane + audit logpartial
Distributed replication / Raft consensus✓ (Turso)

The trade-off is deliberate. quicSQL is a single-owner multiplexer, not a replicated cluster. If you need Raft consensus and multi-node failover, rqlite and Turso are built for that. quicSQL is built to make one powerful SQLite database — especially an encrypted vault — safely and richly network-accessible, from every language you run.

Your ORM already works here

Because quicSQL speaks the protocols your data layers already use, declarative stacks run over the wire unchanged:

  • Drizzle and Prisma in TypeScript
  • SQLAlchemy in Python
  • Ecto in Elixir
  • LiteORM in Go — whose typed vector, full-text, and hybrid search execute server-side against the same database
import (
    "liteorm.org/dialect/sqlite"
    "liteorm.org/orm"
    _ "quicsql.net/client/sqldriver" // registers quicsql://
)

// A local file in dev, a quicSQL server in prod —
// only the DSN changes:
db, _ := sqlite.Open("quicsql://127.0.0.1:7777/app?transport=h2&token=…")
defer db.Close()

orm.AutoMigrate[User](ctx, db) // runs the DDL on the server