freshcrate
Home > Frameworks > go-apispec

go-apispec

Generate OpenAPI 3.1 specs from Go source code via static analysis — zero annotations, automatic framework detection

Description

Generate OpenAPI 3.1 specs from Go source code via static analysis — zero annotations, automatic framework detection

README

go-apispec: Generate OpenAPI from Go code

CI Release Coverage Go VersionLicense Go Reference

go-apispec analyzes your Go source code and generates an OpenAPI 3.1 spec (YAML or JSON). Point it at your module — it detects the framework, follows the call graph from routes to handlers, and infers request/response types from real code.

Quick Start

# Install
go install github.com/antst/go-apispec/cmd/apispec@latest

# Generate (auto-detects framework)
apispec --dir ./your-project --output openapi.yaml

That's it. The tool detects your framework, finds all routes, resolves handler types, and writes the spec.

Features

Framework Support

Framework Routes Params Request Body Responses Mounting/Groups
Chi Full chi.URLParam, render pkg json.Decode, render.DecodeJSON json.Encode, render.JSON, w.Write Mount, Group
Gin Full c.Param, c.Query ShouldBindJSON, BindJSON c.JSON, c.String, c.Data Group
Echo Full c.Param, c.QueryParam c.Bind c.JSON, c.String, c.Blob Group
Fiber Full c.Params, c.Query c.BodyParser c.JSON, c.Status().JSON Mount, Group
Gorilla Mux Full Path template {id} json.Decode json.Encode, w.Write PathPrefix, Subrouter
net/http Basic Path template json.Decode json.Encode, w.Write, http.Error Nested ServeMux

Projects using multiple frameworks simultaneously are fully supported — all routes from all detected frameworks appear in the spec.

All frameworks also detect fmt.Fprintf, io.Copy, and io.WriteString as response writes.

Analysis Capabilities

Response Detection

  • Content-Type inference from w.Header().Set("Content-Type", "image/png")
  • WriteHeader(201) + json.Encode(user) merged into a single 201 response with schema
  • Status code variable resolution: status := http.StatusCreated; w.WriteHeader(status) → 201
  • Cross-function status codes: w.WriteHeader(getStatus()) where getStatus() returns a constant
  • Multiple response types for the same status code → oneOf schema
  • []byte responses → type: string, format: binary
  • Bodyless status codes (1xx, 204, 304) never get body schemas (per RFC 7231)
  • Implicit 200 for handlers that write a body without explicit WriteHeader

Type Resolution

  • Generic struct instantiation: APIResponse[User] → schema with Data: $ref User
  • Interface resolution: handlers registered via interface → concrete implementation schemas
  • Conditional HTTP methods via CFG: switch r.Method { case "GET": ... case "POST": ... } → separate operations
  • Route path variables: path := "/users"; r.GET(path, h) → resolves variable to literal path
  • Decode receiver tracing: json.NewDecoder(file).Decode(&cfg) not misclassified as request body
  • io.Copy source tracing: io.Copy(w, strings.NewReader(...))type: string; io.Copy(w, file)format: binary

Schema Inference

  • Required fields from json:",omitempty" absence and binding:"required" tags
  • Validator dive tag: validate:"dive,email" on []string → items schema has format: email
  • Mux.Vars() map index expressions → path parameter names
  • Type mappings for time.Time, uuid.UUID, and custom types

Output Quality

  • Deterministic YAML/JSON — sorted map keys, identical output across runs, safe for CI diffing
  • Short names by default — DocumentHandler.GetContent instead of github.com/org/.../http.Deps.DocumentHandler.GetContent
  • Config merging — --config extends auto-detected framework defaults instead of replacing them

Call Graph Visualization

Interactive Cytoscape.js diagrams with:

  • Hierarchical tree layout with zoom, pan, and click-to-highlight
  • CFG branch coloring: green (if-then), red dashed (if-else), purple (switch-case)
  • Branch labels showing case values (e.g., "GET", "POST")
  • Paginated mode for large graphs (1000+ edges)
  • PNG/SVG export
apispec --dir ./my-project --output openapi.yaml --diagram diagram.html

Usage

# Basic generation
apispec --dir ./my-project --output openapi.yaml

# With custom config
apispec --dir ./my-project --config apispec.yaml --output openapi.yaml

# Legacy naming (fully-qualified operationIds and schema names)
apispec --dir ./my-project --output openapi.yaml --short-names=false

# With call graph diagram
apispec --dir ./my-project --output openapi.yaml --diagram diagram.html

# Skip CGO packages
apispec --dir ./my-project --output openapi.yaml --skip-cgo

# Tune limits for large codebases
apispec --dir ./my-project --output openapi.yaml --max-nodes 100000 --max-recursion-depth 15

# Performance profiling
apispec --dir ./my-project --output openapi.yaml --cpu-profile --mem-profile

Key Flags

Flag Short Default Description
--output -o openapi.json Output file (.yaml/.json)
--dir -d . Project directory
--config -c Custom YAML config
--short-names true Strip module paths from names
--diagram -g Save call graph as HTML
--title -t Generated API API title
--api-version -v 1.0.0 API version
--skip-cgo true Skip CGO packages
--max-nodes -mn 50000 Max call graph nodes
--max-recursion-depth -mrd 10 Max recursion depth
--verbose -vb false Verbose output

Full flag list: apispec --help

Programmatic Usage

import (
    "os"
    "github.com/antst/go-apispec/generator"
    "github.com/antst/go-apispec/spec"
    "gopkg.in/yaml.v3"
)

func main() {
    cfg := spec.DefaultChiConfig() // or DefaultGinConfig, DefaultEchoConfig, etc.
    gen := generator.NewGenerator(cfg)
    openapi, err := gen.GenerateFromDirectory("./your-project")
    if err != nil { panic(err) }
    data, _ := yaml.Marshal(openapi)
    os.WriteFile("openapi.yaml", data, 0644)
}

Configuration

Auto-detection works for most projects. For custom behavior, create apispec.yaml:

info:
  title: My API
  version: 2.0.0

shortNames: true  # false for legacy fully-qualified names

framework:
  routePatterns:
    - callRegex: ^(?i)(GET|POST|PUT|DELETE|PATCH)$
      recvTypeRegex: ^github\.com/gin-gonic/gin\.\*(Engine|RouterGroup)$
      handlerArgIndex: 1
      methodFromCall: true
      pathFromArg: true
      handlerFromArg: true

typeMapping:
  - goType: time.Time
    openapiType: { type: string, format: date-time }
  - goType: uuid.UUID
    openapiType: { type: string, format: uuid }

externalTypes:
  - name: github.com/gin-gonic/gin.H
    openapiType: { type: object, additionalProperties: true }

Full configuration examples for each framework: see the Default*Config() functions in internal/spec/config.go for the built-in patterns.

How It Works

Go source → Package loading & type-checking → Framework detection
  → AST traversal → Call graph + CFG construction → Pattern matching
  → OpenAPI mapping → YAML/JSON output
  1. Loads and type-checks all Go packages in the module
  2. Detects web frameworks from imports (supports multiple frameworks simultaneously)
  3. Builds a call graph from router registrations to handlers
  4. Builds a control-flow graph (CFG) via golang.org/x/tools/go/cfg for branch analysis
  5. Matches route, request, response, and parameter patterns against the call graph
  6. Resolves conditional methods, generic types, and interface implementations using CFG and type analysis
  7. Maps Go types to OpenAPI schemas (structs, enums, aliases, generics, validators)
  8. Serializes with sorted keys for deterministic output

Known Limitations

These are inherent to static analysis — analyzing code without executing it:

Limitation Example What Happens
Reflection-based routing Routes registered via reflect.Value.Call Not visible in static analysis
Computed paths r.GET("/api/" + version, handler) String concatenation not evaluated; only literal and variable-assigned paths resolved
Complex cross-function values func compute() int { return a + b }; WriteHeader(compute()) Only functions with a single constant return are resolved; computed values are not traced

Interactive Diagram Server

# Build and start
go build -o apidiag ./cmd/apidiag
./apidiag --dir ./my-project --port 8080
# Open http://localhost:8080

Provides a web UI for exploring call graphs with filtering, pagination, and export. See cmd/apidiag/README.md.

Development

# Build
make build

# Test
make test

# Lint
make lint

# Coverage
make coverage

# Update golden files after intentional output changes
# (generates to temp, shows diff, requires confirmation)
go test ./internal/engine/ -run TestUpdateGolden -v

Project Structure

go-apispec/
├── cmd/apispec/          CLI entry point
├── cmd/apidiag/          Interactive diagram server
├── generator/            High-level generator API
├── spec/                 Public types (re-exports from internal/spec)
├── internal/
│   ├── core/             Framework detection
│   ├── engine/           Generation engine
│   ├── metadata/         AST analysis and metadata extraction
│   └── spec/             OpenAPI mapping, patterns, schemas
├── pkg/patterns/         Gitignore-style pattern matching
└── testdata/             Framework fixtures + golden files

Golden File Tests

Every testdata/ directory has expected_openapi.json (short names) and expected_openapi_legacy.json (fully-qualified). Golden files use relative paths only — no machine-specific absolute paths.

One code path: generateGoldenSpec() is the single function used by both comparison and generation. Tests never overwrite golden files.

# Compare (runs in CI — fails on mismatch):
go test ./internal/engine/ -run TestGolden -v

# Update after intentional changes (explicit, never automatic):
go test ./internal/engine/ -run TestUpdateGolden -v

Contributing

See CONTRIBUTING.md. In short:

  1. Fork, branch, make changes
  2. Add tests (make test)
  3. Run lint (make lint)
  4. Open a PR

License

Apache License 2.0 — see LICENSE.

Originally forked from apispec by Ehab Terra.

Release History

VersionChangesUrgencyDate
v0.4.7## What's Changed * feat: extract Go doc comments as OpenAPI summary/description by @antst in https://github.com/antst/go-apispec/pull/14 **Full Changelog**: https://github.com/antst/go-apispec/compare/v0.4.6...v0.4.7High4/14/2026
v0.4.6## What's Changed * fix: isLikelyMediaType rejects Go module paths with multiple slashes by @antst in https://github.com/antst/go-apispec/pull/11 **Full Changelog**: https://github.com/antst/go-apispec/compare/v0.4.5...v0.4.6High4/8/2026

Dependencies & License Audit

Loading dependencies...

Similar Packages

goaDesign-first Go framework that generates API code, documentation, and clients. Define once in an elegant DSL, deploy as HTTP and gRPC services with zero drift between code and docs.v3.26.0
suricataType-safe AI agents for Go. Suricata combines LLM intelligence with Go’s strong typing, declarative YAML specs, and code generation to build safe, maintainable, and production-ready AI agents.0.0.0
go-orcaSelf-hosted AI workflow orchestration server. Runs multi-phase LLM pipelines (Director → Architect → Implementer → QA) and delivers structured artifacts via PR, webhook, or bundle.main@2026-04-21
maestroThe Maestro App Factory: a highly-opinionated multi-agent orchestration tool for app development that emulates the workflow of high-functioning human development teams using AI agentsv1.5.0
gorm-queryA strongly-typed query builder and generic repository libraryv1.0.1