Skip to content

Day 110 — Project: OpenAPI Docs

Month 4 · Week 4 · ⬅ Day 109 · Day 111 ➡ · Journal index

🎯 Learning Objective

Describe the capstone API with an OpenAPI 3.0 document and serve interactive docs, so the API has a machine-readable contract clients and tools can consume.

📚 Topics

  • OpenAPI 3.0 object model: info, paths, operations, components/schemas, $ref
  • Generating the spec from code (swaggo) vs hand-writing YAML/JSON
  • Serving /openapi.json and rendering it with swagger-ui / Redoc
  • Contract-first vs code-first API design

📖 Reading / Sources

📝 Notes

  • An OpenAPI document is the machine-readable contract for a REST API: endpoints, methods, request/response schemas, status codes. It's just structured JSON/YAML → [[openapi]].
  • Core objects: info (title/version), paths (a map of URL → method → operation), each operation has responses keyed by status code, and reusable components/schemas referenced via $ref: '#/components/schemas/User' to avoid repetition → [[dry]].
  • Two workflows: code-first generates the spec from annotations/structs (swaggo) so docs can't drift from handlers; contract-first writes the spec first and generates server/client stubs from it. Both are valid; code-first is easiest to keep in sync on a small project.
  • Pin the version in info to your release (e.g. 0.4.0) so consumers know which contract they're reading.
  • Serving docs: expose /openapi.json and point a static swagger-ui or Redoc bundle at that URL; the UI fetches the JSON and renders an interactive explorer. Nothing Go-specific is required to render it.
  • Because the document is just JSON, you can model it as Go structs and json.MarshalIndent it — encoding/json sorts map keys, so the output is deterministic (good for committing/diffing) → [[encoding-json]].
  • Keep the spec in version control and assert key invariants in a test (every route documented, operationIds unique) so docs are part of the build, not an afterthought.

💻 Code Examples

// Code-first with swaggo: annotations on the handler generate the spec.
// @Summary  Create a user
// @Accept   json
// @Produce  json
// @Param    user  body      CreateUserRequest  true  "new user"
// @Success  201   {object}  User
// @Failure  409   {object}  ErrorResponse
// @Router   /users [post]
func (h *Handler) create(w http.ResponseWriter, r *http.Request) { /* ... */ }

Stdlib version — build the OpenAPI document as structs and serve /openapi.json + a docs page: examples/month-04/openapi/main.go · Run: go run ./examples/month-04/openapi

🏋️ Exercises / Practice

Exercise Status Link
Build a valid OpenAPI 3.0 doc from routes (deterministic JSON) exercises/month-04/week-4/openapi
Serve /openapi.json + /docs examples/month-04/openapi

🐛 Mistakes Made

  • Hand-wrote response schemas inline for every endpoint; refactored to a shared components/schemas/User referenced with $ref to stop duplication.
  • Generated spec drifted from the handlers after a field rename; added a test asserting the documented fields match the struct tags so it can't silently rot.

❓ Open Questions

  • Worth adopting contract-first (write the YAML, generate handlers) for the next project, or stick with swaggo code-first? Depends on whether multiple teams consume the API.

🧠 Active Recall (answer without looking)

  1. Q: What does $ref: '#/components/schemas/User' accomplish in an OpenAPI doc?
A It references a schema defined once under `components/schemas`, so the `User` shape is declared in one place and reused across many operations — DRY for the contract.
  1. Q: Why does marshalling the spec with encoding/json give deterministic output?
A `encoding/json` marshals map keys in sorted order, so the same set of paths/schemas always serialises identically — making the committed `openapi.json` diff cleanly.

🪶 Feynman Reflection

An OpenAPI file is the menu of a restaurant written in a language every delivery app understands: what dishes (endpoints) exist, what you must order with (request body), and what you'll get back (response schema + status). swagger-ui is the glossy photo menu generated from that text. Generating the menu from the kitchen's actual recipes (code-first) means the photos can't lie about what's served.

🕳️ Knowledge Gaps

  • Documenting auth schemes (securitySchemes, bearer JWT) and error envelopes consistently across endpoints — flesh out next.

✅ Summary

I can describe the API as a versioned OpenAPI 3.0 document, reuse schemas with $ref, serve /openapi.json for swagger-ui/Redoc, and keep the spec honest with a test.

⏭️ Next Steps / Prep for Tomorrow

  • Day 111: containerise the service with a multi-stage Dockerfile and wire up CI.

Time spent Difficulty Confidence
90 min 🟦🟦⬜⬜⬜ 🟦🟦🟦⬜⬜

Suggested commit: docs(project): OpenAPI 3.0 spec + served docs (day 110)