API Documentation and OpenAPI/Swagger
A REST endpoint is only as useful as its documentation. OpenAPI (formerly known as Swagger) is the industry-standard, machine-readable format for describing an HTTP API — its paths, request/response shapes, authentication, and error codes — in a way that both humans and tools (client generators, testing tools, API gateways) can consume.
What OpenAPI Actually Is
An OpenAPI document is a YAML or JSON file describing your API's surface. At a high level, it has three main parts:
paths: every route and HTTP method your API exposes, with parameters, request bodies, and possible responses.components/schemas: reusable data shapes (aProduct, aUser, anError) referenced from multiple paths so you define each shape once.info,servers,security: metadata — API title and version, base URLs, and how authentication works (API key, bearer token, OAuth2).
A minimal excerpt:
openapi: 3.0.3
info:
title: Task API
version: "1.0.0"
paths:
/tasks/{id}:
get:
summary: Get a task by ID
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
"200":
description: The requested task
content:
application/json:
schema:
$ref: "#/components/schemas/Task"
"404":
description: Task not found
components:
schemas:
Task:
type: object
properties:
id:
type: integer
text:
type: string
done:
type: boolean
Once this document exists, Swagger UI (or an equivalent renderer) turns it into an interactive page where anyone can browse endpoints and try requests directly from the browser — without writing a single line of frontend code.
Spec-First vs Code-First
There are two ways to arrive at that YAML file:
- Spec-first: write the OpenAPI document by hand (or generate it from a design tool) before writing any code, then implement handlers that match it. This keeps the contract stable and lets frontend and backend teams work in parallel from an agreed shape.
- Code-first: write Go handlers first, and annotate them with comments that a tool scans to generate the OpenAPI document automatically.
swaggo/swagis the standard tool for this in the Go ecosystem.
Neither approach is universally "correct" — spec-first suits teams that value the contract as the source of truth; code-first suits smaller teams that want documentation to stay physically next to the code it describes.
Code-First with swaggo/swag
go install github.com/swaggo/swag/cmd/swag@latest
go get github.com/swaggo/http-swagger
Annotate a handler with structured comments directly above its function:
// GetTask godoc
// @Summary Get a task by ID
// @Description Returns a single task
// @Tags tasks
// @Produce json
// @Param id path int true "Task ID"
// @Success 200 {object} Task
// @Failure 404 {object} ErrorResponse
// @Router /tasks/{id} [get]
func (a *API) getTask(w http.ResponseWriter, r *http.Request) {
// ... handler body as in chapter 4 ...
}
Running swag init scans the codebase for these @-prefixed annotations and generates a docs/ package containing the OpenAPI JSON/YAML plus a Go file the router can serve. Wiring the generated docs into your net/http mux:
import httpSwagger "github.com/swaggo/http-swagger"
mux.Handle("/swagger/", httpSwagger.WrapHandler)
Visiting /swagger/index.html now shows a live, browsable, testable version of the API — regenerated every time you rerun swag init after changing annotations.
Real-World Example: What Swagger UI Shows
Given the annotation above, Swagger UI renders an entry like:
GET /tasks/{id}
Parameters: id (integer, required, in path)
Responses:
200 - Task { id, text, done }
404 - ErrorResponse { error }
[Try it out]
Clicking "Try it out," entering an ID, and executing sends a real GET /tasks/3 to your running server and displays the actual response — useful for manual QA and for onboarding a new engineer who needs to understand the API's shape quickly.
Validating Requests Against the Spec
Beyond display, an OpenAPI document is machine-checkable: request validation middleware can reject a payload that doesn't match the schema before your handler ever sees it, using a library like github.com/getkin/kin-openapi to load the spec and check incoming requests against it:
import (
"log"
"net/http"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/openapi3filter"
"github.com/getkin/kin-openapi/routers"
legacyrouter "github.com/getkin/kin-openapi/routers/legacy"
)
var apiRouter routers.Router
func init() {
loader := openapi3.NewLoader()
doc, err := loader.LoadFromFile("openapi.yaml")
if err != nil {
log.Fatal(err)
}
apiRouter, err = legacyrouter.NewRouter(doc)
if err != nil {
log.Fatal(err)
}
}
func validationMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
route, pathParams, err := apiRouter.FindRoute(r)
if err == nil {
reqValidationInput := &openapi3filter.RequestValidationInput{
Request: r, PathParams: pathParams, Route: route,
}
err := openapi3filter.ValidateRequest(
r.Context(), reqValidationInput,
)
if err != nil {
http.Error(
w, "request does not match API schema",
http.StatusBadRequest,
)
return
}
}
next.ServeHTTP(w, r)
})
}
This turns the spec from documentation-only into an enforced contract — a client sending an extra required field or the wrong type gets a 400 immediately, with no handler code needed to check for it.
Keeping Documentation Honest
Documentation that drifts from the actual behavior of the API is worse than no documentation — it actively misleads. A few practices keep it trustworthy:
- Generate from code or contract, don't hand-maintain a separate document. Whichever direction you chose (spec-first or code-first), let one artifact be the single source of truth and derive the other from it.
- Validate the spec in CI. Tools like
swag fmt(for annotation formatting) or an OpenAPI linter can catch a broken or stale spec before it merges. - Document error responses, not just the happy path. A client integrating against your API needs to know what a
400or404body looks like just as much as what a200looks like. - Version the spec alongside the API itself — when you introduce
/v2/tasks, publish av2OpenAPI document rather than silently mutating thev1one (versioning is covered in depth in a later chapter).
Documentation that isn't generated from the same source as your handlers will eventually lie — treat drift between the two as a bug, not a documentation nitpick.
Frequently Asked Questions
I ran swag init but /swagger/index.html still shows the old response shape — what did I miss?
The generated docs/ package only reflects the state of your @-prefixed annotations as of the last time swag init ran, so any handler comment you've edited since then hasn't been picked up yet. Rerun swag init after every annotation change, and treat forgetting to do so as exactly the kind of spec-drift the chapter warns about.
Should I write the OpenAPI YAML by hand or use swaggo/swag to generate it from comments?
It depends on which team is the source of truth. Spec-first keeps the contract stable and lets frontend and backend build in parallel from an agreed shape, while code-first with swaggo/swag keeps the documentation physically next to the handler it describes, which is usually the more practical default for a smaller team.
Does Swagger UI's "Try it out" button send a fake request, or does it actually hit my server?
It's a real request — clicking "Try it out" and executing sends an honest GET /tasks/3 (or whatever the entry describes) to your running server and shows the actual response body, not a canned example. That's what makes it useful for manual QA and for a new engineer exploring the API's real behavior.
Once I have an OpenAPI document, is validating requests against it optional, or does it happen automatically?
It's entirely opt-in — the spec is just documentation until you wire up something like github.com/getkin/kin-openapi as middleware to actually check incoming requests against it. Without that middleware, a client can send an extra field or the wrong type and your handler will still see it; the middleware shown in this chapter is what turns the spec into an enforced 400-on-mismatch contract.
If I add a /v2/tasks endpoint, do I edit the existing OpenAPI document in place?
No — publish a separate v2 OpenAPI document rather than mutating the v1 one, the same way the API itself should expose /v2/tasks as a new route rather than silently changing /tasks's behavior. Versioning the spec alongside the API keeps clients still on v1 from being blindsided by a document that no longer matches what they're calling.
With the API surface now discoverable and documented, the next chapter turns to proving it actually behaves as documented: testing and mocking APIs in Go.