Skip to content

Day 087 — Routing with chi

Month 4 · Week 1 · ⬅ Day 086 · Day 088 ➡ · Journal index

🎯 Learning Objective

Use github.com/go-chi/chi/v5 for ergonomic routing — URL params, sub-routers, route groups — and know when it earns its keep over the stdlib mux.

📚 Topics

  • chi.NewRouter, r.Get/Post/..., chi.URLParam, r.Route, r.Mount
  • r.Group, route-scoped middleware, 100% net/http-compatible handlers

📖 Reading / Sources

📝 Notes

  • chi is a thin, stdlib-compatible router: every handler is a normal http.Handler/HandlerFunc, and chi.Router is an http.Handler. No framework lock-in → swappable with [[http-handler]].
  • Read params with chi.URLParam(r, "id") (chi stores them on the request context). Patterns use {name} and regex constraints like {id:[0-9]+}.
  • Sub-routers keep large APIs tidy: r.Route("/users", func(r chi.Router){...}) scopes a path prefix; r.Mount("/admin", adminRouter) grafts a whole router.
  • r.Group applies middleware to a set of routes without changing their path prefix — handy for "these endpoints need auth".
  • chi ships a battle-tested middleware package (RequestID, RealIP, Logger, Recoverer, Timeout) — same func(http.Handler) http.Handler shape as Day 088.
  • When to choose it: since Go 1.22 the stdlib mux covers method + wildcard routing, so reach for chi when you want nested routers, route groups, regex params, or the ready-made middleware stack. For a few endpoints, stdlib is enough → [[router-choice]].
  • This needs a third-party module, so today has no runnable example in the repo; the snippet below is the reference.

💻 Code Examples

// go get github.com/go-chi/chi/v5
import (
    "net/http"

    "github.com/go-chi/chi/v5"
    "github.com/go-chi/chi/v5/middleware"
)

func main() {
    r := chi.NewRouter()
    r.Use(middleware.RequestID, middleware.Logger, middleware.Recoverer)

    r.Route("/users", func(r chi.Router) {
        r.Get("/", listUsers)             // GET /users
        r.Post("/", createUser)           // POST /users
        r.Route("/{id:[0-9]+}", func(r chi.Router) {
            r.Get("/", getUser)           // GET /users/{id}
            r.Delete("/", deleteUser)     // DELETE /users/{id}
        })
    })

    // Auth only for this group; same path prefix as siblings.
    r.Group(func(r chi.Router) {
        r.Use(requireAuth)
        r.Get("/me", whoAmI)
    })

    http.ListenAndServe(":8087", r) // chi.Router is an http.Handler
}

func getUser(w http.ResponseWriter, r *http.Request) {
    id := chi.URLParam(r, "id") // chi reads params from the request context
    _ = id
}

🏋️ Exercises / Practice

Exercise Status Link
Re-create method routing on the stdlib mux (chi-free) exercises/month-04/week-1/pathrouter

🐛 Mistakes Made

  • Reached for r.PathValue inside a chi handler — that's the stdlib mux API; with chi you call chi.URLParam(r, "id").
  • Put a /{id} route before more specific literal routes and worried about order; chi matches by specificity, but being explicit avoided surprises.

❓ Open Questions

  • How does chi's trie matcher compare in latency to the stdlib mux at scale? (Both are O(path length)-ish; benchmark before assuming.)

🧠 Active Recall (answer without looking)

  1. Q: How do you read a URL parameter in a chi handler?
A `chi.URLParam(r, "id")` — chi stores matched params on the request context.
  1. Q: Difference between r.Route and r.Group in chi?
A `Route` scopes a **path prefix** (a sub-router); `Group` scopes **middleware** to a set of routes **without** adding a path prefix.

🪶 Feynman Reflection

chi is the stdlib mux with nicer ergonomics: nested routers, regex params, and groups for shared middleware, while every handler stays a plain net/http handler so nothing is locked in. Since Go 1.22 I only graduate to it when the routing tree gets genuinely nested.

🕳️ Knowledge Gaps

  • chi's NotFound/MethodNotAllowed customisation and how it differs from the stdlib mux's automatic 405.

✅ Summary

I can structure a real API with chi — sub-routers, regex params, and grouped middleware — and I know when the stdlib mux is already enough.

⏭️ Next Steps / Prep for Tomorrow

  • Day 088: middleware chains — the func(http.Handler) http.Handler pattern.

Time spent Difficulty Confidence
90 min 🟦🟦⬜⬜⬜ 🟦🟦🟦⬜⬜

Suggested commit: docs(journal): routing with chi sub-routers and groups (day 087)