gorilla/mux: Regex Routes & Host Matching
gorilla/mux is a mature net/http router that emphasizes explicit route constraints: regex path variables, host-based routing, headers, queries, and schemes.
Search across all documentation pages
gorilla/mux is a mature net/http router that emphasizes explicit route constraints: regex path variables, host-based routing, headers, queries, and schemes.
It shines when URLs carry strict formats (IDs with prefixes, version segments, tenant subdomains) that chi-style segment matching expresses awkwardly.
mux compiles routes into a matcher list evaluated per request.
Each route can require HTTP methods, hostnames, headers, and query keys in addition to path templates.
Handlers stay stdlib-compatible, but the project is in maintenance mode - choose mux when its matchers solve a real routing problem, not by default for new services.
Quick-reference recipe card - copy-paste ready.
r := mux.NewRouter()
r.Host("{tenant}.example.com").
Path("/api/v{version:[0-9]+}/items/{id:[a-z]+}").
Methods(http.MethodGet).
HandlerFunc(getItem)When to reach for this:
{tenant}.api.example.com)id must match [0-9]{6})Accept, custom auth headers) or query presencepackage main
import (
"encoding/json"
"net/http"
"strconv"
"github.com/gorilla/mux"
)
type item struct {
ID string `json:"id"`
Version int `json:"version"`
Tenant string `json:"tenant"`
}
func getItem(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
out := item{
ID: vars["id"],
Version: mustAtoi(vars["version"]),
Tenant: vars["tenant"],
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(out)
}
func mustAtoi(s string) int {
n, _ := strconv.Atoi(s)
return n
}
func main() {
r := mux.NewRouter()
api := r.Host("{tenant:[a-z0-9-]+}.localhost").Subrouter()
api.HandleFunc("/v{version:[0-9]+}/items/{id:[a-z]{3,}}", getItem).Methods(http.MethodGet)
r.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok"))
})
http.ListenAndServe(":8080", r)
}What this demonstrates:
mux.Vars maps named captures to handler logicNewRouter creates a root router implementing http.Handler.Subrouter() scopes path prefixes and inherits parent matchers.StrictSlash controls trailing slash behavior via r.StrictSlash(true).| Builder | Purpose | Example |
|---|---|---|
Path / PathPrefix | Path template | /users/{id} |
Host | Subdomain or domain | {tenant}.example.com |
Methods | Verb allowlist | GET, POST |
Headers | Required header values | X-API-Key present |
Queries | Query key/value rules | format=json |
Schemes | http vs https | TLS-terminated routing |
Use {name:pattern} syntax.
{id:[0-9]+} rejects non-numeric IDs before your handler runs, returning 404 for non-matches.
Keep regexes readable - complex patterns belong in documentation and tests.
// Middleware with mux: wrap the router or per-route handlers
func logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
slog.Info("request", "path", r.URL.Path, "host", r.Host)
next.ServeHTTP(w, r)
})
}
srv := &http.Server{Addr: ":8080", Handler: logging(r)}mux does not ship a rich middleware package like chi; compose stdlib-style wrappers or use alice if you want chaining helpers.
r.Host may reflect internal names unless Forwarded/X-Forwarded-Host is normalized. Fix: terminate TLS at gateway that sets external host, or middleware rewrites r.Host deliberately.mux.Vars returns nil if matcher names differ from handler expectations. Fix: unit-test each template and centralize var parsing helpers.StrictSlash policy.| Alternative | Use When | Don't Use When |
|---|---|---|
| chi | Modern radix routing, active middleware ecosystem | You need host/regex matchers mux provides cleanly |
stdlib ServeMux | Simple {id} patterns in Go 1.22+ | Host/header/query rules are complex |
| Gin/Echo | Framework binding and JSON helpers | You want stdlib handlers and explicit matchers |
| API gateway (nginx, Envoy) | Host/path routing at edge | You need in-process routing only |
It is in maintenance mode - safe for existing apps, but evaluate chi or edge routing for new services unless mux matchers are required.
Both name segments; mux adds inline regex constraints and additional matchers for host, headers, and queries on the same route builder.
Yes - wrap the router or individual handlers using standard func(http.Handler) http.Handler middleware.
Use httptest with fully qualified paths and Host headers set on the request to exercise host matchers.
mux returns 404; use r.NotFoundHandler to customize responses and logging.
Child subrouters combine parent path prefixes and can add their own Host or Headers constraints.
Use mux for structural URL shape (digits-only IDs); keep business validation (exists in DB) in handlers/services.
Not built-in - add middleware setting Access-Control-* headers before your handlers.
Rewrite route templates ({id:regex} to {id} or handler validation) and replace mux.Vars with chi.URLParam; keep handlers if already stdlib-shaped.
Yes - host matchers on routes or subrouters dispatch by r.Host on a shared listener.
Configure http.Server TLS as usual; use Schemes("https") when terminating TLS in-process.
Edge gateways excel at TLS and coarse routing; in-process host rules help when you want one binary and testable routing without extra infra.
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).
Reviewed by Chris St. John·Last updated Jul 16, 2026