This episode dissects chi's architecture from the inside: chi.NewRouter returning an http.Handler, a radix tree for matching patterns, and chi.RouteContext carrying params and the middleware stack through the Go context. You will also get to know the main router components and the built-in middleware package.

Episode 1 explained why chi exists. Episode 2 opens the hood: how chi works behind the scenes. Understanding this internal architecture matters, because many chi usage mistakes — for example losing params or middleware not running — are rooted in a poor understanding of chi.RouteContext and the context flow.
This episode's goal is simple: when you're done, you can explain the flow of a request from arrival to handler execution, including where params come from and why middleware order matters.
chi.NewRouter() returns a value of type chi.Router — an interface whose implementation is also an http.Handler. That's why a chi router can be handed directly to http.ListenAndServe without an adapter:
package main
import (
"net/http"
"github.com/go-chi/chi/v5"
)
func main() {
r := chi.NewRouter()
r.Get("/", func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("halo dari chi"))
})
http.ListenAndServe(":8080", r)
}Note that there's no magic here. http.ListenAndServe(":8080", r) accepts the router because the router implements ServeHTTP — exactly like an ordinary handler.
When a request arrives, the ServeHTTP method on the router runs. The router reads the method and path, then matches them against the registered patterns. This matching process is optimized through a special structure called a radix tree.
chi's internal router organizes all route patterns into a radix tree — a tree in which each level represents a path segment. Finding the handler for /users/42 only requires walking down the users node then the {id} node, without comparing against every registered pattern one by one.
The benefits of a radix tree:
Every node that stores a param pattern (for example {id}) records the name and value when a request is matched. These values are stored in the route context, which we'll discuss next.
rctx := chi.RouteContext(req.Context())
param := rctx.URLParam("id")chi.RouteContext(req.Context()) retrieves the route context structure from the request context, then rctx.URLParam("id") reads the matched param value. This is the path used by chi.URLParam in episode 4.
Since Go 1.7, the context package became part of the standard library and http.Request carries a context. chi takes advantage of this by injecting a route context into the request context. In this way, params and the middleware stack move from the router to the handler without global variables.
Important components of the route context:
When a request flows through, the registered middleware runs in sequence like an onion. Each middleware receives an http.Handler and returns a new http.Handler. The route context keeps track of the remaining middleware that hasn't been called yet, so subrouters and With can add new layers dynamically.
chi.NewRouter() — the manager of route patterns and handlers.chi.URLParam).r.Middlewares().middleware.Chain — composing explicit middleware into a single handler.r.Mount — attaching a sub-app as a handler on a path prefix.All of these components work on a single foundation: net/http.
chi ships with a middleware package full of ready-to-use utilities. Some you'll use often:
middleware.RequestID and middleware.RealIP for request metadata.middleware.Logger for simple logging.middleware.Recoverer for catching panics.middleware.Timeout and middleware.Throttle for control.middleware.Compress, middleware.BasicAuth, and middleware.RedirectSlashes.We'll explore all of them in depth starting in episode 6. For now, just understand that this package is made of ordinary func(http.Handler) http.Handler functions.
go doc github.com/go-chi/chi/v5/middlewarego doc github.com/go-chi/chi/v5/middleware shows the complete list of available middleware along with their signatures.
Key takeaways:
chi.NewRouter() returns a chi.Router that is also an http.Handler.chi.RouteContext is carried through the Go context and stores params plus the middleware stack.chi.URLParam or rctx.URLParam.func(http.Handler) http.Handler functions.net/http.In the next episode 3 we will write the first actually-working code: setup and hello world — from installing chi v5, chi.NewRouter(), and your first handler, to the principle that chi is net/http, which you can feel directly with curl.