O que é Router em Go?
Um router (ou multiplexer/mux) em Go é o componente responsável por direcionar cada requisição HTTP para o handler correto com base no método HTTP e no path da URL. O router padrão da biblioteca de Go é o http.ServeMux, que recebeu melhorias significativas no Go 1.22 com suporte nativo a method routing e path parameters — funcionalidades que antes exigiam bibliotecas de terceiros.
O conceito de routing é central em qualquer aplicação web. Quando uma requisição chega ao servidor (por exemplo, GET /api/usuarios/42), o router precisa: identificar que o método é GET, fazer matching do path /api/usuarios/42 contra os padrões registrados, extrair o parâmetro 42 e chamar o handler correspondente. Antes do Go 1.22, o ServeMux só fazia matching por prefixo de path, sem suporte a métodos ou parâmetros — forçando desenvolvedores a usar routers de terceiros como chi, gorilla/mux ou httprouter.
Com as melhorias do Go 1.22, a maioria das aplicações pode usar o ServeMux da stdlib sem dependências externas. Vamos explorar o router moderno de Go e compará-lo com as alternativas populares.
ServeMux — o router da stdlib
Registro básico de rotas
func main() {
mux := http.NewServeMux()
// Rota exata
mux.HandleFunc("/", homeHandler)
// Rota com prefixo (termina com /)
mux.HandleFunc("/api/", apiHandler) // Captura /api/qualquer/coisa
// Funções inline
mux.HandleFunc("/saude", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "OK")
})
http.ListenAndServe(":8080", mux)
}
O ServeMux usa a regra de matching mais específico: /api/usuarios tem prioridade sobre /api/ que tem prioridade sobre /. Isso garante que rotas mais específicas são sempre alcançadas primeiro.
Method routing (Go 1.22+)
A partir do Go 1.22, você pode especificar o método HTTP diretamente no padrão da rota:
func main() {
mux := http.NewServeMux()
// CRUD completo com method routing
mux.HandleFunc("GET /api/produtos", listarProdutos)
mux.HandleFunc("POST /api/produtos", criarProduto)
mux.HandleFunc("GET /api/produtos/{id}", buscarProduto)
mux.HandleFunc("PUT /api/produtos/{id}", atualizarProduto)
mux.HandleFunc("DELETE /api/produtos/{id}", deletarProduto)
// Sem método = aceita qualquer método
mux.HandleFunc("/saude", saudeHandler) // GET, POST, etc.
http.ListenAndServe(":8080", mux)
}
func listarProdutos(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprint(w, `[{"id":1,"nome":"Widget"},{"id":2,"nome":"Gadget"}]`)
}
func criarProduto(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
var produto struct {
Nome string `json:"nome"`
Preco float64 `json:"preco"`
}
if err := json.NewDecoder(r.Body).Decode(&produto); err != nil {
http.Error(w, "JSON inválido", http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(produto)
}
Se um path tem handlers para GET e POST mas recebe um PUT, o ServeMux retorna automaticamente 405 Method Not Allowed com o header Allow listando os métodos aceitos. Isso é comportamento HTTP correto sem código adicional.
Path parameters (Go 1.22+)
Path parameters são definidos com chaves {nome} e extraídos com r.PathValue():
func main() {
mux := http.NewServeMux()
// Parâmetro simples
mux.HandleFunc("GET /usuarios/{id}", func(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
fmt.Fprintf(w, "Usuário ID: %s", id)
})
// Múltiplos parâmetros
mux.HandleFunc("GET /empresas/{empresaId}/funcionarios/{funcId}",
func(w http.ResponseWriter, r *http.Request) {
empresa := r.PathValue("empresaId")
funcionario := r.PathValue("funcId")
fmt.Fprintf(w, "Empresa: %s, Funcionário: %s", empresa, funcionario)
})
// Wildcard — captura o resto do path
mux.HandleFunc("GET /arquivos/{caminho...}",
func(w http.ResponseWriter, r *http.Request) {
caminho := r.PathValue("caminho")
// caminho = "docs/guias/intro.md" para /arquivos/docs/guias/intro.md
fmt.Fprintf(w, "Arquivo: %s", caminho)
})
http.ListenAndServe(":8080", mux)
}
O wildcard {nome...} (com reticências) captura tudo que vier depois, incluindo barras. É útil para servir arquivos estáticos ou implementar rotas catch-all. Sem reticências, {nome} captura apenas um segmento do path.
Host-based routing
O ServeMux também suporta routing por hostname:
mux := http.NewServeMux()
// Rotas específicas por host
mux.HandleFunc("api.exemplo.com/v1/dados", apiV1Handler)
mux.HandleFunc("admin.exemplo.com/", adminHandler)
// Rota padrão (qualquer host)
mux.HandleFunc("/", defaultHandler)
Isso é útil para servir diferentes aplicações no mesmo servidor baseado no hostname, sem necessidade de um reverse proxy para routing básico.
Comparação de routers
Go tem várias opções de routers. Aqui está uma comparação prática:
| Funcionalidade | ServeMux (1.22+) | chi | gorilla/mux | httprouter |
|---|---|---|---|---|
| Method routing | Sim | Sim | Sim | Sim |
| Path params | Sim ({id}) | Sim ({id}) | Sim ({id}) | Sim (:id) |
| Wildcards | Sim ({p...}) | Sim (*) | Sim | Sim (*filepath) |
| Middleware groups | Nao | Sim | Sim | Nao |
| Regex no path | Nao | Nao | Sim | Nao |
| Subrouters | Nao | Sim | Sim | Nao |
| Compatível c/ stdlib | Nativo | Sim | Sim | Adaptador |
| Dependência externa | Nao | Sim | Sim | Sim |
chi — compatibilidade total com stdlib
import "github.com/go-chi/chi/v5"
func main() {
r := chi.NewRouter()
// Middlewares como grupo
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
// Rotas
r.Get("/", homeHandler)
// Grupo com middleware específico
r.Route("/api", func(r chi.Router) {
r.Use(comAuth)
r.Get("/usuarios", listarUsuarios)
r.Post("/usuarios", criarUsuario)
r.Route("/usuarios/{id}", func(r chi.Router) {
r.Get("/", buscarUsuario)
r.Put("/", atualizarUsuario)
r.Delete("/", deletarUsuario)
})
})
http.ListenAndServe(":8080", r)
}
Chi é a escolha mais popular para projetos que precisam de subrouters e agrupamento de middlewares. Seus handlers são 100% compatíveis com a interface http.Handler da stdlib.
gorilla/mux — mais funcionalidades
import "github.com/gorilla/mux"
func main() {
r := mux.NewRouter()
// Path params com regex
r.HandleFunc("/usuarios/{id:[0-9]+}", buscarUsuario).Methods("GET")
// Query params como critério de routing
r.HandleFunc("/busca", buscarHandler).Queries("q", "{query}")
// Subrouter com prefixo
api := r.PathPrefix("/api/v1").Subrouter()
api.HandleFunc("/produtos", listarProdutos).Methods("GET")
http.ListenAndServe(":8080", r)
}
Gorilla/mux oferece regex em paths e routing por query params, útil para APIs complexas. No entanto, o projeto foi arquivado e depois revivido — considere chi como alternativa moderna.
Padrões avançados de routing
Versionamento de API
func main() {
mux := http.NewServeMux()
// V1
mux.HandleFunc("GET /api/v1/usuarios", listarUsuariosV1)
mux.HandleFunc("GET /api/v1/usuarios/{id}", buscarUsuarioV1)
// V2 com resposta diferente
mux.HandleFunc("GET /api/v2/usuarios", listarUsuariosV2)
mux.HandleFunc("GET /api/v2/usuarios/{id}", buscarUsuarioV2)
// Versionamento por header (usando middleware)
mux.Handle("GET /api/usuarios", comVersao(
map[string]http.Handler{
"v1": http.HandlerFunc(listarUsuariosV1),
"v2": http.HandlerFunc(listarUsuariosV2),
},
))
http.ListenAndServe(":8080", mux)
}
func comVersao(handlers map[string]http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
versao := r.Header.Get("API-Version")
if versao == "" {
versao = "v2" // versão padrão
}
h, ok := handlers[versao]
if !ok {
http.Error(w, "Versão não suportada", http.StatusBadRequest)
return
}
h.ServeHTTP(w, r)
})
}
Organização por domínio
Para aplicações maiores, organize rotas por domínio de negócio:
func main() {
mux := http.NewServeMux()
// Cada domínio registra suas próprias rotas
registrarRotasUsuarios(mux)
registrarRotasProdutos(mux)
registrarRotasPedidos(mux)
// Middlewares globais
handler := encadear(mux,
comRecovery,
comLogging,
comCORS("*"),
)
http.ListenAndServe(":8080", handler)
}
func registrarRotasUsuarios(mux *http.ServeMux) {
h := &UsuarioHandler{/* dependências */}
mux.HandleFunc("GET /api/usuarios", h.Listar)
mux.HandleFunc("POST /api/usuarios", h.Criar)
mux.HandleFunc("GET /api/usuarios/{id}", h.Buscar)
mux.HandleFunc("PUT /api/usuarios/{id}", h.Atualizar)
mux.HandleFunc("DELETE /api/usuarios/{id}", h.Deletar)
}
Esse padrão segue os princípios de Clean Architecture e funciona bem com microsserviços. Para APIs mais complexas, consulte o tutorial de API REST em Go e o artigo sobre Go com HTMX.
Testando rotas
func TestRotas(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("GET /api/produtos/{id}", buscarProduto)
mux.HandleFunc("POST /api/produtos", criarProduto)
srv := httptest.NewServer(mux)
defer srv.Close()
// Testa GET com path param
resp, err := http.Get(srv.URL + "/api/produtos/42")
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("GET: esperava 200, recebeu %d", resp.StatusCode)
}
// Testa Method Not Allowed
resp2, _ := http.Get(srv.URL + "/api/produtos")
defer resp2.Body.Close()
// GET /api/produtos sem handler -> 405 (pois POST /api/produtos existe)
// (na verdade depende da implementação exata)
}
Use httptest.NewServer para testes de integração e httptest.NewRequest + httptest.NewRecorder para testes unitários. Consulte o tutorial de testes em Go para mais padrões.
Quando usar um router de terceiros
Use o ServeMux da stdlib quando:
- Suas rotas são CRUD simples com method routing e path params
- Você quer zero dependências externas
- Sua aplicação não precisa de middleware groups ou subrouters aninhados
Use chi quando:
- Precisa de subrouters e agrupamento de middlewares por rota
- Quer middlewares prontos (logger, recoverer, timeout, etc.)
- O projeto tem muitas rotas organizadas em grupos
Use gorilla/mux quando:
- Precisa de regex em paths ou routing por query params
- Nota: considere se chi não atende antes, já que gorilla/mux tem histórico de manutenção instável
Use httprouter quando:
- Performance de routing é crítica (benchmarks mostram ser o mais rápido)
- Nota: não é 100% compatível com
http.Handlersem adaptadores
Perguntas Frequentes
O ServeMux do Go 1.22+ substitui chi e gorilla/mux?
Para muitos casos, sim. O ServeMux agora suporta method routing (GET /path), path parameters ({id}), wildcards ({path...}) e host-based routing. A principal lacuna é a falta de subrouters e agrupamento de middleware por grupo de rotas. Se sua aplicação precisa apenas de CRUD com method routing, o ServeMux é suficiente. Se precisa de middleware groups, chi continua sendo a melhor opção.
Como extrair path parameters no Go 1.22+?
Use r.PathValue("nome") onde nome corresponde ao placeholder {nome} na definição da rota. Por exemplo, se a rota é GET /usuarios/{id}, extraia com r.PathValue("id"). Para wildcards como {caminho...}, o valor inclui todas as barras intermediárias. O valor retornado é sempre uma string — converta conforme necessário.
Qual a diferença entre {nome} e {nome…} no ServeMux?
{nome} captura um único segmento do path (sem barras). {nome...} (com reticências) captura todo o restante do path, incluindo barras. Exemplo: com rota GET /arquivos/{path...}, uma requisição para /arquivos/docs/guia/intro.md terá r.PathValue("path") igual a docs/guia/intro.md. Use wildcards para servir arquivos estáticos ou implementar catch-all routes.
O ServeMux retorna 405 automaticamente?
Sim. Se você registrar GET /api/dados e POST /api/dados, uma requisição PUT /api/dados receberá automaticamente 405 Method Not Allowed com o header Allow: GET, POST. Esse é um comportamento HTTP correto que o ServeMux implementa sem código adicional. Antes do Go 1.22, você precisaria tratar isso manualmente em cada handler.