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

Generated Code

Running goninja generate writes one file per model into the -out directory, named <modellowercase>_generated.go — for a Book model, book_generated.go. The file is run through go/format before being written, and every generated file starts with the same header:

// Code generated by goninja generate. DO NOT EDIT.

Never hand-edit a generated file. Change the model’s struct tags or the templates under internal/codegen/templates/*.tmpl instead, then regenerate.

What one file contains

For book_generated.go, the top-level declarations appear in this order:

  • Types: BookList, BookRetrieve, BookCreate, BookUpdate, BookFilters
  • Var: bookOrderableColumns
  • A const block
  • Functions: toBookList, toBookRetrieve
  • Type: BookResource (embeds goninja.BaseResource)
  • Function: NewBookResource
  • Interface: BookOps
  • Methods: ops, List, Retrieve, Create, Update, Delete
  • Function: parseBookFilters
  • Methods: listHandler, retrieveHandler, createHandler, updateHandler, deleteHandler, OpenAPI, Register
  • Internal OpenAPI helper functions and resourceConfig

The four output types

Every model produces four distinct types, plus a filters type. None of them is the GORM model reused directly — a field only appears in a response or request body because it was explicitly tagged for that purpose. This is deliberate: reusing the model struct as a response type would leak any field added to it later, tagged or not.

Real output for the Book model from the Struct Tags example:

internal/api/book_generated.go
type BookList struct {
	ID        string    `json:"id"`
	Title     string    `json:"title"`
	AuthorID  string    `json:"author_id"`
	Price     float64   `json:"price"`
	Published bool      `json:"published"`
	CreatedAt time.Time `json:"created_at"`
}

type BookRetrieve struct {
	ID        string         `json:"id"`
	Title     string         `json:"title"`
	AuthorID  string         `json:"author_id"`
	Author    AuthorRetrieve `json:"author"`
	Price     float64        `json:"price"`
	Published bool           `json:"published"`
	CreatedAt time.Time      `json:"created_at"`
}

type BookCreate struct {
	Title     string  `json:"title" validate:"required,max=200"`
	AuthorID  string  `json:"author_id" validate:"required,uuid4"`
	Price     float64 `json:"price" validate:"min=0"`
	Published bool    `json:"published"`
}

// BookUpdate has an identical shape to BookCreate.

Note Published carries no validate tag on BookCreate, because the model field itself had none — the tag is copied verbatim, not synthesized. Note also that a relation field on Retrieve is nested as the related model’s own Retrieve type (AuthorRetrieve) when it is not tagged byid — see Struct Tags and Relations.

Extension points

Two things in the generated file exist specifically so you can extend a resource without editing generated code:

  • The <Model>Ops interface (BookOps here) is what makes per-method overriding possible — a wrapper type that embeds *BookResource and redefines one of List/Retrieve/Create/Update/Delete still satisfies it.
  • <Model>Resource embeds goninja.BaseResource, which supplies SetSelf/Self() — the dispatch point hooks, Configurer, and method overrides all resolve through, since Go has no dynamic dispatch through embedding on its own.

See Hooks and Overrides for how these are used in practice.

OpenAPI type mapping

Every generated <Model>Resource also gets an OpenAPI() method built from the same IR as the rest of the file — never hand-maintained JSON. Go types map to JSON Schema type/format like this:

ConditionOpenAPI typeOpenAPI format
Boolboolean(none)
Stringstring(none)
Float (float32/float64)numberdouble
time.Timestringdate-time
Other numeric (int*/uint*)integerint64
Anything else (relation)object(none)

A relation field’s schema is always a $ref to the related model’s own Retrieve schema, mirroring how toBookRetrieve nests it at runtime.

goninja does not generate example values in the OpenAPI document — there is no Example field anywhere in the generated schemas. If your docs UI shows examples, they came from somewhere else.

Commit generated code

Treat generated files as part of the repository, not as a build artifact to .gitignore. Committing them means a reviewer sees the actual API surface change in a pull request, and a fresh checkout builds without running the generator first. Regenerate and re-commit whenever a model’s tags change; see CLI for the exact command.