A WebAssembly SQLite driver for Go that enables database/sql code to run in the browser with OPFS persistence.
- 🚀 Run SQLite databases entirely in the browser
- 💾 Persistent storage using OPFS (Origin Private File System)
- 🔄 Full transaction support (BEGIN/COMMIT/ROLLBACK)
- ⚡ Works with standard database/sql interface
- 📦 Embedded SQLite WASM assets - everything included with
go get - 🔍 VFS detection to know if using OPFS or in-memory storage
- 💼 Database dump/load functionality for backups and migrations
- 🏗️ Built-in Web Worker bridge for optimal performance
- 🌐 Cross-Origin Isolation support for SharedArrayBuffer
- Go 1.19+ with WASM support
- Modern browser with OPFS support (Chrome 102+, Firefox 111+, Safari 15.2+)
- HTTPS or localhost for OPFS access
go get github.com/sputn1ck/go-sqlite3-wasmAll SQLite WASM assets are embedded in the module - no additional downloads needed!
# Setup and build everything
make setup
make build
# Run the demo
make serveVisit http://localhost:8081 to see the demo in action.
import (
"database/sql"
_ "github.com/sputn1ck/go-sqlite3-wasm"
)
func main() {
// Open database with OPFS persistence
db, err := sql.Open("wasmsqlite", "file=/myapp.db?vfs=opfs-sahpool")
if err != nil {
panic(err)
}
defer db.Close()
// Use with database/sql as normal
queries := database.New(db)
// ... your queries here
}go-sqlite3-wasm/
├── Makefile # Build automation
├── go.mod & go.sum # Go module files
├── *.go # Driver source files
├── bridge/ # JavaScript bridge
│ └── sqlite-bridge.js # Handcrafted bridge file
├── assets/ # SQLite WASM files (fetched)
│ ├── sqlite3.wasm
│ ├── sqlite3.js
│ ├── sqlite3-worker1.js
│ ├── sqlite3-worker1-promiser.js
│ └── sqlite3-opfs-async-proxy.js
├── scripts/ # Build scripts
│ └── fetch-sqlite-wasm.sh # Downloads SQLite WASM
└── example/ # Demo application
├── main.go # Demo Go code
├── index.html # Demo UI
├── server.js # Dev server with CORS headers
└── generated/ # SQLC generated code
SQLite WASM assets (v3.50.4) are embedded in the module. You have several options for using them:
import "github.com/sputn1ck/go-sqlite3-wasm"
// Extract all assets to a directory
err := wasmsqlite.ExtractAssets("./static/wasm")
if err != nil {
log.Fatal(err)
}
// Now serve ./static/wasm with your web serverimport "github.com/sputn1ck/go-sqlite3-wasm"
// Create an asset handler with proper CORS headers
handler := wasmsqlite.AssetHandler()
// Serve on /wasm/ path
http.Handle("/wasm/", http.StripPrefix("/wasm", handler))
// Assets will be available at:
// /wasm/assets/sqlite3.wasm
// /wasm/assets/sqlite3.js
// /wasm/assets/sqlite3-worker1.js
// /wasm/assets/sqlite3-worker1-promiser.js
// /wasm/assets/sqlite3-opfs-async-proxy.js
// /wasm/bridge/sqlite-bridge.jsimport "github.com/sputn1ck/go-sqlite3-wasm"
// Get specific assets
wasmBytes, _ := wasmsqlite.GetSQLiteWASM()
jsCode, _ := wasmsqlite.GetSQLiteJS()
bridgeCode, _ := wasmsqlite.GetBridgeJS()
// List all available assets
assets, _ := wasmsqlite.ListAssets()
for _, asset := range assets {
fmt.Println(asset)
}# Build your Go WASM binary
GOOS=js GOARCH=wasm go build -o web/main.wasm ./cmd/app
# Copy Go's WASM support file
cp "$(go env GOROOT)/lib/wasm/wasm_exec.js" ./web/For OPFS and SharedArrayBuffer support, serve with these headers:
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
file- Database file path (default:/app.db)vfs- Virtual file system (default:opfs-sahpool)opfs-sahpool- Persistent storage using OPFS with SharedArrayBuffer poolopfs- Standard OPFS storage:memory:- In-memory database (no persistence)
busy_timeout- Busy timeout in milliseconds (default: 5000)mode- Access mode (ro,rw,rwc,memory)cache- Cache mode (shared,private)
Example with options:
db, err := sql.Open("wasmsqlite", "file=/data.db?vfs=opfs-sahpool&busy_timeout=10000&mode=rwc")Export and import entire databases as SQL:
import wasmsqlite "github.com/sputn1ck/go-sqlite3-wasm"
// Export database
dump, err := wasmsqlite.DumpDatabase(db)
if err != nil {
// handle error
}
// Save dump to localStorage, send to server, etc.
// Import database
err = wasmsqlite.LoadDatabase(db, dump)
if err != nil {
// handle error
}Check if database is using persistent storage:
conn, _ := db.Conn(context.Background())
defer conn.Close()
var vfsType wasmsqlite.VFSType
conn.Raw(func(driverConn interface{}) error {
c := driverConn.(*wasmsqlite.Conn)
vfsType = c.GetVFSType()
return nil
})
switch vfsType {
case wasmsqlite.VFSTypeOPFS:
// Using persistent OPFS storage
case wasmsqlite.VFSTypeMemory:
// Using in-memory storage
}| Browser | Minimum Version | OPFS Support |
|---|---|---|
| Chrome | 102+ | ✅ Full |
| Edge | 102+ | ✅ Full |
| Firefox | 111+ | ✅ Full |
| Safari | 15.2+ | ✅ Full |
go-sqlite3-wasm includes built-in support for golang-migrate, allowing you to manage database schema migrations in your WASM applications.
import (
"embed"
"database/sql"
"github.com/golang-migrate/migrate/v4"
"github.com/golang-migrate/migrate/v4/source/iofs"
wasmsqlite "github.com/sputn1ck/go-sqlite3-wasm"
)
//go:embed migrations/*.sql
var migrationFS embed.FS
func runMigrations(db *sql.DB) error {
// Create source from embedded filesystem
sourceDriver, err := iofs.New(migrationFS, "migrations")
if err != nil {
return err
}
// Create the WASM SQLite migrate driver
dbDriver, err := wasmsqlite.NewMigrateDriver(db)
if err != nil {
return err
}
// Create migrate instance
m, err := migrate.NewWithInstance("iofs", sourceDriver, "wasmsqlite", dbDriver)
if err != nil {
return err
}
// Run migrations to latest version
return m.Up()
}Migration files follow the standard golang-migrate naming pattern:
{version}_{description}.up.sql- Apply migration{version}_{description}.down.sql- Rollback migration
Example:
migrations/
├── 001_initial_schema.up.sql
├── 001_initial_schema.down.sql
├── 002_add_users_table.up.sql
└── 002_add_users_table.down.sql
The MigrateDriver implements the database.Driver interface from golang-migrate:
// Create a new migrate driver
driver, err := wasmsqlite.NewMigrateDriver(db)
// Get current migration version
version, dirty, err := driver.Version()
// Set migration version
err = driver.SetVersion(version, dirty)
// Run a migration
err = driver.Run(migrationReader)
// Drop all tables
err = driver.Drop()- Automatic version tracking - Migrations are tracked in a
schema_migrationstable - Transaction support - Each migration runs in a transaction for atomicity
- Dirty state handling - Detects and handles failed migrations
- SQL statement splitting - Correctly handles multi-statement migrations
- Embedded migrations - Use
embed.FSto bundle migrations in your WASM binary
If you want to modify the embedded assets:
# Fetch latest SQLite WASM
make fetch-assets
# Build everything
make build
# The assets in ./assets/ and ./bridge/ will be embeddedmake test# Build and serve with auto-reload
make devmake help # Show all available commands
make setup # Initial setup (fetch SQLite WASM for development)
make fetch-assets # Download SQLite WASM from official source
make build # Build everything
make build-wasm # Build Go WASM only
make serve # Run demo server
make test # Run tests
make clean # Clean build artifactsThe example/enable-threads.js file is a service worker that enables SharedArrayBuffer support in browsers. This is required for OPFS (persistent storage) to work properly.
Modern browsers require specific Cross-Origin headers for SharedArrayBuffer:
Cross-Origin-Embedder-Policy: require-corp(orcredentialless)Cross-Origin-Opener-Policy: same-origin
These headers enable the "cross-origin isolated" state required for:
- SharedArrayBuffer - Needed for SQLite's OPFS VFS
- High-resolution timers - Better performance measurements
- Memory measurement - Accurate memory usage reporting
The service worker intercepts all requests and adds the required headers to responses. This allows OPFS to work even on development servers that don't set these headers.
<!-- Add this to your HTML before loading WASM -->
<script src="enable-threads.js"></script>If you control your server, you can set these headers directly instead of using the service worker:
// Go example
w.Header().Set("Cross-Origin-Embedder-Policy", "require-corp")
w.Header().Set("Cross-Origin-Opener-Policy", "same-origin")Note: Without these headers or the service worker, SQLite will fall back to in-memory storage (no persistence).
┌─────────────────────────────────────────┐
│ Go Application (WASM) │
│ ┌───────────────────────────────────┐ │
│ │ SQLC Generated Code │ │
│ └───────────────────────────────────┘ │
│ ┌───────────────────────────────────┐ │
│ │ go-sqlite3-wasm Driver │ │
│ └───────────────────────────────────┘ │
└─────────────────────────────────────────┘
↕
┌─────────────────────────────────────────┐
│ JavaScript Bridge (sqlite-bridge.js) │
└─────────────────────────────────────────┘
↕
┌─────────────────────────────────────────┐
│ SQLite Web Worker (Worker Thread) │
│ ┌───────────────────────────────────┐ │
│ │ sqlite3-worker1-promiser.js │ │
│ └───────────────────────────────────┘ │
│ ┌───────────────────────────────────┐ │
│ │ SQLite WASM (sqlite3.wasm) │ │
│ └───────────────────────────────────┘ │
└─────────────────────────────────────────┘
↕
┌─────────────────────────────────────────┐
│ OPFS Storage Layer │
│ (Persistent File System) │
└─────────────────────────────────────────┘
- SQLite extensions cannot be loaded dynamically
- Performance is slower than native SQLite (but optimized with Web Workers)
- OPFS storage is origin-scoped (per domain)
- Requires secure context (HTTPS/localhost)
- Cross-origin restrictions apply
Contributions are welcome! Please feel free to submit a Pull Request.
MIT
- SQLite for the amazing database
- @sqlite.org/sqlite-wasm for the WebAssembly build
- database/sql for the standard interface