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.
type Book struct {
ID string `goninja:"list,retrieve"`
Title string `goninja:"list,retrieve,create"`
Price float64 `goninja:"list,retrieve,filter"`
}type BookList struct {
ID string `json:"id"`
Title string `json:"title"`
Price float64 `json:"price"`
}goninja generate, read the Go it wrote.Code-first
The struct is the single source of truth. No schema files, no YAML, no DSL — a goninja tag on each field decides which operations expose it.
Generated, not reflected
goninja generate writes ordinary Go you can read, diff and step through in a debugger. Zero reflection on the request path.
Plain net/http
Routes mount on an *http.ServeMux. No custom router, no context type of its own, no framework lock-in.
Safe by default
Output types are always separate structs from your GORM model, so a field can never leak into a response just because it exists on the model.
No N+1 by construction
list stays lean and never preloads. retrieve is the detail view and preloads every relation it carries. That split is a guarantee, not a default.
OpenAPI included
Every resource emits an OpenAPI fragment built from the same IR as its handlers, merged into one document and served with Swagger UI or ReDoc.
From struct to running API
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.
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/modelsMount it
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
goninja struct tag accepts.