Overview
Go has no runtime annotations, so the ecosystem answers the doc-from-code vs code-from-doc question with the tool it always reaches for: code generation, in both directions. Contract-first is unusually natural here — oapi-codegen emits server interfaces for every router in the landscape, and the gRPC world's buf toolchain is the same idea perfected. Code-first exists in two generations: comment-driven (swag) and the newer types-driven frameworks (huma, fuego) that derive the spec from handler signatures. This note maps each flavor onto net/http + chi, Gin, Echo and Fiber.
Key points
- OpenAPI contract-first, the default move: oapi-codegen turns an OpenAPI 3 document
into server interfaces + typed clients with dedicated targets for stdlib
ServeMux, chi, Echo, Gin and Fiber — you implement the interface, drift is a compile error. ogen is the stricter, more opinionated alternative; goa goes further and makes a Go DSL the design source, generating code and spec from it. - OpenAPI code-first, comments generation: swag parses
// @Summary-style handler comments into a (Swagger 2) spec, with serving shims per framework — gin-swagger, echo-swagger, fiber-swagger, plainhttp.Handlerfor stdlib. Fine for retrofitting; the comments rot like all comments. - OpenAPI code-first, types generation: huma (adapters for stdlib/chi/Gin/Echo/Fiber) and fuego (net/http-native) derive OpenAPI 3.1 from typed request/response structs and validate requests against it — the correct-by-construction middle path.
- AsyncAPI is the thin spot: no dominant tool. asyncapi-codegen (lerenn) generates pub/sub bindings from an AsyncAPI 2/3 document (code-from-doc, NATS/Kafka); otherwise hand-author the spec, validate/render with the AsyncAPI CLI in CI, and keep payload contracts in a schema registry — watermill has no spec story of its own.
- gRPC: proto is the source, buf is the pipeline:
buf generate(protoc-gen-go + protoc-gen-go-grpc),buf lintandbuf breakingas CI gates, Buf Schema Registry for rendered docs. grpc-gateway + protoc-gen-openapiv2 serves REST and its OpenAPI document from the same.proto— one contract, two protocols. - GraphQL is schema-first by consensus: gqlgen generates resolver scaffolding from SDL — the schema file is simultaneously the design artifact and the docs, introspection and playground come free.
- Spec quality is a CI job: Spectral or vacuum for linting, oasdiff for
breaking-change gates on OpenAPI;
bufcovers proto natively.
Details
Flavor × router/framework matrix
| Flavor | net/http (+ chi) | Gin | Echo | Fiber |
|---|---|---|---|---|
| OpenAPI, contract-first | oapi-codegen std-http/chi-server targets; ogen (stdlib-based) |
oapi-codegen gin-server |
oapi-codegen echo-server |
oapi-codegen fiber-server |
| OpenAPI, code-first (comments) | swag + httpSwagger.Handler |
swag + gin-swagger | swag + echo-swagger | swag + fiber-swagger |
| OpenAPI, code-first (types) | huma (chi/stdlib adapters), fuego (native) | huma gin adapter | huma echo adapter | huma fiber adapter |
| AsyncAPI | Framework-independent: asyncapi-codegen bindings or hand-authored spec + AsyncAPI CLI — attaches to the messaging clients, not the router | ← same | ← same | ← same |
| gRPC docs | buf pipeline; grpc-gateway mounts as a plain http.Handler next to your mux |
grpc-gateway behind gin.WrapH |
grpc-gateway via echo.WrapHandler |
Awkward: fasthttp ≠ net/http — run the gateway on a separate listener |
| GraphQL | gqlgen serves a plain http.Handler |
gin.WrapH(srv) |
echo.WrapHandler(srv) |
fasthttp adaptor shim |
The pattern in the bottom rows is the stdlib-correspondence
story again: doc-serving tools ship as http.Handlers, so stdlib and chi mount them
natively, Gin and Echo wrap them, and Fiber pays its fasthttp tax with adaptors or a
second listener.
Choosing a direction in Go
The community's center of gravity: contract-first (oapi-codegen) when the API crosses a team or language boundary; types-first code-first (huma/fuego) for greenfield services that want the spec for free with validation included; swag only when retrofitting docs onto an existing service. If the service is internal and both ends are Go, skip the argument — gRPC with buf gives contract, codegen, docs and breaking-change gates in one toolchain, and grpc-gateway keeps a documented REST surface for everyone else.
Related
- Backend protocols in Go — the map — parent map; the protocols these documents describe.
- API documentation — the flavors and the code-first vs contract-first trade in the abstract.
- REST & HTTP APIs in Go — the OpenAPI workflows in their REST context, including the huma/fuego details.
- Messaging in Go — Kafka, NATS & AMQP — the channels an AsyncAPI document would describe.
- gRPC in Go — the buf toolchain and grpc-gateway in full.
- GraphQL in Go — gqlgen's schema-first codegen in detail.
- Go web frameworks — the landscape — the routers and frameworks this note maps onto.
- API documentation in Java and in Rust — the neighbours: annotation-shaped and derive-shaped where Go is codegen-shaped.
- Hexagonal architecture reference implementation — Go
— where these contract artifacts live in the house layout: the root
api/directory.