Skip to content

The orm front-end

The orm package is LiteORM’s declarative front-end: you describe your data as plain structs with tags, and a typed repository handles CRUD, associations, lifecycle hooks, soft deletes, and schema migration. It’s convention-driven but never magical — there’s no lazy loading and no silent pluralization, and an ambiguous mapping is a hard error rather than a guess.

It shares one core with the explicit query builder, so a value you fetch through one front-end feeds the other on the same transaction. Reach for orm when you want models, relations, and migrations; reach for query when you want to assemble SQL by hand — see Query or ORM? for the trade-off. For exhaustive API detail, see the reference at pkg.go.dev/liteorm.org/orm.

A model is an exported struct. Annotate fields with orm:"..." tags where you need more than the defaults; gorm:"..." tags are read too, so models carried over from gorm work as-is. With no tag, the column is the snake_case of the field, and an int64 field named ID is treated as an auto-increment primary key by convention.

import (
"database/sql"
"time"
"liteorm.org/orm"
)
type Post struct {
ID int64
AuthorID int64 `orm:"author_id"`
Title string
Slug string `orm:"slug,unique"`
Views int64
CreatedAt time.Time `orm:"created_at,autocreatetime"`
UpdatedAt time.Time `orm:"updated_at,autoupdatetime"`
DeletedAt sql.NullTime `orm:"deleted_at,soft_delete"`
}
func (Post) TableName() string { return "posts" }

The full tag grammar, embeds, and the TableName() rules are in Declaring models.

orm.AutoMigrate[T] brings the table for T into being and keeps it in sync, additively — it creates a missing table with its unique indexes and any junction tables, and adds a column for anything the model gained, but never drops columns or alters types. AutoMigrateAll migrates a whole set in one call (list a referenced table before the table that points at it):

if err := orm.AutoMigrateAll(ctx, db, Author{}, Post{}, Comment{}); err != nil {
return err
}

For reviewable or destructive schema changes see migrations; for the index and constraint tags see indexes & constraints.

orm.NewRepo[T](sess) is the typed repository; it runs against a *liteorm.DB or a transaction.

posts := orm.NewRepo[Post](db)
p := Post{AuthorID: ada.ID, Title: "Generics in Go"}
err := posts.Create(ctx, &p) // stamps timestamps, reads the generated key back into p
got, err := posts.Get(ctx, p.ID) // by primary key → liteorm.ErrNoRows when absent
p.Views = 42
err = posts.Update(ctx, &p)
err = posts.Delete(ctx, &p) // soft delete if the model has a soft_delete column, else hard

The full verb set — Save, Upsert, FirstOrCreate/FirstOrInit, Updates, Select/Omit, batches, and filtered reads — is in CRUD with the Repo.

  • Declaring models — structs, the tag grammar, embeds, TableName().
  • CRUD with the Repo — every read and write verb.
  • Scopes — reusable, composable read filters.
  • Associations — has-many, has-one, belongs-to, many-to-many, eager loading.
  • Hooks — typed lifecycle callbacks around writes.
  • Soft delete — soft deletes and the tri-state read scopes.
  • Conventions — table naming, primary-key and foreign-key inference, and how to override each.
  • The query front-end — the explicit builder these models also work with.