CodeRigor

LESSON

Build and run the complete notes API

Integrate route, input, response and repository boundaries, then run a downloadable loopback server with deterministic fixtures and cleanup.

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.

The complete request path
start / 3
A request passes cancellation and route checks, then bounded input validation before any repository mutation. One response maps the result back to HTTP.
The complete request path
A request passes cancellation and route checks, then bounded input validation before any repository mutation. One response maps the result back to HTTP.

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.

Go
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.

A runnable lab with visible limits
start / 3
The reference service is tested in-process and on a real loopback listener. Its deliberate memory-only/no-auth scope points to the next application boundaries.
A runnable lab with visible limits
The reference service is tested in-process and on a real loopback listener. Its deliberate memory-only/no-auth scope points to the next application boundaries.
Go
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.

Actual handler output · Original fixture

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.

  1. Client requestused
  2. Route & methodused
  3. Bounded decoderused
  4. Repositoryused
  5. JSON responseused
POST /notes201 Created1 stored note after this exchange

Request

Text
POST /notes
Content-Type: application/json
JSON
{
  "title": " learn "
}

Recorded response

JSON
{
  "Content-Length": "25",
  "Content-Type": "application/json; charset=utf-8",
  "Location": "/notes/1",
  "X-Content-Type-Options": "nosniff"
}
JSON
{
  "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 →

Watch & read

Free lectures, talks and readings that cover this skill from another angle. All resources →

Optional design discussion of handlers, dependencies and tests. This 2019 talk predates current Go routing changes; use the current net/http documentation and this course’s explicit route contract for the lab.