What it is
chi is a lightweight, idiomatic HTTP router for Go that is fully compatible with net/http. Handlers are plain http.HandlerFunc, so any standard-library middleware works without adaptation.
chi provides a trie-based router with URL parameters, sub-routers, middleware stacks and route groups — while every handler remains a standard http.Handler. This means net/http middleware, testing with httptest, and any third-party handler all work unchanged.
Installation
go get -u github.com/go-chi/chi/v5Getting started
The smallest useful thing you can do with it, and what each part means.
package main
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.RealIP, middleware.Recoverer)
r.Get("/books/{id}", func(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"id": id})
})
http.ListenAndServe(":8080", r)
}r := chi.NewRouter()
r.Route("/api/v1", func(r chi.Router) {
r.Route("/books", func(r chi.Router) {
r.Get("/", listBooks)
r.Post("/", createBooks)
r.Route("/{bookID}", func(r chi.Router) {
r.Use(BookCtx) // loads the book once for all child routes
r.Get("/", getBook)
r.Delete("/", deleteBook)
})
})
})Advanced usage
Where the library earns its place over a simpler alternative.
type ctxKey string
const bookKey ctxKey = "book"
func BookCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
book, err := store.Find(r.Context(), chi.URLParam(r, "bookID"))
if errors.Is(err, ErrNotFound) {
http.Error(w, "not found", http.StatusNotFound)
return
} else if err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
ctx := context.WithValue(r.Context(), bookKey, book)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func getBook(w http.ResponseWriter, r *http.Request) {
book := r.Context().Value(bookKey).(*Book)
json.NewEncoder(w).Encode(book)
}Errors and fixes
The failures you are most likely to hit, and what actually resolves them.
- chi.URLParam returns an empty string
- The parameter name does not match the pattern, or the handler is not mounted under a route that declares it. Check the braces in the route pattern match the name exactly.
- Middleware appears to be ignored
- chi panics if Use is called after routes are registered on the same router. Register all middleware first, or apply it inside a Group or Route block.
Best practices
- Prefer chi when you want routing improvements without adopting a framework — everything stays http.Handler.
- Use a distinct unexported type for context keys; plain strings risk collisions across packages.
- Apply middleware with r.Use before defining routes on that router — middleware added afterwards will not apply.
- Evaluate whether Go 1.22's standard router is now sufficient; for simple method-and-path routing it often is.
Background
Why it exists, and what it was reacting to.
chi was written by Peter Kieltyka at Pressly, built around the principle that a router should not require you to leave the standard library. Its context-based parameter passing was influential enough that Go 1.22's own router adopted a similar approach.
