v0.1.0
Released 20 August 2026. The first tagged release, and the first version you can install by version number.
Install
go install github.com/caspel26/goninja/cmd/goninja@v0.1.0
go get github.com/caspel26/goninja@v0.1.0Requires 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.
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/httpare assumed. There is no adapter layer for other ORMs or routers. - The primary key must be a field literally named
ID, typedint64orstring(treated as a UUID). A model without one silently falls back toint64and 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.