http.ServeMux gained method routing and path parameters in Go 1.22. What it still lacks is prefix grouping and middleware inheritance. muxx adds exactly those two things and nothing else.
Zero external dependencies. No magic. The underlying ServeMux is exposed through ServeHTTP — everything the stdlib provides works unchanged.
All groups share a single ServeMux. Groups are a registration-time concept only: they carry a prefix and a middleware chain that get applied when a route is registered. By the time a request arrives, the router is flat.
This means r.PathValue() works natively, there is no request rewriting, and there are no hidden layers to reason about.
r := muxx.New()
r.HandleFunc("GET /", homeHandler)
api := r.Group("/api")
api.Use(authMiddleware)
users := api.Group("/users")
users.HandleFunc("GET /", listUsers)
users.HandleFunc("POST /", createUser)
users.HandleFunc("GET /{id}", getUser)
http.ListenAndServe(":8080", r)GET /users/{id} registers as GET /api/users/{id} in the underlying ServeMux. The authMiddleware runs on every route under /api.
Group accepts the same pattern format as ServeMux. If the prefix does not start with /, it is treated as a host, matching ServeMux convention directly.
api := r.Group("api.example.com")
api.Use(authMiddleware)
api.HandleFunc("GET /users", listUsers)Host and path can be combined in a single call:
admin := r.Group("admin.example.com/settings")
admin.HandleFunc("GET /profile", profileHandler)
// registers as: GET admin.example.com/settings/profileHost groups compose with further Group calls and inherit middleware the same way path groups do.
Middleware follows func(http.Handler) http.Handler. Apply it to a group with Use:
api.Use(authMiddleware, loggingMiddleware)Child groups inherit the parent's middleware. Adding middleware to a child does not affect sibling groups or routes already registered.
Middleware runs in declaration order: the first argument is the outermost wrapper, closest to the request.
To compose middleware onto a single handler without a router, use Chain:
h := muxx.Chain(myHandler, authMiddleware, loggingMiddleware)If you already have a *http.ServeMux, wrap it instead of creating a new one:
r := muxx.NewFromMux(existingMux)New() *Router
NewFromMux(mux *http.ServeMux) *Router
(*Router).Group(prefix string) *Router
(*Router).Use(mw ...Middleware)
(*Router).Handle(pattern string, h http.Handler)
(*Router).HandleFunc(pattern string, fn http.HandlerFunc)
(*Router).ServeHTTP(w http.ResponseWriter, r *http.Request)
Chain(h http.Handler, m ...Middleware) http.HandlerPatterns follow the Go 1.22 ServeMux format: [METHOD ][HOST]PATH. Method, host, and path parameters work exactly as documented in net/http.