Support matrix#
| Database | Package | Status |
|---|---|---|
| SQLite (pure Go, no cgo) | core, modernc.org/sqlite | Available |
| PostgreSQL | drivers/postgres (pgx) | Available |
| MySQL 8 / MariaDB 11 | drivers/mysql (go-sql-driver/mysql) | Available |
| Redis (cache, sessions, queues, rate limiting) | drivers/redis (go-redis) | Available |
| MongoDB | drivers/mongo (official driver) | Available |
Drivers are separate Go modules, so importing the core never downloads a heavy driver.
Connecting#
app.DB() opens the default connection on first use from DB_* variables, registers a db health check (/up?checks=1) and closes it on shutdown:
| Variable | Default |
|---|---|
DATABASE_URL | file:storage/database.sqlite |
DB_MAX_OPEN | 10 |
DB_MAX_IDLE | 5 |
db, err := app.DB() // *database.DB
if err != nil {
return err
}
r.Resource("posts", controllers.NewPostController(db))SQLite URLs: file:storage/app.db, sqlite:storage/app.db or :memory:. Other schemes need their driver, added with vukem.WithDatabaseDrivers(...).
Outside an app, database.Open(ctx, url, database.WithConfig(cfg)) returns the same *database.DB; database.FromSQL(conn, dialect) wraps an existing *sql.DB.
SQLite defaults#
Every SQLite connection gets WAL journaling, busy_timeout, foreign_keys=ON and BEGIN IMMEDIATE for write transactions. Writes and transactions go to a single-connection writer pool, reads to a read-only reader pool, so concurrent requests do not fail with SQLITE_BUSY.
Raw SQL#
*database.DB and *database.Tx implement database.Executor:
var n int64
err := db.QueryRowContext(ctx, "SELECT COUNT(*) FROM posts WHERE published = ?", true).Scan(&n)Always pass values as arguments; never concatenate input into SQL. db.Dialect() gives Quote and Placeholder for portable statements, and db.SQL() exposes the underlying *sql.DB.
Transactions#
err := db.Tx(ctx, func(tx *database.Tx) error {
if err := orm.Create(ctx, tx, &order); err != nil {
return err
}
return orm.Create(ctx, tx, &item)
}) // commit on nil, rollback on error or panic (the panic is re-raised)database.RunInTx(ctx, ex, fn) joins the caller's transaction when ex is already a *Tx, or starts one when it is a *DB, which keeps service functions composable.
Next#
- ORM: typed queries and models.
- Migrations: schema builder and
vukem migrate.
PostgreSQL#
go get github.com/AlexGalhardo/vukem/drivers/postgresvukem.New(append([]vukem.Option{
vukem.WithDatabaseDrivers(postgres.Driver()), // postgres:// and postgresql:// URLs
// ...
}, opts...)...)DATABASE_URL=postgres://app:secret@localhost:5432/app?sslmode=disableModels, queries, migrations, relations, the database cache store and SESSION_DRIVER=database work unchanged on SQLite, PostgreSQL and MySQL.
MySQL and MariaDB#
go get github.com/AlexGalhardo/vukem/drivers/mysqlvukem.New(append([]vukem.Option{
vukem.WithDatabaseDrivers(mysql.Driver()), // mysql:// and mariadb:// URLs
// ...
}, opts...)...)DATABASE_URL=mysql://app:secret@localhost:3306/appThe URL becomes a go-sql-driver DSN with parseTime, UTC times, utf8mb4 and a 10s dial timeout; query parameters override them. Hosts other than localhost require TLS with a verified certificate unless the URL sets tls= (for example tls=preferred). Default MySQL collations compare text case-insensitively, so Where and unique indexes on strings ignore case.
Conformance suite#
database/dbtest.Run(t, db) is the suite every dialect passes: migrations, schema changes, ORM round trips (JSON, nullable, booleans, times), unique violations as 409, foreign keys, soft deletes, cursor pagination, relations and transactions (including rollback on panic). drivers/postgres runs it against postgres:17 and drivers/mysql against mysql:8.4 and mariadb:11.8 with go test -tags=integration.
Transactions and SQLite#
Inside db.Tx, pass tx to every query. SQLite has a single writer connection, so a write through db while the transaction holds it waits forever. database.RunInTx(ctx, ex, fn) joins the caller's transaction when ex is already a *database.Tx.