Skip to content
Snapshot of the v0.7 release. Fixes and additions since then are not in it. Current documentation →

v0.1.0

Released 20 August 2026. The first tagged release, and the first version you can install by version number.

Pre-alpha. Everything below is implemented and tested, but the API may change without notice and there is no compatibility guarantee yet.

Install

go install github.com/caspel26/goninja/cmd/goninja@v0.1.0
go get github.com/caspel26/goninja@v0.1.0

Requires Go 1.25 or newer.

Added

Code generation

goninja generate reads a directory of annotated structs and writes one <model>_generated.go per model — separate List/Retrieve/Create/Update output types, HTTP handlers, GORM queries and an OpenAPI fragment, all formatted with go/format. The field-level tag vocabulary is list, retrieve, create, update and filter, plus the relation-only modifier byid.

-watch keeps the generator running and regenerates on any .go change under the models directory, debounced 300ms so one editor save produces one regeneration.

See Struct Tags and the CLI reference.

Relations without N+1

list never preloads; retrieve is the detail view and preloads every relation field it carries. Belongs-to and has-many relations are both supported, and byid exposes a related ID instead of nesting the full object. This split is a guarantee of the generator, not a default you can drift away from — the code that would N+1 is never written.

See Relations.

Filtering, ordering and pagination

filter-tagged fields become exact-match filters on a generated <Model>Filters struct, with _min/_max range filters added for numeric fields. List responses are wrapped in a ListEnvelope[T] ({items, total, limit, offset}), and ?order=-field is resolved against a per-model column whitelist — which is also what makes ordering injection-safe.

See Filtering & Pagination.

Validation and errors

validate struct tags are copied onto Create and Update types only — never onto read paths — and checked before the database is touched, returning a 422 keyed by JSON field name. RegisterValidation registers custom tags at startup.

NotFound, ValidationError and BadRequest map to 404, 422 and 400 through a pluggable ErrorMapper. Anything else becomes a generic 500 that never leaks the underlying error.

See Validation and Errors.

Transactions, hooks and overrides

create, update and delete run inside InTransaction, so an error from any hook rolls the whole operation back — including AfterCreate rolling back the row just inserted. Available hooks are BeforeCreateHook, AfterCreateHook, BeforeUpdateHook and BeforeDeleteHook.

ResourceConfig and Configurer override a resource’s mount path and restrict which routes it emits, and Action with BaseResource.SetActions mounts non-CRUD endpoints alongside the generated ones — documented in the same OpenAPI fragment.

See Hooks & Overrides, Transactions and Actions.

Authentication

Authenticator objects are tried in order, with per-route policy through Config/AuthPolicy and API.MountWithConfig. The security scheme an authenticator describes is emitted into the generated OpenAPI document by the same resolution used to enforce it, so what is documented and what is enforced cannot drift apart. HTTPBearer, HTTPBasic, APIKeyHeader and CookieKey ship built in.

See Authentication.

OpenAPI and docs UI

NewAPI and Mount merge every resource’s fragment into one document, and MountDocs serves it as JSON alongside a rendered UI. Swagger UI and ReDoc are both vendored and embedded — no external CDN — behind the swappable docsui.DocsUI interface.

See OpenAPI & docs UI.

Testing helpers

goninjatest.NewDB and goninjatest.NewServer drive a real generated resource over HTTP against in-memory SQLite, with no Postgres required.

See Testing.

Known limitations

These are the sharp edges in this release, stated plainly:

  • GORM and net/http are assumed. There is no adapter layer for other ORMs or routers.
  • The primary key must be a field literally named ID, typed int64 or string (treated as a UUID). A model without one silently falls back to int64 and fails to compile downstream.
  • Relation fields must be a struct value or a slice of one. Pointer relations are not supported.
  • An unknown ?order= field is ignored, not rejected — a typo in a query string returns unordered results rather than a 400.
  • No OpenAPI example values are generated. Schemas carry types and formats, but no sample payloads.

Links