Skip to main content
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

  1. Open Postman
  2. Click Import
  3. Enter URL: http://localhost:8080/openapi.json
  4. 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

Service Metadata

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 TypeOpenAPI TypeFormat
stringstring-
boolboolean-
int, int32integerint32
int64integerint64
float32numberfloat
float64numberdouble
time.Timestringdate-time
*T (pointer)anyOf: [T, null]-
[]T (slice)arrayitems: T
map[string]TobjectadditionalProperties: 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:
  1. Generate the spec with rest.OpenAPIDocument(svc)
  2. Parse and modify it programmatically
  3. 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.

Can I add custom tags or extensions?

Not directly. The generator creates a minimal, standards-compliant spec. For advanced customization, modify the generated spec programmatically.

What’s Next?