Les frameworks web en Go en 2026 : comparaison de net/http, chi, Gin, Echo et Fiber

Cinq piles HTTP Go, un banc d’essai, de vrais chiffres.

Sommaire

Go embarque un serveur HTTP de qualité production dans sa bibliothèque standard, et depuis Go 1.22, le routeur intégré gère lui-même l’association des méthodes et les paramètres de chemin. La question du framework s’est resserrée, mais elle n’a pas disparu.

Cinq stacks couvrent presque toute décision de backend Go en 2026 : la bibliothèque standard net/http, chi, Gin, Echo et Fiber. Chacun adopte une position différente sur le même compromis — ce que la bibliothèque vous donne par rapport à ce que vous lui devez en termes de dépendances, de conventions et de compatibilité avec l’écosystème. Cet article les compare en termes de puissance du routage, d’ergonomie des gestionnaires, de middleware et de performances mesurées, puis se termine par un tableau de décision plutôt que par un verdict unique.

Si vous partez de zéro et souhaitez le processus de construction complet autour de votre choix — validation, authentification, tests, déploiement — la version étape par étape se trouve dans Construire des API REST en Go. Ici, l’accent reste mis sur les stacks eux-mêmes.

cinq blocs en verre 3D connectés par des lignes lumineuses, un par pile HTTP Go

Le réajustement post-1.22 du ServeMux

La raison pour laquelle le débat sur les frameworks a changé de forme tient à un seul ajout à la bibliothèque standard. Go 1.22 a apporté à http.ServeMux des modèles prenant en compte les méthodes et des paramètres de chemin :

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

Avant cela, toute API nécessitant GET /users/{id} exigait un routeur tiers, c’est ainsi que Gin, chi et Echo sont devenus les valeurs par défaut. En 2026, la bibliothèque standard couvre les routes statiques, l’association des méthodes, les paramètres de segment unique et les paramètres génériques, ainsi que le middleware via une simple encapsulation de fonctions. Ce qu’elle ne vous donne toujours pas : les groupes de routes, les chaînes de middleware composables, le bindage de structures ou les types de paramètres par route.

Les candidats

net/http (bibliothèque std) chi v5.3.2 Gin v1.12.0 Echo v4.16.0 Fiber v2.52.15
Moteur HTTP net/http net/http net/http net/http fasthttp
Signature du gestionnaire func(w, r) func(w, r) func(c *gin.Context) func(c echo.Context) error func(c *fiber.Ctx) error
Compatible http.Handler oui oui oui oui non
Écosystème de middleware en croissance, formé à la stdlib formé à la stdlib le plus grand, spécifique à Gin grand, spécifique à Echo en croissance, spécifique à fasthttp
Bindage/validation manuel manuel balises intégrées intégré via des fonctions auxiliaires
HTTP/2 oui oui oui oui non
Dépendances 0 1 ~6 ~8 ~4

Les cinq utilisent des routeurs de type trie ou radix avec paramètres de chemin et routes groupées. La division structurelle réside dans la dernière ligne de l’histoire de la signature des gestionnaires : chi et la bibliothèque standard parlent en http.Handler pur ; Gin, Echo et Fiber encapsulent les requêtes dans leur propre type de contexte. Fiber est encore l’exception car il repose sur fasthttp, et non sur net/http du tout.

Ce que j’ai benchmarké

Plutôt que de citer des benchmark de routeurs synthétiques, j’ai exécuté le même service sur les cinq stacks. L’installation :

  • 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, sans TLS
  • Trois routes par stack : GET /health, GET /users/{id}, GET /api/v1/users/{uid}/orders/{oid}
  • Charge : 8 workers keep-alive pendant 3 secondes sur la route à deux paramètres, après un passage de réchauffement
  • Chaque gestionnaire serialise un petit document JSON par requête avec encoding/json — les chiffres incluent donc la sérialisation, pas seulement le routage

La version bibliothèque standard de la route « chaude » :

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")))
})

et l’équivalent en 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")))
})

Deux notes d’honnêteté avant les chiffres. Premièrement, il s’agit d’une micro-charge en localhost : pas de base de données, pas de poignée de main TLS, pas de chaîne de middleware. Les services réels sont goulots d’étranglement sur l’E/S — la part du framework dans une requête interrogeant PostgreSQL via GORM, Ent, Bun ou sqlc est une erreur d’arrondi. Deuxièmement, chaque stack a eu son propre processus serveur et une forme de gestionnaire identique, la comparaison est donc entre stacks, et non entre configurations ajustées à la main.

Résultats

Débit sur la route dynamique à deux paramètres :

Stack Requêtes/seconde Latence moyenne
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

Les quatre stacks basés sur net/http restent à moins de 4 % les uns des autres. Le moteur fasthttp de Fiber est en avance d’environ 21 % sur cette charge en localhost — un écart réel, mais du type que rendent irrélants un seul aller-retour base de données (0,5–5 ms) en production.

Pour voir où va réellement le coût par requête, j’ai aussi effectué un benchmark de l’appel du gestionnaire sans le réseau, en appelant chaque routeur directement via http.Handler.ServeHTTP (Fiber via son gestionnaire fasthttp) :

Stack Temps/op Octets/op Allouements/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

* Le chiffre de Fiber comporte un surcoût de l’infrastructure de test — le test doit copier la requête dans le contexte réutilisable de fasthttp à chaque itération, ce que le serveur réel évite en lisant depuis le câble. À traiter comme une limite supérieure ; le débit au niveau serveur ci-dessus est la comparaison juste pour Fiber.

Le profil d’allocation est l’histoire plus durable. Gin et Echo mettent leurs contextes de requête en pool, donc une requête qui les traverse coûte 3 allouements. Les 5 allouements de la bibliothèque standard vont à la plomberie elle-même de portée requête. chi stocke les paramètres de route dans le context.Context de la requête, ce qui se traduit par 897 B et 7 allouements par requête — le prix à payer pour rester un http.Handler pur avec des informations de route riches.

Fiber et le compromis fasthttp

Fiber est le stack le plus rapide sur le benchmark serveur, et la vitesse vient avec une facture de compatibilité. fasthttp est une implémentation HTTP de zéro avec ses propres types de requête/réponse, donc :

  • Le middleware net/http ne s’attache pas. Toute bibliothèque attendant un http.Handler ou une *http.Request a besoin d’une conversion ou d’une version native Fiber.
  • Pas de HTTP/2. fasthttp ne l’implémente pas ; net/http de Go obtient h2 gratuitement depuis la bibliothèque standard sur TLS.
  • WebSocket, streaming et astuces de type http.Flusher passent tous par le chemin fasthttp, qui diffère du comportement de la bibliothèque standard que la plupart de la documentation Go suppose.

Ce que Fiber achète en échange : les chiffres les plus rapides du tableau ci-dessus, une API semblable à Express, et des allouements inférieurs à ce que les bancs de framework suggèrent une fois l’avantage de l’analyse du câble inclus. Il convient lorsque les réponses sont petites, que le routage domine, que le HTTP/2 est terminé en amont et que le middleware dont vous avez besoin existe dans l’écosystème Fiber. Ces conditions se réalisent pour certains services de bordure et se réalisent pour presque rien d’autre.

chi : le chemin du milieu ennuyeux

chi est un routeur et un pipeline de middleware, rien de plus. Les gestionnaires sont func(w, r), le middleware est func(http.Handler) http.Handler, et tout ce qui est écrit pour la bibliothèque standard s’attache sans adaptateurs :

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

C’est toute la promesse : des groupes de routes et une pile de middleware au-dessus de l’interface standard. Le tableau des allocations le taxe honnêtement — un contexte de route plus riche par requête que la bibliothèque standard seule, en échange de middleware composables et de routes imbriquées. Pour les services qui veulent de la structure sans identité de framework, chi plus net/http est la configuration sur laquelle la plupart des équipes Go convergent.

Gin vs Echo, en code

Gin et Echo sont presque jumels en performance — dans le bruit dans les deux benchmarks — donc le choix entre eux est une question de goût de l’API et de philosophie de gestion des erreurs.

Echo centralise les erreurs dans un HTTPErrorHandler. Les gestionnaires renvoient error, et une seule fonction mappe les erreurs vers des réponses :

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)
}

Les gestionnaires Gin ne renvoient rien ; les erreurs s’accumulent sur le contexte et vous les rendez là où vous le choisissez :

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)
}

Le modèle d’Echo se mappe proprement sur l’architecture de gestion des erreurs Go avec des erreurs enveloppées et une gestion centralisée des limites — un seul endroit traduit les résultats de errors.Is en codes de statut. Le modèle de Gin correspond au modèle plus courant de décider par gestionnaire. Au-delà des erreurs, les avantages décisifs de Gin sont la taille de l’écosystème et le bindage de structures intégré avec des balises de validation ; ceux d’Echo sont une API de contexte cohérente et un middleware intégré plus robuste. Les deux intègrent la génération de Swagger via leurs propres adaptateurs — la configuration par framework est couverte dans Ajouter Swagger à votre API Go.

Un autre axe : la propagation du contexte. Gin et Echo encapsulent context.Context dans leurs propres types de contexte, donc la transmission de l’annulation aux gestionnaires nécessite une étape d’extraction. Les modèles pour garder l’annulation, les délais d’attente et les valeurs clairs à travers n’importe lequel de ces encapsulateurs se trouvent dans Go context.Context Bien Fait.

Les délais d’attente que personne ne configure

Chaque stack de cette comparaison laisse les délais d’attente du serveur aux valeurs par défaut nulles à moins que vous ne les configuriez — et nul signifie pas de limite. Un client qui ouvre une connexion et n’envoie rien retient une goroutine et un descripteur de fichier indéfiniment :

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 est celui à configurer en premier — il borne l’épuisement de connexions de type slowloris sans casser les longues téléversements de la manière dont un ReadTimeout général peut le faire. WriteTimeout doit couvrir la réponse légitime la plus lente, ce qui en fait une décision politique liée à vos budgets de délai d’attente plutôt qu’une constante à copier-coller. La même discipline s’applique par framework : Gin, Echo et Fiber acceptent tous la configuration des délais d’attente au niveau du serveur, et aucun ne l’active par défaut.

Choisir

flowchart TD A[Nouveau service HTTP Go] --> B{Outil interne, plugin,
ou exigence de zéro dépendance ?} B -- oui --> C[net/http ServeMux] B -- non --> D{Voulez-vous du bindage intégré,
de la validation, un grand catalogue de middleware ?} D -- non --> E[chi + net/http] D -- oui --> F{Convention d'équipe ?} F -- gestionnaires renvoyant des erreurs --> G[Echo] F -- plus grand écosystème, idiomes Gin --> H[Gin] A --> I{Petites réponses, débit extrême,
pas de HTTP/2, terminaison TLS en amont ?} I -- tous vrais --> J[Fiber] I -- non --> E
Situation Choix
Service interne, point de terminaison CLI, plugin, bibliothèque net/http seul
La plupart des API — structure sans verrouillage de framework chi au-dessus de net/http
API publique, équipe plus grande, validation et bindage prêts à l’emploi Gin
API publique, traduction centralisée des erreurs, API de contexte cohérente Echo
Goulot d’étranglement routage/sérialisation prouvé, JSON minuscule, pas de HTTP/2 Fiber

Si la valeur par défaut honnête pour un nouveau service en 2026 doit être nommée : commencez avec net/http, ajoutez chi lorsque le middleware ou les groupes de routes commencent à faire mal, et passez à Gin ou Echo seulement lorsque le bindage, la validation ou la convention d’équipe justifient une identité de framework. Fiber reste un outil spécialisé — vaut la peine d’être benchmarké lorsque le débit s’avère être le problème, prix à comparer à la facture de compatibilité qu’il impose.

Migration entre les stacks

Le coût de migration suit la division de la signature des gestionnaires. bibliothèque standard ↔ chi est mécanique : les gestionnaires conservent la même signature, les routes passent des modèles mux.HandleFunc à r.Get, le middleware s’attache à la pile chi. Gin ↔ Echo est une traduction de type de contexte — c.Param, c.JSON et la logique de middleware se portent presque ligne par ligne. Tout ce qui bouge vers ou depuis Fiber est une réécriture de la couche HTTP, car le code basé sur *http.Request, le middleware net/http et les suppositions h2 ne survivent pas à la frontière fasthttp ; la route la moins douloureuse est de garder la logique métier dans des paquets sans framework et de régénérer la couche de transport, c’est aussi la structure qui rend la prochaine migration moins coûteuse.

Liens utiles

S'abonner

Recevez de nouveaux articles sur les systèmes, l'infrastructure et l'ingénierie IA.