Skip to content

Database: Getting Started ​

Introduction ​

github.com/vuka-lang/orm/v2 is the ORM for Go and Vuka. A model is a plain struct, its manager a package-level value, and its queries lazy, immutable QuerySets. Connections are plain database/sql.

Each field's column — name, type, default, checks — is declared in one attribute beside it, and custom value types and attributes plug in the same way. On top of that you get relations and prefetching, aggregates, Case/When, subqueries, text and vector search, raw SQL, several databases with replicas and routing, mirrored writes, and Go migration files written by makemigrations.

Checked Fields and Queries ​

In plain Go, an ORM names fields as strings — Where("author_name = ?", x), Order("-views") — that the compiler never checks, and a query for one model takes conditions written for another.

Vuka declares models with field attributes and writes queries with field references. In Go, the same attributes go in a Schema method and the references are generated. Here is a model declared that way:

go
type Post struct {
	orm.Model[Post]
	ID        int64
	Title     string
	Slug      string
	Status    string
	Author    orm.FK[User]
	Tags      orm.M2M[Tag]
	Published vuka.Option[time.Time]
	Views     int
	Created   time.Time
}

func (p *Post) Schema(s *orm.Schema) {
	s.Model(orm.Meta{Table: "posts", Ordering: []string{"-published"}})
	s.Field(&p.ID, orm.PK{Column: "post_id"})
	s.Field(&p.Title, orm.Char{Max: 200, Trim: true})
	s.Field(&p.Slug, orm.Slug{Unique: true})
	s.Field(&p.Status, orm.Char{Max: 20, Default: "draft", Choices: []string{"draft", "published"}})
	s.Field(&p.Author, orm.Rel{OnDelete: orm.Cascade, Related: "posts"})
	s.Field(&p.Published, orm.Datetime{Index: true})
	s.Field(&p.Views, orm.Int{Default: 0, Min: 0})
	s.Field(&p.Created, orm.Datetime{DefaultNow: true})
}

You query it through the generated fields:

go
f := PostFields // typed fields, written by go generate
posts, err := orm.Objects[Post]().Filter(f.Title.Contains("Go"), f.Author.Name.Eq("ada")).
	OrderBy(f.Views.Desc()).All(ctx) // []Post
if err != nil {
	return err
}

p, err := orm.Objects[Post]().Get(ctx, f.Slug.Eq(slug))
if errors.Is(err, orm.ErrNotFound) { // there is none
	return web.NotFound
}

A model's Schema names each field by its address, so a rename is checked by the compiler. PostFields, written by //go:generate go run github.com/vuka-lang/orm/v2/cmd/ormgen, gives typed lookups. orm.Objects[Post]() is the model's manager; without the generated fields, every query takes orm.Q{"title__contains": "Go"} strings.

Terminals return (T, error). Everything is plain Go underneath — the attributes are the model's Schema method — so Go and Vuka packages share models and databases.

NOTE

A value outside a field's Choices, bounds or length is refused on every write with an orm.Invalid naming the field, before the database sees it.

Installation ​

Add the module to your project:

sh
go get github.com/vuka-lang/orm/v2

The ORM needs Go 1.27, because it uses generic methods: q.Values[R](…).

Choosing a Driver ​

Drivers are opt-in blank imports, database/sql style; the orm package links none. Import the one for your database:

ImportDriverDialect
github.com/vuka-lang/orm/v2/postgrespgxpostgres
github.com/vuka-lang/orm/v2/mysqlgo-sql-driver/mysqlmysql
github.com/vuka-lang/orm/v2/sqlitepure-Go glebarez/go-sqlitesqlite

Your First Program ​

This complete program connects to SQLite, creates a table, saves a user and queries it back:

go
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/vuka-lang/orm/v2"
	_ "github.com/vuka-lang/orm/v2/sqlite"
)

//go:generate go run github.com/vuka-lang/orm/v2/cmd/ormgen

type User struct {
	orm.Model[User]
	ID    int64
	Name  string
	Email string
}

func (u *User) Schema(s *orm.Schema) {
	s.Field(&u.Name, orm.Char{Max: 100, Unique: true})
	s.Field(&u.Email, orm.Char{Max: 200})
}

func run(ctx context.Context) error {
	db, err := orm.ConnectURL("sqlite:app.db")
	if err != nil {
		return err
	}
	orm.AddDatabase("main", db, orm.AsDefault())
	if err := orm.CreateTables(ctx, orm.Objects[User]()); err != nil { // a real program runs migrations
		return err
	}

	ada := User{Name: "ada", Email: "ada@example.com"}
	if err := ada.Save(ctx); err != nil {
		return err
	}

	users, err := orm.Objects[User]().Filter(UserFields.Name.StartsWith("a")).OrderBy(UserFields.Name.Asc()).All(ctx)
	if err != nil {
		return err
	}
	fmt.Println(len(users), "users")
	return nil
}

func main() {
	if err := run(context.Background()); err != nil {
		log.Fatal(err)
	}
}

orm.ConnectURL picks the dialect from the URL — postgres://…, mysql://user:pass@tcp(host)/db, sqlite:file.db. orm.Connect(dialect, dsn) and orm.Open(sqlDB, dialect) take it apart.

A program with several databases, replicas and a supervised pool declares them instead: see Databases.

Where to Go Next ​

The rest of the database guide covers each part in turn:

Modelsorm.Model[T], Schema, relations, Option fields
Fieldscolumn attributes (PK, Char, Int, Datetime, …), defaults, write checks, custom value types and attributes
Queriespredicates from field references, Get/First, writes, relations, prefetching
Expressions & Functionsorm/fn, Case/When, subqueries, custom functions and lookups
Searchtext, trigram, vector and hybrid search on each database
Transactionsorm.Transaction, retries, OnCommit
Migrationsthe orm command and Go migration files
Databasesdialects, several databases, routing, raw SQL, the connection pool
Observabilityobservers, N+1 warnings, slow queries, pool statistics
Errorsthe error kinds
Database Testingormtest

The repository's examples/blog is the Vuka layer at work.

Released under the MIT License. Vuka builds on Go and its tools, which are made by the Go Authors. Vuka is not affiliated with Google or the Go project. Go is a trademark of Google LLC.