
I built a tool that generates OpenAPI specs from Go code — no annotations needed
I've been working on go-apispec, a CLI tool that generates OpenAPI 3.1 specs from Go source code using static analysis. No // @Summary comments, no struct tags, no code changes. Just point it at your project:
go install github.com/antst/go-apispec/cmd/apispec@latest
apispec --dir ./your-project --output openapi.yaml
It detects your framework (Chi, Gin, Echo, Fiber, Gorilla Mux, net/http), builds a call graph from route registrations to handlers, and traces through your code to figure out what goes in and what comes out.
Concrete example. Given this handler:
func CreateUser(w http.ResponseWriter, r *http.Request) {
var user User
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(ErrorResponse{Error: "invalid body"})
return
}
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(user)
}
It produces:
/users:
post:
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/User'
responses:
"201":
description: Created
content:
application/json:
schema:
$ref: '#/components/schemas/User'
"400":
description: Bad Request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
Both status codes, both response types, the request body — all inferred from the code. Fields without omitempty are marked required in the schema.
Some of the harder problems it solves:
switch r.Method { case "GET": ... case "POST": ... }→ produces separate operations per method. This uses control-flow graph analysis viagolang.org/x/tools/go/cfgto understand which code runs under which branch.APIResponse[User]→ instantiates the generic struct with concrete types in the schema.- Handlers registered via interfaces → traces to the concrete implementation to find response types.
w.Header().Set("Content-Type", "image/png")→ that endpoint's response usesimage/png, not the defaultapplication/json.- Multiple frameworks in one project (e.g., Chi on :8080, Gin on :9090) → all routes from all frameworks appear in the spec.
What it can't do (static analysis limitations): reflection-based routing, computed string concatenation for paths ("/api/" + version), complex arithmetic across functions for status codes. These require runtime information.
Output is deterministic (sorted keys), so you can commit the spec and diff it in CI.
Background: This started as a fork of apispec by Ehab Terra, which provided the foundational architecture — the AST traversal approach, call graph construction, and the pattern-matching concept for framework detection. I've since rewritten most of the internals: type resolution pipeline, schema generation, CFG integration, generic/interface support, deterministic output, and the test infrastructure. But the original design shaped where this ended up, and I'm grateful for that starting point.
GitHub: https://github.com/antst/go-apispec
Try it: go install github.com/antst/go-apispec/cmd/apispec@latest && apispec --dir . --output openapi.yaml
Would love to hear if you try it on a real project — especially cases where it gets something wrong or misses a pattern. That's the most useful feedback.



