Routing a Resource
You've got writeJSON/writeError for sending responses and a decode
helper for reading request bodies. Fundamentals' "Building HTTP Services"
chapter already covered http.ServeMux and method patterns like
"GET /path" — this lesson doesn't re-teach the mechanics of registration.
What it does is apply them to a real resource: the tasks API you'll spend
the rest of this book building out.
Two URL shapes, one resource
Almost every REST resource boils down to two URLs. The collection URL
names the resource as a whole — /tasks — and supports listing everything
and creating something new. The item URL names one member of that
collection — /tasks/{id} — and supports reading, replacing, or removing
that single thing. Once you see this pattern you'll recognize it
everywhere: /users and /users/{id}, /orders and /orders/{id}, and so
on. Learn the shape once and every resource you add to this service follows
the same two-URL structure.
The verb-to-action mapping
HTTP methods aren't arbitrary here — each one maps to a specific action, and that mapping is consistent across almost every REST API you'll ever touch:
| Method | Path | Action |
|---|---|---|
GET | /tasks | list every task |
POST | /tasks | create a new task |
GET | /tasks/{id} | fetch one task |
PUT | /tasks/{id} | replace one task |
DELETE | /tasks/{id} | remove one task |
Five methods, two paths, five distinct handlers. GET shows up on both
paths but means something different on each — "list" on the collection,
"fetch one" on the item — because the path scopes what the verb applies to.
Registering the table on one mux
Each row in that table becomes one mux.HandleFunc call, with the method
baked into the pattern string:
mux := http.NewServeMux()
mux.HandleFunc("GET /tasks", listTasks)
mux.HandleFunc("POST /tasks", createTask)
mux.HandleFunc("GET /tasks/{id}", getTask)
mux.HandleFunc("PUT /tasks/{id}", updateTask)
mux.HandleFunc("DELETE /tasks/{id}", deleteTask)
Five routes, five lines, no branching logic inside any handler to figure out "was this a GET or a PUT." The mux does that dispatch for you before your handler code ever runs — each handler only has to deal with the one case it was written for.
Method patterns give you 405 for free
This is the payoff for using method-scoped patterns instead of a single
catch-all "/tasks/{id}" registration with a switch r.Method inside. If a
request arrives for a registered path but an unregistered method — say,
PATCH /tasks/7, which isn't one of the five rows above — ServeMux
responds 405 Method Not Allowed automatically. You don't write that
branch, you don't remember to write that branch, and you can't forget to
write that branch, because it doesn't exist as code you own. That's a small
thing on one resource, but it's one whole category of bug that just isn't
possible to introduce as this service grows to a dozen resources and fifty
routes.
What's still a stub
The five handlers wired up here — listTasks, createTask, getTask,
updateTask, deleteTask — currently just write their own name to the
response. That's intentional: this lesson is about getting requests to the
right handler, not about what that handler does once it gets there. Once
Chapter 2 introduces the task store, these same five functions grow real
bodies — reading from and writing to that store — without the routing table
above needing to change at all. The route table and the handler logic are
separate concerns, and keeping them separate is exactly what makes the next
chapter a matter of filling in function bodies rather than rewriting how
requests get dispatched.
Practice
1. Routing a Resource
IntermediateA REST resource has two URL shapes: the collection (
/tasks) and a single item (/tasks/{id}). Wire them up.Implement
NewRouter() *http.ServeMuxregistering exactly these five routes, each pointing at the matching handler (the handlers are provided — they just write their name so the test can see routing worked):GET /tasks→listTasksPOST /tasks→createTaskGET /tasks/{id}→getTaskPUT /tasks/{id}→updateTaskDELETE /tasks/{id}→deleteTask
Because the patterns name a method, a
PATCH /tasks/7gets an automatic405— you don't write that yourself.package main import ( "fmt" "net/http" ) func listTasks(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "list") } func createTask(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "create") } func getTask(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "get") } func updateTask(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "update") } func deleteTask(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "delete") } // NewRouter wires the tasks resource: a collection (/tasks) and an item // (/tasks/{id}), each with the appropriate methods. Method patterns give us // automatic 405s for the wrong verb. func NewRouter() *http.ServeMux { mux := http.NewServeMux() // TODO: register the five routes return mux } func main() { http.ListenAndServe(":8080", NewRouter()) }Sign in to write and run your solution in the browser.
Sign in for hints and the solution.