Polish: docs, seeds, and the finishing touches
The difference between "it works on my machine" and something another person can run.
- Version an API under
/v1from the first route. - Write a README that someone else can follow from clone to first request.
- Write doc comments that
go docshows 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.
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 unversionedA 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.
cmd/ api/main.go seed/main.go go run ./cmd/seedThe README someone can actually follow
# SnapNotes APINotes 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
@host = http://localhost:8080/v1### registerPOST {{host}}/auth/registerContent-Type: application/json{"name":"Sahdev","email":"s@example.com","password":"password123"}### login# @name loginPOST {{host}}/auth/loginContent-Type: application/json{"email":"s@example.com","password":"password123"}### create a notePOST {{host}}/notesAuthorization: Bearer {{login.response.body.access_token}}Content-Type: application/json{"title":"First note","body":"hello"}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.
// 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 }Final touches worth the hour
.env.examplecommitted with every key and no values..envin.gitignore.- A
/livezand/readyzthat check what they claim to check. X-Request-IDechoed 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_caseand use it in every tag.
- Version the path now. Retrofitting
/v1after clients exist is painful. - A seed command and a
requests.httpfile 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.
/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.- 1Clone your own repo into a fresh folder and follow your README exactly. Every improvised step is a README bug.
- 2Add
/v1to the paths and find every place you hardcoded a URL. - 3Write
requests.httpand run the whole flow — register, login, create, upload — from your editor.