Dev Skills

Polish: docs, seeds, and the finishing touches

The difference between "it works on my machine" and something another person can run.

Topic 85 of 98 · 5 min to read · 45 min to build
After this topic you can
  • Version an API under /v1 from the first route.
  • Write a README that someone else can follow from clone to first request.
  • Write doc comments that go doc shows correctly.

What polish means

The API works. Polish is the difference between something that works on your machine and something another person can clone, run and use without asking you a single question.

It is the least exciting hour of the project and the one most visible to anybody else: an interviewer, a teammate, or you in six months. Everything in this topic is small, and all of it matters once somebody else will touch the code.

Version your API from the start

Put /v1 in the path now. It costs nothing today. Once clients exist, adding it means moving every URL they call.

routes.go
v1 := http.NewServeMux()
userH.Routes(v1)
noteH.Routes(v1)
mux := http.NewServeMux()
mux.Handle("/v1/", http.StripPrefix("/v1", v1))
mux.HandleFunc("GET /livez", live) // health checks stay unversioned

A seed command

A second binary under cmd/ that fills the database with sample data. Two minutes to write, and it saves you from hand-registering a user every time you reset the database.

layout
cmd/
api/main.go
seed/main.go go run ./cmd/seed

The README someone can actually follow

README.md
# SnapNotes API
Notes with accounts and file attachments. Go 1.25, Postgres, no framework.
## Run it
cp .env.example .env
docker compose up -d
make migrate
make run
## Endpoints
POST /v1/auth/register
POST /v1/auth/login
POST /v1/auth/refresh
POST /v1/auth/logout
GET /v1/me
POST /v1/notes
GET /v1/notes?limit=20&offset=0
GET /v1/notes/{id}
PATCH /v1/notes/{id}
DELETE /v1/notes/{id}
POST /v1/notes/{id}/attachments multipart, field "file", max 10MB
GET /v1/notes/{id}/attachments
GET /v1/attachments/{id}
DELETE /v1/attachments/{id}
GET /livez
GET /readyz
## Notes
- Access token 15 min, refresh token 7 days with rotation.
- All list endpoints paginated, max limit 100.
- Uploads: jpeg, png, gif, webp, pdf. Type sniffed, not trusted.

A runnable request collection

requests.http
@host = http://localhost:8080/v1
### register
POST {{host}}/auth/register
Content-Type: application/json
{"name":"Sahdev","email":"s@example.com","password":"password123"}
### login
# @name login
POST {{host}}/auth/login
Content-Type: application/json
{"email":"s@example.com","password":"password123"}
### create a note
POST {{host}}/notes
Authorization: Bearer {{login.response.body.access_token}}
Content-Type: application/json
{"title":"First note","body":"hello"}
VS Code REST Client and JetBrains both run this file directly. It lives in the repo, so it changes in the same commits as the API it calls, unlike a Postman export on someone's laptop.

Doc comments

A doc comment starts with the name of the thing it describes. go doc and pkg.go.dev display them, so they are the documentation people will read.

doc.go
// Package note stores and retrieves a user's notes.
//
// Every repository method takes a userID and scopes its query to it, so a note
// belonging to another user is indistinguishable from one that does not exist.
package note
// Service applies the business rules for notes. It is safe for concurrent use.
type Service struct{ repo Repository }
Comment WHY, not what. The code already says what.

Final touches worth the hour

  • .env.example committed with every key and no values. .env in .gitignore.
  • A /livez and /readyz that check what they claim to check.
  • X-Request-ID echoed on every response, so a user can quote it in a bug report.
  • One consistent error shape everywhere: {"error": "..."}, never a bare string sometimes and an object other times.
  • Consistent JSON casing. Pick snake_case and use it in every tag.
The short version
  • Version the path now. Retrofitting /v1 after clients exist is painful.
  • A seed command and a requests.http file save you time every time you reset the database.
  • The README is the run instructions plus the endpoint list. Nothing else is needed.
  • Doc comments start with the identifier name and explain why, not what.
Remember: The project is finished when someone else can clone it and have it running in three commands.
Build this before moving on
Add /v1, the seed command, the README, and requests.http. Then clone your own repo into a fresh folder and follow your own instructions word for word. Every step you have to improvise is a bug in the README.
Then try these in your own editor
  1. 1Clone your own repo into a fresh folder and follow your README exactly. Every improvised step is a README bug.
  2. 2Add /v1 to the paths and find every place you hardcoded a URL.
  3. 3Write requests.http and run the whole flow — register, login, create, upload — from your editor.
Check yourselfTake the Production test