The final handler connects the boundaries you have built. It does not parse arbitrary paths, concatenate JSON or own a global map. It coordinates explicit helpers and one injected repository.
The native exercise uses the real handler with recorders. The complete download also supplies a Go module, a runnable loopback command, original request/response fixtures, the reference and starter, contract tests and a local listener/lifecycle test. No external Go package or database is required.
Return immediately after every handled error. Writing a 400 response and then continuing into Create is a state bug even if the client still sees a 400 status. Tests must inspect both the exchange and the repository.
Keep private backend errors out of the public representation. Known missing/capacity categories map to their declared statuses; an unexpected repository failure becomes a generic internal_error response. Logging a raw request body is not part of this contract.
package main
import ("fmt";"net/http/httptest";"encoding/json")
func main(){
w:=httptest.NewRecorder();w.Header().Set("Location","/notes/1");w.WriteHeader(201);_ =json.NewEncoder(w).Encode(map[string]any{"id":1,"title":"learn"});fmt.Println(w.Code,w.Header().Get("Location"));fmt.Print(w.Body.String())
}Use URL.EscapedPath for the declared canonical route policy. A missing item and an unknown route both yield not_found, but the routing and repository checks remain separate internally. A wrong method keeps Allow. A successful create keeps Location.
The local command binds 127.0.0.1:0 and prints the actual URL. Ctrl-C requests graceful shutdown with a bounded context, closes on failure and joins the serving result. The tests use only an ephemeral listener owned by the test and close it deterministically.
Notes disappear when the process stops. There is no authentication, durable database, cross-process coordination or production deployment in this lab. Those are concrete next boundaries, not hidden features implied by a working server. The Boot.dev continuation extends into the larger storage/auth/webhook application.
package main
import ("fmt";"net/http/httptest";"strings")
func main(){
raw:=`{"title":"learn"}`;r:=httptest.NewRequest("POST","http://example.test/notes",strings.NewReader(raw));r.Header.Set("Content-Type","application/json");fmt.Println(r.Method,r.URL.EscapedPath(),r.Header.Get("Content-Type"))
}Complete the handler, then run the download’s tests and server command. Use the printed URL for create/list/get/delete examples. Try duplicate keys, oversized bodies, wrong media types and unsupported methods; rejected requests must not create notes.
Stop with Ctrl-C and verify the process exits. Keep the source and fixtures for the next course: persistence and authentication should be added through explicit boundaries and tested contracts.
Download the complete notes service lab: standard-library Go server, original fixtures, starter, tests and run/cleanup instructions.
Follow one request through the service.
These seven exchanges were generated by the downloadable Go replay against one fresh in-memory store. Choose a recorded step to inspect its request, response and resulting note count. Choosing a step does not send a live request.
- Client requestused
- Route & methodused
- Bounded decoderused
- Repositoryused
- JSON responseused
Request
POST /notes
Content-Type: application/json{
"title": " learn "
}Recorded response
{
"Content-Length": "25",
"Content-Type": "application/json; charset=utf-8",
"Location": "/notes/1",
"X-Content-Type-Options": "nosniff"
}{
"id": 1,
"title": "learn"
}Headers, status, body and stored count are actual recorder results. The highlighted stages annotate the corresponding code path; this is a replay, not a distributed trace. The duplicate-key step leaves the stored count at one.
Download the runnable service, replay and tests →