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:

FuncionalidadeServeMux (1.22+)chigorilla/muxhttprouter
Method routingSimSimSimSim
Path paramsSim ({id})Sim ({id})Sim ({id})Sim (:id)
WildcardsSim ({p...})Sim (*)SimSim (*filepath)
Middleware groupsNaoSimSimNao
Regex no pathNaoNaoSimNao
SubroutersNaoSimSimNao
Compatível c/ stdlibNativoSimSimAdaptador
Dependência externaNaoSimSimSim

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.Handler sem 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.