CodeRigor

LESSON

Route methods and canonical paths deliberately

Separate missing routes from unsupported methods, preserve an explicit Allow contract and reject ambiguous note identifiers.

A router answers two questions: does this path name a route, and does that route support this method? Keep those questions separate. GET /missing is 404. PUT /notes is 405 with an Allow header because /notes is a real collection route.

This API’s small route table is written out. Health supports GET/HEAD, the collection supports GET/POST, and an item supports GET/DELETE. A numeric path does not prove a stored note exists; the repository decides that later.

Path recognition precedes method dispatch
start / 3
A known path can reject a method with 405 and an Allow header. Unknown or noncanonical paths return 404 before storage is consulted.
Path recognition precedes method dispatch
A known path can reject a method with 405 and an Allow header. Unknown or noncanonical paths return 404 before storage is consulted.

The parser is supplied in this exercise; complete the method dispatch. Use equality, not substring containment. HTTP method names are case-sensitive.

For item IDs, conversion and validation are different. Atoi can parse 01 or +1, but this route admits only the canonical positive decimal spelling from 1 through 1,000,000. A round trip through Itoa rejects aliases.

Go
package main
import ("fmt";"strconv")
func main(){
for _,raw:=range []string{"1","01","+1","0"}{n,err:=strconv.Atoi(raw);ok:=err==nil&&n>0&&n<=1000000&&strconv.Itoa(n)==raw;fmt.Println(raw,ok)}
}

The handler passes URL.EscapedPath to the router. That preserves a spelling such as /notes/%31 instead of silently turning it into /notes/1. This is a deliberate teaching API policy; other APIs can choose a different normalization contract, but all layers must agree.

No slash cleaning, redirects or fallback page is part of this service. Paths with trailing or extra segments fail. A query string does not change the URL path. Method failures preserve an exact, stable Allow list for clients and tests.

Routing returns a decision rather than writing the response or mutating storage. This makes the table straightforward to test and keeps responsibilities visible in the final handler.

Route identity is not record existence
start / 3
A canonical /notes/12 path identifies an item operation. Only repository lookup determines whether note 12 is stored.
Route identity is not record existence
A canonical /notes/12 path identifies an item operation. Only repository lookup determines whether note 12 is stored.
Go
package main
import ("fmt";"net/url")
func main(){
u,_:=url.Parse("http://example.test/notes/12?view=short");fmt.Println(u.EscapedPath(),u.RawQuery)
}

Complete the dispatch and test every allowed method, several wrong methods, leading zeros, encoded digits and oversized IDs. Do not query the repository from the router.

Next we define a bounded request-body schema before any note can be created.

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.