How to Use Turso with Go: Complete Guide to the tursogo Database Driver

The tursogo driver implements the standard database/sql interface, allowing you to connect to Turso databases in Go by importing turso.tech/database/tursogo and using sql.Open("turso", dsn) for local files or turso.NewTursoSyncDb() for cloud-synced instances.

The tursogo package in the tursodatabase/turso repository provides native Go support for Turso's SQLite-compatible databases. It implements the standard database/sql driver interface, meaning any code that works with database/sql works with Turso without modification, while also offering advanced synchronization features for edge computing scenarios.

Driver Architecture and Core Components

The tursogo driver bridges Go's standard database abstraction with Turso's native C API, supporting both standalone local databases and synchronized edge replicas.

Driver Registration and Initialization

In bindings/go/driver_db.go lines 70-73, the driver registers itself with Go's database/sql package via sql.Register("turso", &tursoDbDriver{}). This registration happens automatically when you import the package.

The InitLibrary function in bindings/go/bindings.go lines 10-21 handles one-time loading of the platform-specific native library, ensuring thread-safe initialization exactly once per process.

Connection Lifecycle

When you call sql.Open("turso", dsn), the driver's Open method (implemented in driver_db.go lines 90-129) performs several critical operations:

  1. Parses the DSN using parseDSN (lines 219-255 in driver_db.go)
  2. Invokes InitLibrary to load native bindings
  3. Creates the underlying database via turso_database_new
  4. Establishes a connection via turso_database_connect
  5. Applies the busy-timeout if specified in the DSN

The tursoDbConnection struct (lines 131-200 in driver_db.go) implements driver.Conn, driver.ExecerContext, and driver.QueryerContext, translating standard SQL calls to Turso's low-level C API through wrappers like turso_statement_bind_positional_* and turso_statement_step.

Synced Database Architecture

For edge-sync scenarios, the driver provides TursoSyncDb in bindings/go/driver_sync.go. This high-level wrapper manages:

  • Push/pull replication with Turso Cloud
  • Checkpointing of the write-ahead log
  • Automatic IO processing via an extraIo hook

The tursoSyncConnector.Connect method (lines 99-106 in driver_sync.go) attaches the processOneIo callback, allowing the SQL executor to interleave network and file IO while stepping statements.

Basic Usage: Local SQLite Databases

For standalone local databases without cloud synchronization, use the standard database/sql workflow:

package main

import (
	"database/sql"
	"log"

	_ "turso.tech/database/tursogo" // registers the "turso" driver
)

func main() {
	// DSN: path to the SQLite file + optional query params (e.g. _busy_timeout=5000)
	db, err := sql.Open("turso", "example.db?_busy_timeout=5000")
	if err != nil {
		log.Fatalf("open: %v", err)
	}
	defer db.Close()

	// Create a table
	_, err = db.Exec(`
		CREATE TABLE IF NOT EXISTS items (
			id   INTEGER PRIMARY KEY AUTOINCREMENT,
			name TEXT NOT NULL
		)
	`)
	if err != nil {
		log.Fatalf("create: %v", err)
	}

	// Insert a row
	res, err := db.Exec(`INSERT INTO items (name) VALUES (?)`, "hello")
	if err != nil {
		log.Fatalf("insert: %v", err)
	}
	id, _ := res.LastInsertId()
	log.Printf("inserted row id=%d", id)

	// Query back
	var name string
	row := db.QueryRow(`SELECT name FROM items WHERE id = ?`, id)
	if err := row.Scan(&name); err != nil {
		log.Fatalf("query: %v", err)
	}
	log.Printf("retrieved name=%s", name)
}

The driver handles all statement preparation and execution through tursoDbStatement and tursoDbConnection (see driver_db.go lines 131-200), translating your Go SQL calls to native Turso operations.

Advanced Usage: Synced Databases with Turso Cloud

For applications requiring synchronization between local storage and Turso Cloud, use NewTursoSyncDb:

package main

import (
	"context"
	"database/sql"
	"log"
	"os"
	"os/signal"
	"syscall"
	"time"

	turso "turso.tech/database/tursogo"
)

func main() {
	// Required env vars – see the example in the repo (examples/go/sync/sync.go)
	remoteURL := os.Getenv("TURSO_DATABASE_URL")
	authToken := os.Getenv("TURSO_AUTH_TOKEN")
	if remoteURL == "" || authToken == "" {
		log.Fatal("TURSO_DATABASE_URL and TURSO_AUTH_TOKEN must be set")
	}

	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
	defer stop()

	// Build a synced DB wrapper
	syncDB, err := turso.NewTursoSyncDb(ctx, turso.TursoSyncDbConfig{
		Path:      "local.db",    // local file
		RemoteUrl: remoteURL,    // libsql://… or https://…
		AuthToken: authToken,
	})
	if err != nil {
		log.Fatalf("NewTursoSyncDb: %v", err)
	}
	defer syncDB.Checkpoint(ctx) // optional final checkpoint

	// Obtain a *sql.DB that uses the sync connector
	db, err := syncDB.Connect(ctx)
	if err != nil {
		log.Fatalf("Connect: %v", err)
	}
	defer db.Close()

	// Normal SQL usage …
	_, err = db.Exec(`CREATE TABLE IF NOT EXISTS events (id INTEGER PRIMARY KEY, msg TEXT, ts INTEGER)`)
	if err != nil {
		log.Fatalf("create table: %v", err)
	}
	_, err = db.Exec(`INSERT INTO events (msg, ts) VALUES (?, ?)`, "first", time.Now().Unix())
	if err != nil {
		log.Fatalf("insert: %v", err)
	}

	// Periodically push local changes and pull remote ones
	go func() {
		tick := time.NewTicker(60 * time.Second)
		for {
			select {
			case <-ctx.Done():
				return
			case <-tick.C:
				if err := syncDB.Push(ctx); err != nil {
					log.Printf("[push] error: %v", err)
				}
				changed, err := syncDB.Pull(ctx)
				if err != nil {
					log.Printf("[pull] error: %v", err)
				} else if changed {
					log.Println("[pull] applied remote changes")
				}
			}
		}
	}()

	<-ctx.Done()
	log.Println("shutting down")
}

The NewTursoSyncDb function (lines 14-44 in driver_sync.go) creates the underlying sync engine via turso_sync_database_new, parses the DSN using parseSyncDSN (lines 111-132), and sets up the IO processing queue. The Connect method returns a standard *sql.DB backed by tursoSyncConnector, which handles driveOpUntilDone and processIoQueue internally.

Configuration and DSN Options

DSN Parameters

The driver parses Data Source Names in driver_db.go lines 219-255 via parseDSN. Key parameters include:

  • _busy_timeout: Duration in milliseconds the database waits before returning a SQLITE_BUSY error
  • File path: The local SQLite database file location

For synced databases, parseSyncDSN (lines 111-132 in driver_sync.go) handles additional authentication parameters.

Using the Connector API

For explicit configuration, use NewConnector to create a TursoConnector (lines 44-74 in driver_db.go):

import (
	"context"
	"database/sql"

	_ "turso.tech/database/tursogo"
	"turso.tech/database/tursogo"
)

func main() {
	// Use the connector API to set a 10‑second busy timeout
	connector, _ := turso.NewConnector(
		"mydb.db?_busy_timeout=10000",
		turso.WithBusyTimeout(10000), // same value, overrides DSN if present
	)

	db := sql.OpenDB(connector) // regular *sql.DB
	defer db.Close()
	// … use db as usual
}

The connector stores the DSN and any ConnectorOptions, overriding busy-timeout values in the parsed config before opening the native connection (see driver_db.go lines 77-91).

Core Source Files Reference

Understanding the implementation helps debug complex scenarios:

  • bindings/go/driver_db.go: Core database/sql driver implementation, DSN parsing, connection and statement handling (lines 70-200)
  • bindings/go/driver_sync.go: High-level synced-database wrapper (TursoSyncDb), tursoSyncConnector implementation, push/pull/checkpoint logic, and IO processing (lines 14-132)
  • bindings/go/bindings.go: One-time loading of the platform-specific native library via InitLibrary (lines 10-21)
  • examples/go/sync/sync.go: Working reference implementation demonstrating sync operations

Summary

  • Import once: Use _ "turso.tech/database/tursogo" to register the "turso" driver name with database/sql
  • Local mode: Call sql.Open("turso", "file.db?_busy_timeout=5000") for standalone databases; the Open function in driver_db.go handles native library initialization and connection setup
  • Sync mode: Use turso.NewTursoSyncDb() for edge-computing scenarios requiring cloud synchronization; it manages push/pull operations and checkpointing via driver_sync.go
  • Standard interface: Both modes return *sql.DB compatible with standard database/sql patterns, including connection pooling, prepared statements, and transaction handling
  • IO interleaving: Synced connections use the extraIo hook (processOneIo) to perform network operations without blocking SQL execution

Frequently Asked Questions

What is the difference between using sql.Open and NewTursoSyncDb?

sql.Open("turso", dsn) creates a standard connection to a local SQLite database file managed by Turso's engine, suitable for single-node applications. NewTursoSyncDb creates a high-level wrapper (TursoSyncDb) that manages bidirectional synchronization between a local database file and Turso Cloud, handling replication via Push, Pull, and Checkpoint methods defined in driver_sync.go.

How does the driver handle network IO during SQL execution?

The synced driver implements an extraIo callback mechanism set in tursoSyncConnector.Connect (lines 99-106 in driver_sync.go). During statement stepping, the driver yields control to processOneIo, which processes network operations (push/pull) without blocking the main SQL execution thread, enabling asynchronous replication while maintaining query responsiveness.

Can I use standard database/sql features like connection pooling?

Yes. The tursogo driver implements the complete database/sql driver interface including driver.Conn, driver.Stmt, and driver.Tx. Connections returned via sql.Open or tursoSyncDb.Connect support standard connection pooling, prepared statements via PrepareContext (lines 131-200 in driver_db.go), and full transaction support via BeginTx.

How do I configure the busy timeout for concurrent access?

Set the _busy_timeout parameter in the DSN (e.g., "my.db?_busy_timeout=5000"), which is parsed by parseDSN in driver_db.go lines 219-255. Alternatively, use the WithBusyTimeout() option when creating a custom connector via NewConnector, which overrides the DSN value as implemented in lines 77-91 of driver_db.go.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →