The document#
func API(r *router.Router, app *vukem.App) error {
vukem.ServeOpenAPI(app, r, "/openapi.json")
apidocs.Serve(app, r, "/docs", apidocs.Config{Theme: "purple"})
// api.Register(...) routes
return nil
}vukem openapi:export > openapi.jsonopenapi:export and codegen:types build the app in warmup mode: no provider Boot, no database connection, APP_KEY optional. A warmup app never boots or serves (Boot and Run return vukem.ErrWarmupRun), so never expose its router from a custom main.
| Variable | Default | Meaning |
|---|---|---|
APP_NAME | Vukem | info.title |
API_VERSION | 1.0.0 | info.version |
OPENAPI_PREFIX | empty | keep only routes under it, e.g. /api |
OPENAPI_IN_PRODUCTION | false | serve /openapi.json and /docs in production |
vukem.ServeOpenAPIWith(app, r, "/api/v1/openapi.json", vukem.OpenAPIOptions{PathPrefix: "/api/v1", InProduction: true}) sets the prefix in code and opts one document into production; OPENAPI_PREFIX stays the default for ServeOpenAPI.
- Typed handlers are described from their types:
jsontags give property names,validaterules give constraints (required,min/max/len,email,url,uuid,oneof,regex,positive,unique,dive…), and adoc:"..."tag becomes the description. requiredlists the key inrequired. On a string it also emitsminLength: 1, since an empty string is rejected; on a list it emits nominItems, since an empty list passes (tagmin=1when items are needed).- Named structs become
components/schemas;ormrelations andorm.NullTimemap to their JSON shape; errors reference sharedProblemandValidationErrorresponses;Op.Securitynames become security requirements (beareris predefined). - Plain routes get a generic entry; hide one with
.Doc(openapi.Skip{}). app.OpenAPI()returns the document, andopenapi.Generate(r.Routes(), openapi.Info{...})builds one directly. CI validates a golden document with Redocly.
Checking responses in tests#
app.Client(t).GetJSON("/api/v1/products").AssertOK().AssertMatchesOpenAPI()AssertMatchesOpenAPI finds the operation for the request path and checks the body against the schema of the response status (or the default problem response). Outside tests, doc.ValidateResponse(method, path, status, body) does the same.
Types for clients#
vukem codegen:types -o resources/js/api.ts # TypeScript types
vukem codegen:types -zod -o resources/js/api.ts # plus Zod 4 schemas (zod 4.0+)You get one type per component schema plus <Operation>Body, <Operation>Query and <Operation>Response for every named operation, and, with -zod, Zod 4 validators: <Name>Schema per component and <Operation>BodySchema per JSON request body (inline bodies included). The command builds the app in warmup mode: no provider Boot, so no database connection, and APP_KEY may be unset (routes that need the key at build time still fail closed), so CI can run it without secrets. A component whose name would shadow a TypeScript or DOM global, or a name the generated client declares (Number, String, Object, Date, Error, Promise, Record, Array, Response, Request, URL, Client, ...), gets a Type suffix: a Go type Number becomes NumberType (Zod: NumberTypeSchema). If two generated names still collide (a Number and a NumberType schema, a component named like an operation type such as <Op>Body or <Op>Params, or two operation names that map to the same identifier), codegen:types and codegen:client fail with an error naming the duplicate instead of emitting code that does not compile. CI type-checks the golden output with tsc --strict.
Any z schema also exports on its own: z.JSONSchema(schema) returns JSON Schema 2020-12 (types, required keys, lengths, ranges, formats, patterns, enums, defaults, descriptions; Refine/Custom logic cannot be expressed).
API reference with Scalar#
apidocs.Serve(app, r, "/docs", apidocs.Config{
SpecURL: "/openapi.json",
Theme: "purple",
Layout: "modern",
Authentication: map[string]any{"preferredSecurityScheme": "bearer"},
})- The Scalar bundle (
@scalar/api-reference1.72.3, MIT) is vendored, embedded and pinned by SHA-256: no CDN, no telemetry, no remote fonts, no AI agent. - The page runs under the app's nonce-only CSP (no
'unsafe-inline'): it passes the request nonce to Scalar (acsp-noncemeta and a smallpage.jsshim that nonces the<style>elements Scalar creates and applies the plain size/overflow style attributes it sets through the CSSOM; any other style attribute is left to CSP, which blocks it). HTML from the OpenAPI document (descriptions) never gets the nonce: its<style>elements and style attributes stay blocked. Scalar's "Generate MCP" link and developer-tools header (both scalar.com services) are off. Its policy isapidocs.CSP, the default one plusdata:fonts;connect-src 'self'keeps "try it" requests on your origin. Inline styles inside HTML that Scalar injects (code highlighting) stay blocked, except the numeric line-number variables, which the shim re-applies. - Accessibility: the default theme gets AA text colors (muted text, HTTP method labels) and the shim makes the scrollable endpoint list keyboard-focusable and puts the visible text into the "Show More" buttons' accessible names, so the page passes axe serious checks in light and dark mode (checked by hand with axe-core 4.10;
TestDefaultThemeColorsMeetAAkeeps the colors at 4.5:1). Other themes keep their own colors. - Like
/openapi.json, it is not registered in production unlessOPENAPI_IN_PRODUCTION=trueorConfig.InProduction(one page only). For a public, versioned API serve its document and page explicitly:
vukem.ServeOpenAPIWith(app, r, "/api/v1/openapi.json",
vukem.OpenAPIOptions{PathPrefix: "/api/v1", InProduction: true})
apidocs.Serve(app, r, "/api/docs", apidocs.Config{SpecURL: "/api/v1/openapi.json", InProduction: true})- The
apidocspackage (about 4 MB) is linked only when you import it. Addapidocs.Command()to your commands forvukem openapi:serve [-addr localhost:8081], a local preview.