Skip to content
Snapshot of the v0.6 release. Fixes and additions since then are not in it. Current documentation →
Pre-alpha · built in the open

Typed REST APIs, 
generated from your structs

Annotate a Go struct, run one command, and get real .go files: handlers, queries, validation, filters, pagination and OpenAPI. Nothing is reflected at request time — if a tag is wrong, the build fails, not production.

Tag the struct, run goninja generate, read the Go it wrote.

From struct to running API

Three steps. The middle one is a command; the other two are files you write.

Annotate a model

Each goninja verb decides which operations expose that field. validate tags apply to input only, so Price can never be written negative.

models/book.go
type Book struct {
    ID        string    `gorm:"primaryKey;type:uuid" goninja:"list,retrieve"`
    Title     string    `gorm:"size:200;not null" goninja:"list,retrieve,create,update" validate:"required,max=200"`
    AuthorID  string    `goninja:"list,retrieve,create,update,filter" validate:"required,uuid4"`
    Price     float64   `goninja:"list,retrieve,create,update,filter" validate:"min=0"`
    Published bool      `goninja:"list,retrieve,create,update,filter"`
    CreatedAt time.Time `goninja:"list,retrieve"`
    Author    Author    `goninja:"retrieve"`
}

Generate

This writes one <model>_generated.go per model — output types, handlers, queries and an OpenAPI fragment. Add -watch to regenerate on save.

goninja generate -models-import myapp/models

Mount it

main.go
mux := http.NewServeMux()
app := goninja.NewAPI("Bookstore API", "0.1.0")

app.Mount(mux, api.NewAuthorResource(db), api.NewBookResource(db))
app.MountDocs(mux, "/docs", docsui.SwaggerUI())

http.ListenAndServe(":8080", mux)

That serves a full CRUD surface with filtering, ordering, pagination, validation and live API docs:

curl "localhost:8080/books?published=true&price_min=10&order=-created_at&limit=20"
{
  "items": [{ "id": "9f1c…", "title": "The Go Programming Language", "price": 34.99 }],
  "total": 128,
  "limit": 20,
  "offset": 0
}

Where to go next