Go is a wonderful language for building web apps. By early 2015 the ecosystem had everything you could want — fast HTTP servers, a dozen good routers, helper libraries for days. Everything, that is, except a decent way to document your APIs. And let's be honest: nobody has ever woken up excited to write API documentation.
So at GopherGala 2015 — a 48-hour global Go hackathon — a few of us at Betacraft decided to fix the part everyone quietly hated. We built what was, as far as we could tell, the first API doc generator for Go web apps, and — true to the genre of "yet another <thing>" — called it YAAG: Yet Another API doc Generator.
Why another doc tool?
The tools that existed at the time all asked for the same tax up front. Swagger wanted you to maintain a separate spec, or sprinkle your handlers with special annotation comments and keep them in sync by hand. Other generators wanted a DSL, or a config file, or a build step. Every one of them added a second source of truth that drifted away from the code the moment you shipped a change.
Most web services expose their APIs to mobile teams or third-party developers, and keeping that contract documented is genuinely painful. We weren't trying to win the public-facing, beautifully-themed-portal game. We wanted the 80% case: the in-house docs your own team needs, generated for free, always matching reality, with zero lines of comments or annotations.
The trick: documentation as a side effect
The core insight is that you already exercise your API constantly — from Postman, from curl, from the frontend, and especially from your test suite. Every one of those calls is a perfectly good, real example of a request and its response. Why describe the API by hand when it's busy describing itself?
YAAG is middleware. You wrap your handlers (or register a filter) and then just
use your API the way you already do. YAAG sits in the request path, observes the
real request — method, path, params, headers, body — and the real response, and
continuously writes out a clean, Bootstrap-based apidoc.html
grouped by endpoint. It also drops a JSON file capturing every call it saw, so
you can post-process the data however you like. Your documentation becomes a
side effect of running your own code.
Four lines to wire it up
With the standard net/http package, adding YAAG is about as small
as it gets — initialize it once, then wrap your handler:
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:])
}
func main() {
yaag.Init(&yaag.Config{
On: true,
DocTitle: "Core",
DocPath: "apidoc.html",
BaseUrls: map[string]string{"Production": "", "Staging": ""},
})
http.HandleFunc("/", middleware.HandleFunc(handler))
http.ListenAndServe(":8080", nil)
}
That Config is the whole control surface. On is the
master switch — flip it off and YAAG gets out of the way entirely.
DocTitle and DocPath name and place the generated
file, and BaseUrls lets you label environments (Production,
Staging, whatever) so the generated examples point at the right host.
It went wherever Go web apps went
A doc tool is only useful if it speaks your framework. So YAAG grew adapters, and kept growing them as the community contributed. With Gorilla Mux it's the same wrap-the-handler pattern:
func main() {
yaag.Init(&yaag.Config{On: true, DocTitle: "Gorilla Mux", DocPath: "apidoc.html"})
r := mux.NewRouter()
r.HandleFunc("/", middleware.HandleFunc(handler))
http.ListenAndServe(":8080", r)
}
And with Gin you register it as a piece of middleware once and forget about it:
func main() {
r := gin.Default()
yaag.Init(&yaag.Config{On: true, DocTitle: "Gin", DocPath: "apidoc.html"})
r.Use(yaag_gin.Document())
r.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "Hello World!")
})
r.Run(":8080")
}
By the end it covered the whole zoo of the era: plain
net/http, Gorilla Mux, Martini, Revel, Gin, Iris, and httprouter.
Whatever you were building with, YAAG could probably tag along.
The workflow that made it click
The combination we recommended — and used ourselves — was simple and a little
bit delightful: drive YAAG from your test suite. Write tests
that hit every handler (you wanted those tests anyway), run them, and let YAAG
produce apidoc.html from real, passing requests. Commit the
generated file alongside the code. Then turn the middleware
off in production so it never touches a live request.
The payoff: your docs can never silently rot, because they're regenerated from the same tests that gate your build. If an endpoint's shape changes and the tests still pass, the docs update to match. If the tests don't cover it, well — that's a different problem, and at least now it's visible.
See it in motion
We recorded a screencast back when we built it, walking through wiring YAAG in and watching the docs assemble themselves as the API gets called:
▶ Watch the YAAG screencast on YouTube
The afterlife
We built YAAG for ourselves, in a weekend, to scratch an in-house itch. People liked it more than we ever expected. Last I checked it's sitting at around 286 stars and 57 forks, with contributions that came in from well beyond the original Betacraft team — it was a genuine little open-source project, kicked off by Akshay Deo, Brian Newsom, and me during that GopherGala weekend.
Not bad for something whose name is, fundamentally, a shrug. Sometimes the best tools are the ones you build because the alternative was writing documentation by hand.