CRUD Oro Store
Connect Oro models to Runa CRUD and quickly expose business data APIs
crud/orostore adapts Oro model queries to Runa crud.Store. If your project already uses Database Oro or raw Oro ORM, this adapter lets you expose list, show, create, edit, patch, delete, soft-delete, and export APIs with much less code
It solves the bridge between Oro models and Runa CRUD. Models, relations, query expressions, and database behavior belong to Oro. Resource routes, actions, writable fields, output transforms, filters, and pagination belong to Runa CRUD
Install
go get github.com/duxweb/runa/crud github.com/duxweb/runa/crud/orostore
If Runa manages the database lifecycle, also install:
go get github.com/duxweb/runa/database github.com/duxweb/runa/database/oro
SQLite examples need:
go get modernc.org/sqlite
Prerequisite
First prepare an Oro DB:
app.Install(database.Provider(
database.RegisterDriver("default", dboro.Driver(
dboro.DSN("file:app.db"),
dboro.Dialect("sqlite"),
)),
))
After Freeze, read the Oro runtime:
orm := dboro.From(database.Default().MustGet("default"))
Define a model
import oro "github.com/duxweb/oro"
type User struct {
oro.Model
Name string
Email string
Status int
}
func (User) Define(s *oro.SchemaBuilder) {
s.Table("users")
s.Field("Name").String().Size(120)
s.Field("Email").String().Size(180).Unique()
s.Field("Status").Int()
}
Register and sync during development:
if err := orm.Register(User{}); err != nil {
panic(err)
}
if err := orm.Sync(context.Background()); err != nil {
panic(err)
}
Create CRUD APIs
import (
"github.com/duxweb/runa/crud"
"github.com/duxweb/runa/crud/filter"
"github.com/duxweb/runa/crud/orostore"
"github.com/duxweb/runa/resource"
"github.com/duxweb/runa/route"
)
func registerUserRoutes(orm *oro.DB) {
users := resource.New(route.Default().Group("/admin"), "/users").
Name("user").
Summary("Users")
crud.New[User](users, orostore.Store[User](orm)).
Actions(crud.ListAction, crud.ShowAction, crud.CreateAction, crud.EditAction, crud.StoreAction, crud.DeleteAction).
Key("id").
Page(20, 100).
Sort("id", "desc").
SortFields(crud.SortField{Name: "name"}).
Filters(
filter.Eq[int]("status"),
filter.Like("name"),
)
}
Common generated routes:
| Action | Method and path | Description |
|---|---|---|
ListAction |
GET /admin/users |
List and paginate |
ShowAction |
GET /admin/users/{id} |
Show one record |
CreateAction |
POST /admin/users |
Create |
EditAction |
PUT /admin/users/{id} |
Full update |
StoreAction |
PATCH /admin/users/{id} |
Partial update |
DeleteAction |
DELETE /admin/users/{id} |
Delete |
Output transform
Prefer returning explicit output structs instead of exposing database models directly:
type UserOutput struct {
ID uint64 `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Status int `json:"status"`
}
crud.New[User](users, orostore.Store[User](orm)).
Transform[UserOutput](func(c *crud.Context[User], model *User) UserOutput {
return UserOutput{ID: model.ID, Name: model.Name, Email: model.Email, Status: model.Status}
})
Writable fields
Use Format to define which request fields can write to the model:
crud.New[User](users, orostore.Store[User](orm)).
Format(func(c *crud.Context[User], f *crud.Formatter[User]) {
f.Field("name").To(&c.Model.Name).Actions(crud.CreateAction, crud.EditAction, crud.StoreAction)
f.Field("email").To(&c.Model.Email).Actions(crud.CreateAction, crud.EditAction, crud.StoreAction)
f.Field("status").To(&c.Model.Status).Actions(crud.CreateAction, crud.EditAction, crud.StoreAction)
})
Filters, sorting, and pagination
crud.New[User](users, orostore.Store[User](orm)).
Page(20, 100).
Sort("id", "desc").
SortFields(crud.SortField{Name: "id"}, crud.SortField{Name: "name"}).
Filters(filter.Eq[int]("status"), filter.Like("name"))
Runa CRUD reads request parameters and creates filter/sort values. orostore applies them to Oro queries
Relations
If the model defines Oro relations, preload them through CRUD:
crud.New[User](users, orostore.Store[User](orm)).
Relations("Role")
Relation definitions still follow Oro’s model conventions
Soft delete
If the model uses Oro softdelete extension:
crud.New[User](users, orostore.Store[User](orm)).
Actions(crud.ListAction).
SoftDelete()
Export
crud.New[User](users, orostore.Store[User](orm)).
Export[UserOutput](func(c *crud.Context[User], model *User) (UserOutput, error) {
return UserOutput{ID: model.ID, Name: model.Name}, nil
}, func(exporter *crud.Exporter[User, UserOutput]) error {
exporter.Name("users").Formats("csv").Batch(500)
exporter.Field("id").Title("ID")
exporter.Field("name").Title("Name")
return nil
})
Resolve database per request
store := orostore.StoreFrom[User](func(ctx *crud.Context[User]) *oro.DB {
return dboro.From(database.Default().MustGet("default"))
})
crud.New[User](users, store)
Use StoreFrom for tenants, regions, or business lines that select different connections per request
Standalone usage
If you already have a raw Oro DB, pass it directly:
oroDB, err := oro.Open(oro.Config{/* ... */})
if err != nil {
panic(err)
}
store := orostore.Store[User](oroDB)
crud.New[User](users, store)
Supported capabilities
orostore implements standard CRUD store behavior, transactions, pagination, cursor pagination, sorting, filtering, relation loading, soft-delete actions, and export batch reads
Common mistakes
orostore database is nilmeans the*oro.DBresolver returned nil, often because the database was read beforeFreeze- Keep
Key("id")aligned with your route parameter and model primary key - Use
Transformfor output instead of exposing models directly - Use
Formatto restrict writable fields - Read Oro docs for model, relation, transaction, and query details