Documentation Index
Fetch the complete documentation index at: https://docs.go-mizu.dev/llms.txt
Use this file to discover all available pages before exploring further.
OpenAPI (formerly known as Swagger) is a standard way to describe REST APIs. Contract automatically generates an OpenAPI specification from your service - no manual YAML writing needed!
What Is OpenAPI?
OpenAPI is a specification format that describes your API:
- What endpoints exist
- What parameters they accept
- What responses they return
- What your data types look like
This specification can be used to:
- Generate documentation: Beautiful, interactive API docs with Swagger UI or Redoc
- Generate client code: Create typed clients in TypeScript, Python, Go, and more
- Test your API: Import into Postman, Insomnia, or other API tools
- Validate requests: Ensure requests match the expected format
Quick Start
Generate and serve OpenAPI documentation:
import (
"github.com/go-mizu/mizu"
contract "github.com/go-mizu/mizu/contract/v2"
"github.com/go-mizu/mizu/contract/v2/transport/rest"
"yourapp/todo"
)
// Your interface is defined in the todo package as todo.API:
// type API interface {
// Create(ctx context.Context, in *CreateInput) (*Todo, error)
// List(ctx context.Context) (*ListOutput, error)
// Get(ctx context.Context, in *GetInput) (*Todo, error)
// }
// Create your service implementation
impl := todo.NewService()
// Register your service
svc := contract.Register[todo.API](impl,
contract.WithDefaultResource("todos"),
)
app := mizu.New()
// Mount REST endpoints
rest.Mount(app.Router, svc)
// Generate and serve OpenAPI spec
specBytes, _ := rest.OpenAPIDocument(svc)
app.Get("/openapi.json", func(c *mizu.Ctx) error {
c.Set("Content-Type", "application/json")
return c.Send(specBytes)
})
app.Listen(":8080")
Now visit http://localhost:8080/openapi.json to see your API specification.
What Gets Generated
Contract analyzes your service and generates a complete OpenAPI 3.0.3 specification.
From Your Interface
// todo/api.go
package todo
type API interface {
Create(ctx context.Context, in *CreateInput) (*Todo, error)
List(ctx context.Context) (*ListOutput, error)
Get(ctx context.Context, in *GetInput) (*Todo, error)
}
// todo/types.go
package todo
type CreateInput struct {
Title string `json:"title"`
Description string `json:"description"`
}
type Todo struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Completed bool `json:"completed"`
CreatedAt time.Time `json:"createdAt"`
}
type GetInput struct {
ID string `json:"id" path:"id"`
}
type ListOutput struct {
Items []*Todo `json:"items"`
Total int `json:"total"`
}
To OpenAPI Specification
{
"openapi": "3.0.3",
"info": {
"title": "Todo",
"version": "0.1.0"
},
"paths": {
"/todos": {
"post": {
"operationId": "todos_Create",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/CreateInput" }
}
}
},
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/Todo" }
}
}
}
}
},
"get": {
"operationId": "todos_List",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/ListOutput" }
}
}
}
}
}
},
"/todos/{id}": {
"get": {
"operationId": "todos_Get",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "string" }
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/Todo" }
}
}
}
}
}
}
},
"components": {
"schemas": {
"CreateInput": {
"type": "object",
"properties": {
"title": { "type": "string" },
"description": { "type": "string" }
},
"required": ["title", "description"]
},
"Todo": {
"type": "object",
"properties": {
"id": { "type": "string" },
"title": { "type": "string" },
"description": { "type": "string" },
"completed": { "type": "boolean" },
"createdAt": { "type": "string", "format": "date-time" }
},
"required": ["id", "title", "description", "completed", "createdAt"]
},
"ListOutput": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": { "$ref": "#/components/schemas/Todo" }
},
"total": { "type": "integer", "format": "int32" }
},
"required": ["items", "total"]
}
}
}
}
Adding Interactive Documentation
The JSON spec is machine-readable. For human-readable docs, add Swagger UI or Redoc.
Option 1: Swagger UI
Swagger UI provides an interactive interface where users can try your API:
app := mizu.New()
rest.Mount(app.Router, svc)
// Serve OpenAPI spec
specBytes, _ := rest.OpenAPIDocument(svc)
app.Get("/openapi.json", func(c *mizu.Ctx) error {
c.Set("Content-Type", "application/json")
return c.Send(specBytes)
})
// Serve Swagger UI at /docs
app.Get("/docs", func(c *mizu.Ctx) error {
return c.HTML(200, `<!DOCTYPE html>
<html>
<head>
<title>API Documentation</title>
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css">
</head>
<body>
<div id="swagger-ui"></div>
<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
<script>
SwaggerUIBundle({
url: "/openapi.json",
dom_id: "#swagger-ui"
});
</script>
</body>
</html>`)
})
app.Listen(":8080")
Now visit http://localhost:8080/docs to see interactive documentation!
Option 2: Redoc
Redoc provides clean, three-panel documentation:
app.Get("/docs", func(c *mizu.Ctx) error {
return c.HTML(200, `<!DOCTYPE html>
<html>
<head>
<title>API Documentation</title>
</head>
<body>
<redoc spec-url="/openapi.json"></redoc>
<script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"></script>
</body>
</html>`)
})
Complete Example
This example shows OpenAPI generation using the recommended package-based organization:
// todo/types.go
package todo
import "time"
type Todo struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Completed bool `json:"completed"`
CreatedAt time.Time `json:"createdAt"`
}
type CreateInput struct {
Title string `json:"title"`
Description string `json:"description,omitempty"`
}
type GetInput struct {
ID string `json:"id" path:"id"`
}
type ListOutput struct {
Items []*Todo `json:"items"`
Total int `json:"total"`
}
// todo/api.go
package todo
import "context"
// API defines the contract for todo operations
type API interface {
Create(ctx context.Context, in *CreateInput) (*Todo, error)
Get(ctx context.Context, in *GetInput) (*Todo, error)
List(ctx context.Context) (*ListOutput, error)
}
// todo/service.go
package todo
import (
"context"
"fmt"
"sync"
"time"
contract "github.com/go-mizu/mizu/contract/v2"
)
// Service implements todo.API
type Service struct {
mu sync.RWMutex
todos map[string]*Todo
nextID int
}
// Compile-time check
var _ API = (*Service)(nil)
func NewService() *Service {
return &Service{todos: make(map[string]*Todo)}
}
func (s *Service) Create(ctx context.Context, in *CreateInput) (*Todo, error) {
if in.Title == "" {
return nil, contract.ErrInvalidArgument("title is required")
}
s.mu.Lock()
defer s.mu.Unlock()
s.nextID++
todo := &Todo{
ID: fmt.Sprintf("%d", s.nextID),
Title: in.Title,
Description: in.Description,
CreatedAt: time.Now(),
}
s.todos[todo.ID] = todo
return todo, nil
}
func (s *Service) Get(ctx context.Context, in *GetInput) (*Todo, error) {
s.mu.RLock()
defer s.mu.RUnlock()
todo, ok := s.todos[in.ID]
if !ok {
return nil, contract.ErrNotFound("todo not found")
}
return todo, nil
}
func (s *Service) List(ctx context.Context) (*ListOutput, error) {
s.mu.RLock()
defer s.mu.RUnlock()
items := make([]*Todo, 0, len(s.todos))
for _, t := range s.todos {
items = append(items, t)
}
return &ListOutput{Items: items, Total: len(items)}, nil
}
// main.go
package main
import (
"fmt"
"github.com/go-mizu/mizu"
contract "github.com/go-mizu/mizu/contract/v2"
"github.com/go-mizu/mizu/contract/v2/transport/rest"
"yourapp/todo"
)
func main() {
impl := todo.NewService()
svc := contract.Register[todo.API](impl,
contract.WithName("Todo"),
contract.WithDescription("A simple todo list API"),
contract.WithDefaultResource("todos"),
)
app := mizu.New()
// Mount REST API
rest.Mount(app.Router, svc)
// Serve OpenAPI specification
specBytes, err := rest.OpenAPIDocument(svc)
if err != nil {
panic(err)
}
app.Get("/openapi.json", func(c *mizu.Ctx) error {
c.Set("Content-Type", "application/json")
return c.Send(specBytes)
})
// Serve Swagger UI
app.Get("/docs", swaggerUIHandler)
fmt.Println("Server: http://localhost:8080")
fmt.Println("API: http://localhost:8080/todos")
fmt.Println("Spec: http://localhost:8080/openapi.json")
fmt.Println("Docs: http://localhost:8080/docs")
app.Listen(":8080")
}
func swaggerUIHandler(c *mizu.Ctx) error {
return c.HTML(200, `<!DOCTYPE html>
<html><head>
<title>Todo API Docs</title>
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css">
</head><body>
<div id="swagger-ui"></div>
<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
<script>SwaggerUIBundle({url: "/openapi.json", dom_id: "#swagger-ui"});</script>
</body></html>`)
}
Using the OpenAPI Spec
Generate Client Code
Use tools like openapi-generator to create typed clients:
# Install openapi-generator
npm install -g @openapitools/openapi-generator-cli
# Generate TypeScript client
openapi-generator-cli generate \
-i http://localhost:8080/openapi.json \
-g typescript-fetch \
-o ./client
# Generate Python client
openapi-generator-cli generate \
-i http://localhost:8080/openapi.json \
-g python \
-o ./python-client
# Generate Go client
openapi-generator-cli generate \
-i http://localhost:8080/openapi.json \
-g go \
-o ./go-client
Import into Postman
- Open Postman
- Click Import
- Enter URL:
http://localhost:8080/openapi.json
- Your API is now ready to test!
Validate with oasdiff
Check for breaking changes between versions:
# Compare two versions of your spec
oasdiff diff old-api.json http://localhost:8080/openapi.json
Add service information that appears in the generated spec:
svc := contract.Register[todo.API](impl,
contract.WithName("Todo"),
contract.WithDescription("A simple todo list API"),
contract.WithDefaultResource("todos"),
)
This produces:
{
"openapi": "3.0.3",
"info": {
"title": "Todo",
"description": "A simple todo list API",
"version": "0.1.0"
}
}
Multiple Services
Generate specs for multiple services:
todoSvc := contract.Register[todo.API](todoImpl,
contract.WithName("Todo"),
contract.WithDefaultResource("todos"),
)
userSvc := contract.Register[user.API](userImpl,
contract.WithName("User"),
contract.WithDefaultResource("users"),
)
app := mizu.New()
// Mount REST endpoints
rest.Mount(app.Router, todoSvc) // /todos
rest.Mount(app.Router, userSvc) // /users
// Serve separate OpenAPI specs
todoSpec, _ := rest.OpenAPIDocument(todoSvc)
userSpec, _ := rest.OpenAPIDocument(userSvc)
app.Get("/api/todo/openapi.json", func(c *mizu.Ctx) error {
c.Set("Content-Type", "application/json")
return c.Send(todoSpec)
})
app.Get("/api/user/openapi.json", func(c *mizu.Ctx) error {
c.Set("Content-Type", "application/json")
return c.Send(userSpec)
})
app.Listen(":8080")
Type Mappings
Contract converts Go types to OpenAPI schema types:
| Go Type | OpenAPI Type | Format |
|---|
string | string | - |
bool | boolean | - |
int, int32 | integer | int32 |
int64 | integer | int64 |
float32 | number | float |
float64 | number | double |
time.Time | string | date-time |
*T (pointer) | anyOf: [T, null] | - |
[]T (slice) | array | items: T |
map[string]T | object | additionalProperties: T |
Optional Fields
Use omitempty to mark fields as optional:
type CreateInput struct {
Title string `json:"title"` // required
Description string `json:"description,omitempty"` // optional
}
This produces:
{
"type": "object",
"properties": {
"title": { "type": "string" },
"description": { "type": "string" }
},
"required": ["title"]
}
Path Parameters
Fields with the path tag are documented as path parameters:
type GetInput struct {
ID string `json:"id" path:"id"`
}
This produces:
{
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "string" }
}
]
}
Query Parameters
For GET requests, input fields not used in the path become query parameters:
type ListInput struct {
Status string `json:"status" query:"status"`
Limit int `json:"limit" query:"limit"`
}
This produces:
{
"parameters": [
{
"name": "status",
"in": "query",
"required": true,
"schema": { "type": "string" }
},
{
"name": "limit",
"in": "query",
"required": true,
"schema": { "type": "integer", "format": "int32" }
}
]
}
Common Questions
How do I add descriptions to endpoints?
Method descriptions come from the contract.WithDescription option at the method level (not yet implemented). For now, the operation ID includes the method name.
What OpenAPI version is generated?
OpenAPI 3.0.3, a widely supported version.
Can I customize the generated spec?
The current generator produces a standard spec. For extensive customization:
- Generate the spec with
rest.OpenAPIDocument(svc)
- Parse and modify it programmatically
- Serve the modified version
Does it support authentication schemes?
Yes, when using WithDefaults with auth settings:
svc := contract.Register[todo.API](impl,
contract.WithDefaults(contract.Defaults{
Auth: "bearer",
}),
)
This adds bearer token security to the spec.
Not directly. The generator creates a minimal, standards-compliant spec. For advanced customization, modify the generated spec programmatically.
Whatβs Next?