Putnami
DocsGitHub

Licensed under FSL-1.1-MIT

Getting Started
Concepts
How To
Build A Web App
Build An Api Service
Share Code Between Projects
Configure Your App
Add Persistence
Add Authentication
Add Background Jobs
Develop With Ai
Structure Business Logic With Di
Upgrade Putnami
Principles
Tooling & Workspace
Workspace
Cli
Jobs & Caching
Extensions
Templates
Error Handling
Frameworks
Typescript
ExtensionOverviewWebReact RoutingForms And ActionsStatic FilesApiErrors And ResponsesConfigurationLoggingHttp And MiddlewareDependency InjectionPlugins And LifecycleSessionsAuthPersistenceEventsStorageCachingWebsocketsTestingHealth ChecksTelemetryProto GrpcSmart ClientSchema
Go
ExtensionOverviewHttpDependency InjectionPlugins And LifecycleConfigurationSecurityPersistenceErrorsEventsStorageCachingLoggingTelemetryGrpcService ClientsValidationOpenapiTesting
Python
Extension
Platform
Ci
  1. DocsSeparator
  2. FrameworksSeparator
  3. GoSeparator
  4. Openapi

OpenAPI

go.putnami.dev/openapi generates OpenAPI 3.0.3 specifications from Go struct types and endpoint metadata.

Plugin setup

import (
    "go.putnami.dev/app"
    fhttp "go.putnami.dev/http"
    "go.putnami.dev/openapi"
)

server := fhttp.NewServerPlugin(fhttp.ServerConfig{Port: 3000})
oapiPlugin := openapi.NewPlugin(openapi.PluginOptions{})

// Register the plugin — serves the spec at /_/openapi.json
oapiPlugin.RegisterOn(server)

a := app.New("my-service")
a.Module.Use(server)
a.Module.Use(oapiPlugin)

The spec is served at GET /_/openapi.json.

Documenting endpoints

Register endpoints with the plugin using the endpoint builder:

import "reflect"

type CreateUserBody struct {
    Name  string `json:"name" validate:"required,minlen=2" description:"User's display name"`
    Email string `json:"email" validate:"required,email" description:"Email address"`
}

type UserResponse struct {
    ID    string `json:"id" description:"User ID"`
    Name  string `json:"name" description:"User's display name"`
    Email string `json:"email" description:"Email address"`
}

type UserParams struct {
    ID string `json:"id" validate:"required,uuid" description:"User ID"`
}

// Register endpoint with OpenAPI metadata
oapiPlugin.AddRoute(openapi.DiscoveredRoute{
    Method:      "POST",
    Path:        "/users",
    Description: "Create a new user",
    Tags:        []string{"Users"},
    Body:        reflect.TypeOf(CreateUserBody{}),
    Returns:     reflect.TypeOf(UserResponse{}),
    Throws: []openapi.ThrowsMeta{
        {Status: 409, Description: "Email already exists"},
        {Status: 422, Description: "Validation failed"},
    },
})

oapiPlugin.AddRoute(openapi.DiscoveredRoute{
    Method:      "GET",
    Path:        "/users/{id}",
    Description: "Get a user by ID",
    Tags:        []string{"Users"},
    Params:      reflect.TypeOf(UserParams{}),
    Returns:     reflect.TypeOf(UserResponse{}),
    Throws: []openapi.ThrowsMeta{
        {Status: 404, Description: "User not found"},
    },
})

DiscoveredRoute fields

Field Type Description
Method string HTTP method
Path string Route path (supports {param} and [param])
Description string Operation description
Tags []string Grouping tags
Params reflect.Type Path parameters struct type
Query reflect.Type Query parameters struct type
Body reflect.Type Request body struct type
Returns reflect.Type Success response struct type
Throws []ThrowsMeta Possible error responses
Security *SecurityMeta Security requirements

Schema generation

The plugin generates OpenAPI schemas by introspecting Go struct types using reflection and struct tags.

Type mapping

Go type OpenAPI type Format
string string —
int, int64 integer int64
float32 number float
float64 number double
bool boolean —
[]T array —
struct object —

Struct tags for OpenAPI

Tag OpenAPI mapping Example
json:"name" Property name json:"email"
description:"text" Field description description:"User email"
validate:"required" Added to required array validate:"required"
validate:"uuid" format: "uuid" validate:"uuid"
validate:"email" format: "email" validate:"email"

Example schema

type Product struct {
    ID          string   `json:"id" validate:"required,uuid" description:"Product ID"`
    Name        string   `json:"name" validate:"required" description:"Product name"`
    Price       float64  `json:"price" validate:"required,min=0" description:"Price in USD"`
    Tags        []string `json:"tags" description:"Product tags"`
    Description string   `json:"description" description:"Product description"`
}

Generates:

{
  "type": "object",
  "required": ["id", "name", "price"],
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid",
      "description": "Product ID"
    },
    "name": {
      "type": "string",
      "description": "Product name"
    },
    "price": {
      "type": "number",
      "format": "double",
      "minimum": 0,
      "description": "Price in USD"
    },
    "tags": {
      "type": "array",
      "items": { "type": "string" },
      "description": "Product tags"
    },
    "description": {
      "type": "string",
      "description": "Product description"
    }
  }
}

Programmatic access

Generate the spec without the plugin:

doc := openapi.GenerateSpec(routes, openapi.Options{
    Title:   "My API",
    Version: "1.0.0",
})

specJSON, err := doc.JSON()

Security metadata

Document security requirements:

oapiPlugin.AddRoute(openapi.DiscoveredRoute{
    Method:      "DELETE",
    Path:        "/users/{id}",
    Description: "Delete a user",
    Security: &openapi.SecurityMeta{
        Roles:  []string{"admin"},
        Scopes: []string{"write"},
    },
})

Related guides

  • HTTP & Middleware — endpoint registration
  • Validation — validation tags
  • Security — authorization

On this page

  • OpenAPI
  • Plugin setup
  • Documenting endpoints
  • DiscoveredRoute fields
  • Schema generation
  • Type mapping
  • Struct tags for OpenAPI
  • Example schema
  • Programmatic access
  • Security metadata
  • Related guides