Skip to content

Repository files navigation

Go OpenAPI v3 Server Code Generator

A powerful, modern code generator that creates clean server boilerplate from OpenAPI v3 specifications. Built with Go 1.22+ and the latest dependencies, featuring enhanced error handling and extensive customization options.

Table of Contents

Quick Start

Prerequisites

  • Go 1.22+ (updated for latest language features)
  • Valid OpenAPI 3.0+ specification file
  • Basic understanding of Go modules

Installation

go install github.com/mikekonan/go-oas3@latest

Basic Usage

# Generate from local file
go-oas3 -swagger-addr swagger.yaml -package myapi -path ./generated

# Generate from remote URL
go-oas3 -swagger-addr https://example.com/api/swagger.yaml -package myapi -path ./generated

Key Ideas

  • Request Parsing: Stubs handle all request parsing logic automatically
  • Type Safety: Response builders ensure you can only respond according to your specification
  • Validation: Built-in validation for all request parameters and bodies
  • Security: Automatic security middleware generation from OpenAPI security schemes

Note: Path stub generation relies on the first tag from your paths. While tags are not required, they are strongly recommended for better organization:

  • With tags: Creates separate service interfaces per tag (e.g., UserService, OrderService)
  • Without tags: All operations are grouped under a single DefaultService interface

Example with tags:

paths:
  /users:
    get:
      tags: [users]  # Creates UserService interface
  /orders:
    post:
      tags: [orders]  # Creates OrderService interface

Usage

Command Line Arguments

Flag Type Description Default
-swagger-addr string Path or URL to OpenAPI specification swagger.yaml
-package string Required. Go package name for generated code -
-path string Required. Output directory for generated files -
-componentsPackage string Package name for components (if different from main) Same as -package
-componentsPath string Path for components (if different from main) Same as -path
-authorization string Headers for remote swagger files (key1:value1,key2:value2) -
-prioritize-x-go-type bool Prioritize x-go-type over schema properties false
-pass-raw-request bool Pass raw HTTP request to handler functions false
-default-generics bool Generate every operation in generic mode by default (see Generic Response Mode); can be overridden per-operation with x-go-generics. false

Examples

# Basic local generation
go-oas3 -swagger-addr swagger.yaml -package api -path ./generated

# Remote file with custom components
go-oas3 \
  -swagger-addr https://petstore3.swagger.io/api/v3/openapi.json \
  -package petstore \
  -path ./api \
  -componentsPackage models \
  -componentsPath ./api/models

# With authorization headers
go-oas3 \
  -swagger-addr https://api.example.com/swagger.yaml \
  -package myapi \
  -path ./generated \
  -authorization "X-API-Key:secret,Authorization:Bearer token"

Complete Workflow Example

Here's a complete example from an OpenAPI spec to a running server:

1. Create OpenAPI Specification (api.yaml)

openapi: 3.0.0
info:
  title: User API
  version: 1.0.0
paths:
  /users/{id}:
    get:
      tags: [users]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            x-go-type: github.com/google/uuid.UUID
      responses:
        '200':
          description: User found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '404':
          description: User not found
components:
  schemas:
    User:
      type: object
      required: [id, email]
      properties:
        id:
          type: string
          format: uuid
        email:
          type: string
          format: email
        name:
          type: string
          x-go-omitempty: true

2. Generate Code

go-oas3 -swagger-addr api.yaml -package userapi -path ./generated

3. Implement Handlers

package main

import (
    "context"
    "net/http"
    "github.com/go-chi/chi/v5"
    userapi "./generated"
)

type UsersService struct{}

func (s *UsersService) GetUsersId(ctx context.Context, request userapi.GetUsersIdRequestObject) userapi.GetUsersIdResponseObject {
    // Your business logic here
    user := userapi.User{
        Id:    request.Id,
        Email: "user@example.com",
        Name:  "John Doe",
    }
    
    return userapi.GetUsersId200JSONResponse{
        Body: user,
    }
}

func main() {
    service := &UsersService{}
    
    r := chi.NewRouter()
    userapi.HandlerFromMux(service, r)
    
    http.ListenAndServe(":8080", r)
}

4. Set Up Your Project

# Initialize Go module
go mod init myproject

# Add dependencies and run
go mod tidy
go run main.go

5. Test Your API

# Test the endpoint
curl http://localhost:8080/users/550e8400-e29b-41d4-a716-446655440000

# Response:
# {
#   "id": "550e8400-e29b-41d4-a716-446655440000",
#   "email": "user@example.com", 
#   "name": "John Doe"
# }

The generated boilerplate includes:

  • Type-safe handlers - Request/response objects with proper Go types
  • Automatic validation - Built-in validation for all parameters and request bodies
  • Router integration - Ready-to-use chi router setup
  • Error handling - Structured error responses matching your OpenAPI spec

OpenAPI Features

Required Fields

Required fields in path, query, headers, and components are supported.

Security

Security schemes for HTTP and API key (header/cookie) are supported.

Cookie

Response header Set-Cookie is supported. Cookies in requests are supported via security schemes only.

Validation

Type validation supports the following data types:

  • string: minLength, maxLength
  • number, integer: minimum, maximum, exclusiveMinimum, exclusiveMaximum

Custom Types

The generator supports several OpenAPI types for components:

OpenAPI Type Go Type
uuid github.com/google/uuid.UUID
iso4217-currency-code github.com/mikekonan/go-types/v2/currency.Code
iso3166-alpha-2 github.com/mikekonan/go-types/v2/country.Alpha2Code
iso3166-alpha-3 github.com/mikekonan/go-types/v2/country.Alpha3Code

Extensions Reference

The generator supports powerful OpenAPI extensions to customize Go code generation:

Core Type Extensions

x-go-type - Custom Go Types

Specify custom Go types for any schema:

# Use encoding/json.RawMessage for flexible JSON
metadata:
  type: object
  x-go-type: encoding/json.RawMessage

# Use third-party types
user_id:
  type: string
  x-go-type: github.com/google/uuid.UUID

# Use custom domain types
amount:
  type: string
  x-go-type: github.com/shopspring/decimal.Decimal

x-go-type-string-parse - Custom Parsing

For string parameters that need custom parsing:

# Custom UUID parsing from string
user_id:
  type: string
  x-go-type: github.com/google/uuid.UUID
  x-go-type-string-parse: github.com/google/uuid.Parse

# Custom time parsing
created_at:
  type: string
  x-go-type: time.Time
  x-go-type-string-parse: github.com/spf13/cast.ToTimeE

Field Modifiers

x-go-pointer - Force Pointer Types

# Make field a pointer (useful for optional fields)
optional_amount:
  type: integer
  x-go-pointer: true
  # Generates: OptionalAmount *int `json:"optional_amount,omitempty"`

x-go-omitempty - JSON Omitempty Tag

# Add omitempty to JSON tag
description:
  type: string
  x-go-omitempty: true
  # Generates: Description string `json:"description,omitempty"`

x-go-string-trimmable - Auto-trim Strings

# Automatically trim whitespace before validation
title:
  type: string
  minLength: 1
  x-go-string-trimmable: true
  # Trims spaces before checking minLength

Validation Extensions

x-go-regex - Regex Validation

# Add regex validation to parameters
phone:
  type: string
  x-go-regex: ^\+?[1-9]\d{1,14}$
  # Generates validation code with regexp.MustCompile

x-go-skip-validation - Disable Validation

# Skip validation for performance-critical paths
large_payload:
  type: object
  properties:
    data: 
      type: string
  x-go-skip-validation: true

Generic Mode Extension

x-go-generics - Per-Operation Generic Response Mode

Opt an operation into generic-mode generation. The response builder cascade (20+ types per operation) is replaced by a single shared *Response[B]. See Generic Response Mode for the full picture.

paths:
  /widgets:
    post:
      x-go-generics: true
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Widget'

Use -default-generics on the CLI to flip the default for the whole spec; then x-go-generics: false can opt individual operations back to classic.

Security Extensions

x-go-skip-security-check - Skip Security Validation

# For operations that parse auth but don't enforce it
paths:
  /health:
    get:
      x-go-skip-security-check: true
      security:
        - ApiKeyAuth: []
      # Parses auth header but doesn't fail on missing/invalid auth

Advanced Map Types

x-go-map-type - Custom Map Types

# Custom map with typed keys/values
metadata:
  type: object
  additionalProperties:
    type: string
  x-go-map-type: map[github.com/google/uuid.UUID]string
  # Generates: map[uuid.UUID]string instead of map[string]string

Complete Extension Example

components:
  schemas:
    User:
      type: object
      required: [id, email]
      properties:
        id:
          type: string
          x-go-type: github.com/google/uuid.UUID
        email:
          type: string
          format: email
          x-go-regex: ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
        profile:
          type: object
          x-go-pointer: true
          x-go-omitempty: true
          properties:
            bio:
              type: string
              x-go-string-trimmable: true
        metadata:
          type: object
          additionalProperties:
            type: string
          x-go-type: map[string]interface{}
      x-go-skip-validation: false

  parameters:
    UserID:
      name: user_id
      in: path
      required: true
      schema:
        type: string
        x-go-type: github.com/google/uuid.UUID
        x-go-type-string-parse: github.com/google/uuid.Parse

Generic Response Mode

By default the generator emits a dedicated response-builder cascade for every operation: XxxResponseBuilder, XxxStatusCodeBuilder, Xxx200ContentTypeBuilder, Xxx200ApplicationJsonResponseBuilder, plus their methods. For a spec with several status codes per endpoint that's ~20 types and ~280 lines of generated code per operation.

Generic mode collapses this into a single shared *Response[B] type per package. Opt in by marking the operation with x-go-generics: true (or flip the global default via -default-generics).

What changes

  • The service-interface return type becomes *Response[BodyT] where BodyT is inferred from the 2xx response's application/json schema (any if no body is declared).
  • The per-operation builder cascade is not emitted.
  • The request struct embeds RequestMeta (shared ProcessingResult + SecurityCheckResults) and gains a GetHeader() any accessor, so it satisfies the package-level Request interface.

Generated helpers in every package

When at least one operation uses x-go-generics, the generator emits these one-time symbols:

// Implemented by every generic request — application helpers can accept any
// generic request as a single parameter.
type Request interface {
    GetProcessingResult() RequestProcessingResult
    GetSecurityCheckResults() map[SecurityScheme]string
    GetHeader() any
}

type RequestMeta struct {
    ProcessingResult     RequestProcessingResult
    SecurityCheckResults map[SecurityScheme]string
}

// Single shared response type — replaces the per-operation cascade.
type Response[B any] struct { /* ... */ }
type ResponseBuilder[B any] struct { /* ... */ }

func NewResponse[B any]() *ResponseBuilder[B]

// Fluent setters on ResponseBuilder[B]:
//   .Status(code int)
//   .ContentType(ct string)          // default "application/json"
//   .ApplicationJson()
//   .ApplicationXml()
//   .ApplicationOctetStream()
//   .TextHtml() / .TextPlain() / .TextCsv()
//   .Body(B)                          // typed success body
//   .BodyAny(any)                     // error body of a different type
//   .BodyRaw([]byte)
//   .Header(key, value string)
//   .Cookie(c http.Cookie)
//   .Redirect(url string)
//   .Build() *Response[B]

Other shared helpers (emitted for all modes)

These were extracted to keep per-operation code small regardless of generic mode:

  • respond(w, r, hooks, name, response, hasContent) — single shared HTTP writer. Per-operation handlers become 4 lines.
  • ensureHooks(*Hooks) *Hooks — fills every nil hook with a no-op stub, so generated code calls hooks unconditionally.
  • extractSecurity(r, hooks, name, requirements, skipCheck) — runs the OR-of-AND security matrix; replaces ~80 inlined lines per operation.

Example

paths:
  /widgets:
    post:
      tags: [widgets]
      x-go-generics: true
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateWidgetRequest'
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Widget'
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorBody'

Service implementation:

func (s *widgetsService) PostWidgets(ctx context.Context, req PostWidgetsRequest) *Response[Widget] {
    if err := req.ProcessingResult.Err(); err != nil {
        return NewResponse[Widget]().Status(400).
            BodyAny(ErrorBody{Code: "bad_request", Message: err.Error()}).
            Build()
    }
    return NewResponse[Widget]().Status(200).
        Body(Widget{ID: "w-1", Name: req.Body.Name}).
        Build()
}

Coexistence with classic mode

Generic and classic operations live side-by-side in the same generated package — only operations marked with x-go-generics: true (or covered by -default-generics) use the new shape. Migration can be done one operation at a time without touching the rest of the codebase.

Limitations

Known Limitations

Inline Schema Responses

The generator currently has limited support for inline schemas in responses. For example:

# ❌ Not fully supported - may cause compilation issues
responses:
  '200':
    content:
      application/json:
        schema:
          type: object
          properties:
            message: 
              type: string

# ✅ Recommended - use $ref to components
responses:
  '200':
    content:
      application/json:
        schema:
          $ref: '#/components/schemas/SuccessResponse'

Workaround: Define all response schemas in components/schemas and reference them using $ref.

Anonymous Types

  • Anonymous slice elements and map entries have limited support
  • Complex nested anonymous types may not generate correctly

Workaround: Define explicit component schemas for complex types.

Best Practices

  1. Always use $ref for schemas - Avoid inline type definitions
  2. Define reusable components - Create schemas in components/schemas
  3. Use meaningful names - Component names become Go type names
  4. Test generated code - Always compile and test after generation

Questions or Feature Requests

Have a question or need some functionality? Feel free to open an issue or submit a pull request.

Contributing

Go OpenAPI v3 server code generator uses github.com/dave/jennifer for code generation. Using github.com/aloder/tojen is the suggested way to generate Jennifer code.

Releases

Used by

Contributors

Languages