OOro

Oro ORM

Go 1.27+

Your code is the query

A generic-first ORM for Go — semantic conditions, transparent SQL, zero codegen.

  • Humane semantics
  • No codegen
  • No import cycle
  • No black box
go get github.com/duxweb/oro
product.go
// a model is just a plain Go struct
type Product struct {
    oro.Model
    Code  string
    Price uint
}

// write conditions the way you'd say them
list, _ := db.Use[Product]().
    Where(
        oro.Field("Price").Gte(100),
        oro.Field("Code").Like("P%"),
    ).
    Get(ctx)
Core Design

Make ORM code feel like Go code

Readable queries, no generated models, relations without import cycles, and result types with clear boundaries.

Readable queries

Fields, comparisons, and grouped conditions have explicit methods. Query intent stays visible without hiding logic in SQL strings.

db.Use[Product]().Where(oro.Field("Price").Gte(100))
db.Use[Product]().Where(oro.Field("Code").Like("A%"))

No generator

Models are ordinary Go structs and queries use the generic model entry. There is no generated client or extra build step.

db.Register(Product{})
db.Use[Product]().Create(ctx, product)

No relation cycles

Relations live in methods instead of embedded struct fields. Models can split across packages without forcing import cycles.

article.Cover().One[Image]()
db.Use[Article]().With(Article{}.Cover())

Clear type boundaries

Model queries return concrete types, dynamic writes use Map, and raw SQL goes through Raw. Flexible paths still have clear boundaries.

db.Use[Product]().First(ctx)  // *Product
db.Table("products").First(ctx) // oro.Map
Design Tradeoffs

Keep ORM boundaries visible in code

Oro does not hide complexity behind generators or pack schema settings into tag strings. Models, tables, and raw SQL are separate entries: use models for business code, tables for dynamic flows, and Raw for complex queries.

  • Schema lives in Define, not in overloaded tags.
  • Writes are explicit, so zero values are not guessed.
  • Relations live in methods, reducing import-cycle pressure.
func (Product) Define(s *oro.SchemaBuilder) {
  s.Table("products")
  s.Field("Code").String().Unique()
  s.Field("Price").Uint().Default(0)
}

db.Use[Product]()      // *Product / []Product
db.Table("products")  // oro.Map
db.Raw("select ...")  // Map or MapTo[T]()

Ready in a single go get

Open a database, register a model, sync the schema and run your first typed query in minutes.