Frameworks Web em Go em 2026: Comparativo entre net/http, chi, Gin, Echo e Fiber

Cinco pilhas HTTP de Go, uma bancada de benchmark, números reais.

Conteúdo da página

O Go inclui um servidor HTTP com capacidade para produção em sua biblioteca padrão, e desde o Go 1.22, o roteador incorporado lida com correspondência de métodos e parâmetros de caminho por conta própria. A questão de qual framework usar não desapareceu, mas se estreitou.

Cinco stacks cobrem quase todas as decisões de backend em Go em 2026: a biblioteca padrão net/http, chi, Gin, Echo e Fiber. Cada um ocupa uma posição diferente na mesma compensação (tradeoff) — o que a biblioteca lhe oferece versus o que você deve a ela em dependências, convenções e compatibilidade de ecossistema. Este artigo os compara em poder de roteamento, ergonomia dos handlers, middlewares e desempenho medido, terminando com uma tabela de decisão em vez de um único veredito.

Se você está partindo do zero e deseja o processo completo de construção ao redor da sua escolha — validação, autenticação, testes, implantação — a versão passo a passo está em Construindo APIs REST em Go. Aqui, o foco permanece nos próprios stacks.

cinco blocos de vidro 3D conectados por linhas brilhantes, um para cada stack HTTP do Go

A reinicialização do ServeMux pós-1.22

O motivo pelo qual o debate sobre frameworks mudou de forma é uma única adição à biblioteca padrão. O Go 1.22 deu ao http.ServeMux padrões com consciência de métodos e parâmetros de caminho:

mux := http.NewServeMux()
mux.HandleFunc("GET /users/{id}", func(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")
    // ...
})
mux.HandleFunc("POST /users", createUser)

Antes disso, qualquer API que precisasse de GET /users/{id} exigia um roteador de terceiros, o que é como Gin, chi e Echo se tornaram padrões. Em 2026, a biblioteca padrão cobre rotas estáticas, correspondência de métodos, parâmetros de segmento único e curingas (wildcards), além de middlewares por meio de simples encapsulamento de funções. O que ela ainda não lhe oferece: grupos de rotas, cadeias de middlewares composáveis, binding de structs ou tipos de parâmetros por rota.

Os concorrentes

net/http (stdlib) chi v5.3.2 Gin v1.12.0 Echo v4.16.0 Fiber v2.52.15
Motor HTTP net/http net/http net/http net/http fasthttp
Assinatura do Handler func(w, r) func(w, r) func(c *gin.Context) func(c echo.Context) error func(c *fiber.Ctx) error
Compatível com http.Handler sim sim sim sim não
Ecossistema de Middlewares em crescimento, formato stdlib formato stdlib o maior, específico do Gin grande, específico do Echo em crescimento, específico do fasthttp
Binding/Validação manual manual tags integradas integrado via auxiliares
HTTP/2 sim sim sim sim não
Dependências 0 1 ~6 ~8 ~4

Todos os cinco usam roteadores de estilo trie ou radix com parâmetros de caminho e rotas agrupadas. A divisão estrutural é a última linha da história da assinatura do handler: chi e stdlib falam http.Handler puro; Gin, Echo e Fiber encapsulam requisições em seu próprio tipo de contexto. O Fiber é o outlier novamente porque está baseado no fasthttp, e não no net/http de todo.

O que eu benchmarkei

Em vez de citar benchmarks sintéticos de roteadores, executei o mesmo serviço em todos os cinco stacks. O aparato:

  • Go 1.27.1, Gin v1.12.0, chi v5.3.2, Echo v4.16.0, Fiber v2.52.15
  • Intel Core Ultra 7 265, Linux, 20 threads, localhost, sem TLS
  • Três rotas por stack: GET /health, GET /users/{id}, GET /api/v1/users/{uid}/orders/{oid}
  • Carga: 8 workers keep-alive por 3 segundos contra a rota de dois parâmetros, após uma passagem de aquecimento
  • Cada handler serializa um pequeno documento JSON por requisição com encoding/json — portanto, os números incluem serialização, não apenas roteamento

A versão da biblioteca padrão da rota quente:

mux.HandleFunc("GET /api/v1/users/{uid}/orders/{oid}", func(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    w.Write(orderBody(r.PathValue("uid"), r.PathValue("oid")))
})

e o equivalente no Fiber:

app.Get("/api/v1/users/:uid/orders/:oid", func(c *fiber.Ctx) error {
    c.Set("Content-Type", "application/json")
    return c.Send(orderBody(c.Params("uid"), c.Params("oid")))
})

Duas notas de honestidade antes dos números. Primeiro, esta é uma microcarga de trabalho de localhost: sem banco de dados, sem handshake TLS, sem cadeia de middlewares. Serviços reais têm gargalo em E/S — a parte do framework em uma requisição que consulta PostgreSQL via GORM, Ent, Bun ou sqlc é um erro de arredondamento. Segundo, cada stack recebeu seu próprio processo de servidor e uma forma de handler idêntica, então a comparação é entre stacks, não entre configurações ajustadas manualmente.

Resultados

Taxa de transferência (throughput) na rota dinâmica de dois parâmetros:

Stack Requisições/seg Latência média
Fiber (fasthttp) 205.958 0,034 ms
Echo 169.826 0,042 ms
Gin 169.715 0,042 ms
net/http ServeMux 169.708 0,042 ms
chi 163.017 0,044 ms

Os quatro stacks baseados em net/http ficam dentro de 4% entre si. O motor fasthttp do Fiber está cerca de 21% à frente neste trabalho de localhost — real, mas o tipo de diferença que uma única ida e volta ao banco de dados (0,5–5 ms) torna irrelevante em produção.

Para ver para onde o custo por requisição vai na verdade, também benchmarkizei a invocação do handler sem a rede, chamando cada roteador diretamente através de http.Handler.ServeHTTP (Fiber através de seu handler fasthttp):

Stack Tempo/op Bytes/op Alocações/op
Gin 289 ns 192 B 3
Echo 333 ns 192 B 3
net/http ServeMux 430 ns 240 B 5
Fiber 541 ns* 192 B 3
chi 592 ns 897 B 7

* A figura do Fiber carrega overhead do aparato (harness) — o teste tem que copiar a requisição para o contexto reutilizável do fasthttp em cada iteração, o que o servidor real evita lendo do cabeamento (wire). Trate-a como um limite superior; a taxa de transferência no nível do servidor acima é a comparação justa para o Fiber.

O perfil de alocação é a história mais duradoura. Gin e Echo fazem o pool de seus contextos de requisição, então uma requisição através deles custa 3 alocações. As 5 alocações da biblioteca padrão vão para a própria infraestrutura de escopo de requisição. O chi armazena parâmetros de rota no context.Context da requisição, o que aparece como 897 B e 7 alocações por requisição — o preço de permanecer um http.Handler puro com informações ricas de rota.

Fiber e a compensação com fasthttp

O Fiber é o stack mais rápido no benchmark de servidor, e a velocidade vem com uma fatura de compatibilidade. O fasthttp é uma implementação HTTP feita do zero com seus próprios tipos de requisição/resposta, então:

  • Middlewares net/http não se acoplam. Qualquer biblioteca que espere um http.Handler ou um *http.Request precisa de uma conversão ou de um port nativo do Fiber.
  • Sem HTTP/2. O fasthttp não o implementa; o net/http do Go obtém h2 gratuitamente da biblioteca padrão sobre TLS.
  • WebSocket, streaming e truques no estilo http.Flusher todos tomam o caminho do fasthttp, que difere do comportamento da biblioteca padrão que a maior parte da documentação Go assume.

O que o Fiber compra em troca: os números mais rápidos na tabela acima, uma API parecida com Express e alocações menores do que os benches de framework sugerem, uma vez que a vantagem de parsing do cabeamento (wire) é incluída. Ele se encaixa quando as respostas são pequenas, o roteamento domina, o HTTP/2 é terminado a montante (upstream) e o middleware de que você precisa existe no ecossistema Fiber. Essas condições se mantêm para alguns serviços de borda (edge) e quase para nada mais.

chi: o caminho do meio chato

O chi é um roteador e um pipeline de middlewares, nada mais. Os handlers são func(w, r), o middleware é func(http.Handler) http.Handler, e tudo escrito para a biblioteca padrão se acopla sem adaptadores:

r := chi.NewRouter()
r.Use(RequestID, RealIP, Logger, Recoverer)
r.Route("/api/v1", func(r chi.Router) {
    r.Get("/users/{uid}/orders/{oid}", getOrder)
})

Esse é o todo o argumento de venda: grupos de rotas e uma pilha de middlewares por cima da interface padrão. A tabela de alocação o preçifica honestamente — contexto de rota mais rico por requisição do que apenas a biblioteca padrão, em troca de middlewares composáveis e rotas aninhadas. Para serviços que querem estrutura sem uma identidade de framework, chi mais net/http é a configuração para a qual a maioria das equipes Go converge.

Gin vs Echo, em código

Gin e Echo são gêmeos próximos em desempenho — dentro do ruído em ambos os benchmarks — então a escolha entre eles é gosto pela API e filosofia de tratamento de erros.

O Echo centraliza erros em um HTTPErrorHandler. Os handlers retornam error, e uma função mapeia erros para respostas:

func getOrder(c echo.Context) error {
    o, err := store.Order(c.Param("uid"), c.Param("oid"))
    if err != nil {
        return echo.NewHTTPError(http.StatusNotFound, "order not found")
    }
    return c.JSON(http.StatusOK, o)
}

Os handlers do Gin não retornam nada; os erros se acumulam no contexto e você os renderiza onde escolher:

func getOrder(c *gin.Context) {
    o, err := store.Order(c.Param("uid"), c.Param("oid"))
    if err != nil {
        c.JSON(http.StatusNotFound, gin.H{"error": "order not found"})
        return
    }
    c.JSON(http.StatusOK, o)
}

O modelo do Echo mapeia limamente para a arquitetura de tratamento de erros em Go com erros embrulhados (wrapped) e tratamento de fronteira centralizado — um único lugar traduz resultados de errors.Is em códigos de status. O modelo do Gin corresponde ao padrão mais comum de decidir por handler. Além dos erros, as vantagens decisivas do Gin são o tamanho do ecossistema e o binding de structs integrado com tags de validação; as do Echo são uma API de contexto consistente e middlewares integrados mais fortes. Ambos integram a geração de Swagger através de seus próprios adaptadores — a configuração por framework é coberta em Adicionando Swagger à Sua API em Go.

Um eixo a mais: propagação de contexto. Gin e Echo encapsulam context.Context dentro de seus próprios tipos de contexto, então passar cancelamento para os handlers requer um passo de extração. Os padrões para manter cancelamento, timeouts e valores em ordem através de qualquer um desses encapsulamentos estão em Go context.Context Feito da Maneira Certa.

Os timeouts que ninguém configura

Todo stack nesta comparação deixa os timeouts do servidor em padrões zero se você não os definir — e zero significa sem limite. Um cliente que abre uma conexão e não envia nada segura uma goroutine e um descriptor de arquivo indefinidamente:

srv := &http.Server{
    Addr:              ":8080",
    Handler:           mux,
    ReadHeaderTimeout: 5 * time.Second,
    ReadTimeout:       10 * time.Second,
    WriteTimeout:      30 * time.Second,
    IdleTimeout:       120 * time.Second,
}
srv.ListenAndServe()

ReadHeaderTimeout é o primeiro a ser definido — ele limita a exaustão de conexões no estilo slowloris sem quebrar uploads longos da forma como um ReadTimeout abrangente pode. O WriteTimeout deve cobrir a resposta legítima mais lenta, o que o torna uma decisão de política atrelada aos seus orçamentos de timeout, em vez de uma constante de cópia e cola. A mesma disciplina se aplica por framework: Gin, Echo e Fiber aceitam todos a configuração de timeout no nível do servidor, e nenhum deles a ativa por padrão.

Escolhendo

flowchart TD A[Novo serviço HTTP em Go] --> B{Ferramenta interna, plugin,
ou requisito de zero dependências?} B -- sim --> C[net/http ServeMux] B -- não --> D{Quer binding embutido,
validação, grande catálogo de middlewares?} D -- não --> E[chi + net/http] D -- sim --> F{Convenção da equipe?} F -- handlers que retornam erro --> G[Echo] F -- maior ecossistema, idiomáticas do Gin --> H[Gin] A --> I{Respostas pequenas, throughput extremo,
sem HTTP/2, terminação TLS a montante?} I -- tudo verdadeiro --> J[Fiber] I -- não --> E
Situação Escolha
Serviço interno, endpoint de CLI, plugin, biblioteca apenas net/http
A maioria das APIs — estrutura sem lock-in de framework chi por cima de net/http
API pública, equipe maior, validação e binding prontos Gin
API pública, tradução centralizada de erros, API de contexto consistente Echo
Gargalo comprovado de roteamento/serialização, JSON minúsculo, sem HTTP/2 Fiber

Se o padrão honesto para um novo serviço em 2026 tiver que ser nomeado: comece com net/http, adicione chi quando middlewares ou grupos de rotas começarem a doer, e mova para Gin ou Echo apenas quando binding, validação ou convenção de equipe justificarem uma identidade de framework. O Fiber permanece uma ferramenta especializada — vale a pena benchmarkizar contra ele quando o throughput é comprovadamente o problema, precificado contra a fatura de compatibilidade que ele cobra.

Migração entre stacks

O custo de migração acompanha a divisão da assinatura do handler. stdlib ↔ chi é mecânico: os handlers mantêm a mesma assinatura, as rotas se movem de padrões mux.HandleFunc para r.Get, e o middleware se acopila ao stack do chi. Gin ↔ Echo é uma tradução de tipo de contexto — c.Param, c.JSON e lógica de middleware portam quase linha a linha. Qualquer coisa se movendo para ou saindo do Fiber é uma reescrita da camada HTTP, porque código baseado em *http.Request, middleware net/http e premissas de h2 não sobrevivem à fronteira do fasthttp; o caminho menos doloroso é manter a lógica de negócio em pacotes livres de framework e regenerar a camada de transporte, o que também é a estrutura que torna a próxima migração barata.

Subscrever

Receba novos artigos sobre sistemas, infraestrutura e engenharia de IA.