libsqlq is a simple, generic, durable queue implementation in Go that uses libsql (the open-source fork of SQLite) as its storage backend. It works both with local .db files and remote Turso databases, giving you a reliable, at-least-once message queue with minimal dependencies.
Perfect for background job processing, task queues, or any scenario where you need persistence without pulling in Redis, RabbitMQ, or other heavy infrastructure.
- Generic –
Queue[T any]works with any JSON-serializable type. - Two deployment modes:
- Local file-based SQLite (
.db/<name>.db) - Remote Turso (cloud-hosted libsql)
- Local file-based SQLite (
- Built-in retry logic with backoff (configurable)
- Dead-letter behavior – jobs exceeding
maxRetriesstay in the DB forever (manual cleanup possible) - At-least-once delivery via claim-and-ack pattern
- Single-writer safe – uses transactions + row locks for concurrency
go get github.com/shaneikennedy/libsqlqRequires Go 1.21+ (for generics)
Make sure you have the libsql driver:
go get github.com/tursodatabase/go-libsqlCheckout examples/main.go for a LocalQueue implementation.
Set these environment variables:
export TURSO_URL="libsql://your-db.turso.io"
export TURSO_AUTH_TOKEN="your-auth-token"
# optional:
export TURSO_REMOTE_ENCRYPTION_KEY="your-key"Then:
q, err := libsqlq.NewTursoQueue[MyJob]()
if err != nil {
log.Fatal(err)
}Everything else works exactly the same.
// Local file-based
q, err := NewLocalQueue[MyPayload]("queue_name")
// Remote Turso
q, err := NewTursoQueue[MyPayload]()q = q.WithRetryBackoff(15 * time.Second)
q = q.WithMaxRetires(10)err := q.Insert(MyPayload{...})event, err := q.Next() // returns (*Event[T], error)
// event == nil → queue is emptytype Event[T any] struct {
Id int
Content *T // pointer to deserialized payload
}q.Ack(event.Id) // remove permanently
q.Nack(event.Id) // retry later after backoffsize, _ := q.Size() // only counts retry-eligible jobs- Background job processing
- Rate-limited API retries
- Webhook delivery queues
- Simple task scheduling
- Microservices that need durable messaging without extra infra
- Not designed for millions of QPS – libsql is SQLite under the hood.
- At-least-once only (duplicates possible if worker crashes after
Next()but beforeAck()). - No built-in scheduling (delayed jobs) – use
Nack+ long backoff as workaround. - Payload must be JSON-serializable.
libsqlq – Because sometimes all you need is a reliable SQLite queue. No brokers. No drama. Just go run./