OpenAPI
The openapi module introspects every route registered across all modules at startup and
emits a complete OpenAPI 3.0.3 specification — with typed schemas, security schemes,
validation constraints, and $ref references. A zero-configuration interactive UI is served
alongside the raw spec JSON.
How it works
Module routes + metadata options
↓
openapi.SchemaRegistry
(struct reflection → JSON Schema)
↓
openapi.GenerateSpec()
↓
/openapi.json ← spec
/reference ← UI (Scalar / Swagger UI / Redoc)
Every call to router.Handle() carries optional metadata options. The openapi module
collects them at startup and generates a complete spec — no annotations, no code generation,
no separate YAML files.
Quick start
import (
_ "github.com/redelay/go-framework/modules/openapi"
)
Blank-import registers the module. On first request to /openapi.json the spec is generated
and cached. Restart restarts generation.
Endpoint metadata
Declare metadata directly on the route handler with functional options:
func (m *Module) Routes(router modules.Router) {
router.Group("/orders", func(r modules.Router) {
r.Handle("GET", "/", http.HandlerFunc(m.list),
modules.Summary("List orders"),
modules.QueryParam("page", "integer", "Page number", "1"),
modules.QueryParam("page_size","integer","Items per page", "20"),
modules.Response(200, "Paginated orders", OrderListResponse{}),
modules.Security("BearerAuth"),
)
r.Handle("POST", "/", http.HandlerFunc(m.create),
modules.Summary("Create order"),
modules.Body(OrderCreateInput{}),
modules.Response(201, "Order created", OrderResponse{}),
modules.Response(400, "Invalid request", modules.RedelayErrorResponse{}),
modules.Response(409, "Duplicate order", modules.RedelayErrorResponse{}),
modules.Security("BearerAuth"),
)
r.Handle("GET", "/{id}", http.HandlerFunc(m.get),
modules.Summary("Get order by ID"),
modules.PathParam("id", "Order ID", "string", "objectid"),
modules.Response(200, "Order", OrderResponse{}),
modules.Response(404, "Not found", modules.RedelayErrorResponse{}),
modules.Security("BearerAuth"),
)
})
}
All metadata options:
| Option | Description |
|---|---|
Summary(s) | Short operation summary (shown in UI lists) |
Description(s) | Longer Markdown description |
Tags(t...) | Group operations — defaults to module name |
PathParam(name, desc, type?, format?) | Path parameter, auto-marked required |
QueryParam(name, type, desc, default?) | Query string parameter |
Body(model, desc?) | Request body — reflects Go struct to JSON Schema |
Response(status, desc, model?) | Response — reflects model if provided |
Security(scheme, scopes...) | Security requirement e.g. "BearerAuth" |
EndpointDeprecated() | Mark operation as deprecated |
Schema generation
Go structs are reflected into JSON Schema automatically. No struct tags beyond json: and
validate: are needed:
type OrderCreateInput struct {
CustomerID string `json:"customer_id" validate:"required,uuid4"`
Items []Item `json:"items" validate:"required,min=1"`
Currency string `json:"currency" validate:"required,len=3"`
Notes *string `json:"notes"` // pointer → nullable
}
type Item struct {
ProductID string `json:"product_id" validate:"required"`
Quantity int `json:"quantity" validate:"required,min=1"`
UnitPrice float64 `json:"unit_price" validate:"required,min=0"`
}
Produces:
{
"OrderCreateInput": {
"type": "object",
"required": ["customer_id", "items", "currency"],
"properties": {
"customer_id": { "type": "string", "format": "uuid" },
"items": {
"type": "array",
"minItems": 1,
"items": { "$ref": "#/components/schemas/Item" }
},
"currency": { "type": "string", "minLength": 3, "maxLength": 3 },
"notes": { "type": "string", "nullable": true }
}
}
}
Tag → schema mapping:
validate tag | JSON Schema |
|---|---|
required | field in required array |
email | format: "email" |
uuid4 | format: "uuid" |
min=N | minLength: N (string) · minimum: N (number) |
max=N | maxLength: N (string) · maximum: N (number) |
len=N | minLength: N + maxLength: N |
Type mapping:
| Go type | JSON Schema |
|---|---|
string | string |
int, int32 | integer |
float32, float64 | number |
bool | boolean |
*T | type of T + nullable: true |
[]T | array + items schema |
map[string]T | object + additionalProperties |
time.Time | string + format: "date-time" |
primitive.ObjectID | string + format: "objectid" |
| Embedded struct | merged properties |
Security schemes
Configure on the spec itself, not per-route. Schemes declared here are referenced
by name in per-route Security() options:
import "github.com/redelay/go-framework/openapi"
openapi.GenerateSpec(title, version, registry,
openapi.WithBearerAuth(),
// OR:
openapi.WithOAuth2PasswordFlow("/api/v1/auth/login", map[string]string{
"read": "Read access",
"write": "Write access",
}),
)
WithOAuth2PasswordFlow enables Swagger UI's built-in Authorize popup —
testers can enter username and password directly in the UI without copying tokens manually.
UI providers
The OPENAPI_UI environment variable selects the documentation UI:
| Value | UI | Best for |
|---|---|---|
scalar (default) | Scalar | Clean, modern look |
swagger | Swagger UI | OAuth2 Authorize flow, familiar |
redoc | Redoc | Read-heavy reference docs |
disabled | — | Spec-only (headless services) |
All UIs are served from a CDN build embedded in the binary — no Node.js, no build step.
Schema reference diagram
Go struct (OrderCreateInput)
│
▼
SchemaRegistry.Register()
│
▼
components/schemas/OrderCreateInput ← named, reusable
│
├── referenced by POST /orders requestBody
├── referenced by GET /orders/{id} 200 response
└── referenced by inline Item sub-schema
Named structs appear once in components/schemas and are referenced via $ref everywhere
they are used. Primitive and anonymous types are inlined.
Built-in module spec preview
When all built-in modules are loaded, the generated spec includes:
POST /api/v1/auth/login Auth — Login
POST /api/v1/auth/refresh Auth — Refresh token
POST /api/v1/auth/revoke Auth — Revoke token
GET /api/v1/users/me Users — Current user profile
GET /api/v1/users/ Users — List users
POST /api/v1/users/ Users — Create user
GET /api/v1/users/{id} Users — Get user
PATCH /api/v1/users/{id} Users — Update user
DELETE /api/v1/users/{id} Users — Delete user
GET /api/v1/health Health — Health check
All schemas (UserResponse, AuthTokenResponse, RedelayErrorResponse, …) are in
components/schemas and referenced via $ref.
Modspec integration
The modspec browser at /modules links
directly to /reference in its top navigation bar, so your team can navigate from the
architecture view straight to the interactive API docs without leaving the browser.
Configuration
| Variable | Default | Description |
|---|---|---|
OPENAPI_UI | scalar | UI provider (scalar, swagger, redoc, disabled) |
OPENAPI_SPEC_PATH | /openapi.json | URL path for the JSON spec |
OPENAPI_UI_PATH | /reference | URL path for the interactive UI |
APP_NAME | redelay | Value of info.title in the spec |
APP_VERSION | 1.0.0 | Value of info.version in the spec |
Programmatic spec generation
The openapi package can generate specs outside of HTTP context — useful for CI checks,
contract testing, or publishing to API gateways:
import (
"github.com/redelay/go-framework/openapi"
"github.com/redelay/go-framework/modules"
)
registry := openapi.NewSchemaRegistry()
spec := openapi.GenerateSpec("My Service", "1.0.0", registry,
openapi.WithBearerAuth(),
)
// Write to file
f, _ := os.Create("openapi.json")
json.NewEncoder(f).Encode(spec)