Backend · Go · Octobre 2026

Quel framework Go choisir quand aucun ne s’impose ?

Vous démarrez un backend en Go, et chacun vous conseille un outil différent. En Java ou en Python, la question serait presque réglée d’avance. En Go, elle reste ouverte. Voici de quoi la trancher selon votre situation, et pourquoi le critère qui compte le plus n’est pas celui qu’on regarde d’habitude.

Écrit pour
  • les tech leads
  • les devs

Demandez à une équipe Java avec quoi elle démarre un nouveau backend : la réponse tient le plus souvent en deux mots, Spring Boot. En Python, ce sera Django ou FastAPI ; en Ruby, Rails ; en PHP, Laravel ou Symfony ; en C#, ASP.NET Core. Des alternatives sérieuses existent partout, mais elles se définissent par rapport au grand nom.

Posez la même question à des développeurs Go, et vous aurez cinq réponses. L’une d’elles sera presque toujours « pas besoin de framework ». Les autres citeront Gin, le plus connu, Echo, Chi, Fiber, et depuis peu Huma ou Encore. Pour comprendre cette diversité, et pour choisir, il faut partir du début : ce que fait un backend.

Cet article décrit l’écosystème tel qu’il est fin octobre 2026 : Go 1.27, Chi v5, Gin 1.12, Echo v5, Fiber v3, Huma v2. Les grandes lignes tiendront longtemps, parce qu’elles reposent sur la bibliothèque standard, qui bouge peu. Les frameworks, eux, publient de nouvelles versions majeures : vérifiez celle que vous adoptez.
Partie 1 · backend

Ce que fait un backend, et ce qu’un framework lui apporte

Une application backend a une mission principale : recevoir des requêtes, les traiter, et répondre. Ces requêtes arrivent presque toujours en HTTP, le protocole du web : c’est le cas d’une API REST, appelée par un navigateur ou une application mobile.

Prenons un exemple qui nous suivra tout l’article : Carnet, un petit service fictif écrit en Go, qui gère des carnets de notes. Un client lui demande le carnet n° 42. Suivons sa requête, de son arrivée à la réponse :

Le clientun navigateur, une application mobile : il envoie une requête, puis attend la réponse.
la requêteGET /carnets/42 la réponse200 OKet le carnet en JSON
Le backendle programme Carnet, écrit en Go : le langage est le cadre de tout le reste
  1. 1Serveur HTTPreçoit la connexion et décode la requête HTTPbibliothèque standard
  2. 2Routeurchoisit la fonction qui répond, selon la méthode et l’adressebibliothèque standard
  3. 3Middlewarestraitements communs à toutes les requêtes : journal, authentification…framework, facultatif
  4. 4Handlerla fonction qui répond : lit la demande, appelle le métier, écrit la réponsevotre code
  5. 5Métierles règles de l’application, la base de donnéesvotre code
1 à 3 : la plomberie, la même pour toutes les applications4 et 5 : ce qui fait votre application
votre codeframeworkbibliothèque standardlangage, le cadre
La requête traverse les cinq étapes, puis la réponse repart vers le client. Les couleurs disent qui fournit chaque étape dans un backend Go ; ce sont les mêmes dans tous les schémas de l’article.

Les étapes 1 à 3 se ressemblent d’une application à l’autre, et personne n’a envie de les réécrire à chaque fois. C’est le rôle du framework :

Définition

Un framework est un ensemble d’outils déjà écrits qui prend en charge la plomberie d’une application (ici, les étapes 1 à 3) et impose une façon d’organiser le reste. Il ne change pas le langage : c’est du code écrit dans ce langage, que vous ajoutez à votre projet.

Dans le schéma, pourtant, un seul bloc porte la couleur du framework, et il est en pointillé. C’est qu’en Go la bibliothèque standard, livrée avec le langage, assure déjà l’essentiel de la plomberie. Comparons avec Java, où c’est le framework qui fait tout :

votre codeframeworkbibliothèque standardlangage
Mêmes couches, mêmes couleurs : seule la taille change. En Java, le framework assure toute la plomberie, serveur HTTP compris. En Go, la bibliothèque standard en assure déjà l’essentiel : le framework n’est plus qu’une couche fine, et facultative. Les numéros renvoient aux étapes du trajet.

=== RUN TestCeQueFaitUnBackend --- PASS: TestCeQueFaitUnBackend (partie 1)

Un backend, ce sont cinq étapes, et un framework en prend en charge la plomberie. En Go, la bibliothèque standard en fait déjà l’essentiel. Ce n’est pas un hasard : c’est une philosophie.

Partie 2 · Philosophie et histoire de Go
Partie 2 · philosophie

Philosophie et histoire de Go

L’absence d’un grand framework en Go n’est ni un retard ni un accident. Elle découle de choix faits dès la conception du langage : une culture qui se méfie de la magie, et une bibliothèque standard à qui l’on confie l’essentiel. Deux événements récents les ont renforcés.

Une culture qui se méfie de la magie

Go a été conçu chez Google pour de grandes bases de code maintenues par beaucoup de monde, avec une obsession : que le code se lise sans surprise.

Pas de magie

Pas d’annotations qui génèrent du comportement, pas d’héritage : on lit le trajet d’une requête de haut en bas.

Des dépendances passées à la main

Pas de conteneur d’injection dans la bibliothèque standard : on passe ce dont on a besoin par les constructeurs.

La compatibilité Go 1

Un programme écrit pour Go 1.0 compile encore aujourd’hui : la bibliothèque standard est le pari le plus sûr à long terme.

Un peu de copie vaut mieux qu’un peu de dépendance.

Rob Pike, l’un des créateurs de Go, dans ses « proverbes Go », 2015 (traduit de l’anglais)

Tout ce qui fait la puissance de Spring, la configuration par convention, les annotations, l’injection automatique, est précisément ce que cette culture regarde avec méfiance. Un framework Go doit justifier chaque ligne qu’il ajoute.

Tout miser sur la bibliothèque standard

Cette culture a une conséquence directe : plutôt que de laisser l’écosystème fournir l’essentiel, Go le livre avec le langage. Ailleurs, la bibliothèque standard sait parler HTTP, mais on ne lui confie pas la production : la documentation de Python le dit en toutes lettres de son module http.server, et en Java le serveur vient historiquement de Tomcat ou de Jetty. En Go, la bibliothèque standard couvre presque tout ce qu’un backend utilise au quotidien :

net/httpserveur, client, routeur, fichiers statiques
encoding/jsonlecture et écriture du JSON
encoding/json/v2le nouveau JSON, par défaut depuis Go 1.27
html/templategabarits HTML, avec échappement selon le contexte
crypto/tlsTLS, sans OpenSSL
database/sqlaccès aux bases, pool de connexions
log/slogjournaux structurés (Go 1.21)
contextannulation et délais, de bout en bout
net/http/httptesttests de handlers, sans réseau
net/http/pprofprofilage en production

net/http, sa pièce maîtresse

Pour un backend web, la pièce maîtresse de cette bibliothèque est net/http, le package qui assure les étapes 1 et 2 du trajet. Trois raisons en font le centre de gravité de tout l’écosystème :

Livré avec Go

Rien à installer : il est là depuis Go 1.0, maintenu par l’équipe du langage et couvert par sa promesse de compatibilité.

Prêt pour la production

HTTP/1.1 et HTTP/2, délais d’expiration, arrêt propre, reverse proxy. Et depuis Go 1.22, un vrai routeur.

Une prise standard

Presque tous les frameworks Go sont construits dessus, et une seule interface relie tout l’écosystème : http.Handler.

Cette prise standard mérite qu’on s’y arrête, car toute la suite en dépend. C’est une interface d’une seule méthode, appelée à l’étape 4 pour faire répondre votre handler :

Bibliothèque standard, package net/http
type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}

// Un middleware n’est qu’une fonction
// qui enveloppe un Handler dans un autre.
func Journal(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		début := time.Now()
		next.ServeHTTP(w, r)
		slog.Info("requête", "chemin", r.URL.Path, "durée", time.Since(début))
	})
}

Comme une prise électrique normalisée : tout ce qui a cette forme se branche sur tout le reste, sans adaptateur.

Les middlewares de l’écosystème : CORS, limitation de débit, compression, authentification.
L’instrumentation : otelhttp, d’OpenTelemetry, enveloppe n’importe quel handler.
Les tests : httptest appelle un handler sans ouvrir de port.
Les garde-fous : http.MaxBytesHandler, http.TimeoutHandler, et depuis Go 1.25 http.CrossOriginProtection contre les attaques CSRF.
Les autres protocoles : un service ConnectRPC, un serveur gRPC via ServeHTTP, un reverse proxy : tous des http.Handler.

Un framework qui garde cette prise hérite de tout cela gratuitement. Un framework qui la remplace doit réécrire ces briques ou fournir des adaptateurs.

Deux événements qui ont changé la donne

Décembre 2022 Gorilla est archivé

Faute de mainteneurs, le projet Gorilla passe en lecture seule, et avec lui gorilla/mux, l’un des routeurs les plus utilisés de l’écosystème. De nouveaux mainteneurs le reprennent à l’été 2023, mais la leçon est restée.

Février 2024 Go 1.22 : le routeur entre dans la bibliothèque standard

Le routeur standard apprend à distinguer les méthodes HTTP et à lire un paramètre du chemin ("GET /carnets/{id}", r.PathValue("id")), avec une règle claire quand deux routes se recouvrent : la plus précise gagne.

Le premier a montré, à grande échelle, ce que vaut la prise standard : comme gorilla/mux la garde, les équipes concernées ont pu changer de routeur en ne réécrivant que la déclaration des routes. Le second a retiré aux routeurs tiers leur argument le plus simple. Depuis, « la bibliothèque standard suffit » n’est plus une posture de puriste, mais une option raisonnable pour beaucoup de services.

En Go, un framework est un assemblage

Dernière différence, celle qui déroute le plus en venant de Java : Spring Boot couvre toute la pile, alors qu’un framework web Go ne couvre presque toujours que la couche HTTP. Le reste se choisit pièce par pièce, quel que soit le framework.

BesoinAvec Spring BootEn Go, dans la bibliothèque standardEn Go, bibliothèques courantes
Serveur HTTPTomcat embarquénet/httprarement remplacé (fasthttp pour Fiber)
Routage, middlewaresSpring MVC, filtreshttp.ServeMux, fonctions qui enveloppent un handlerChi Gin Echo Fiber…
Lecture et validation des entréesBean Validationencoding/json, vérifications à la maingo-playground/validator, Huma
Configurationapplication.ymlos.Getenv, flagenvconfig, koanf, Viper
Injection de dépendancesle conteneur Springles constructeurs, à la mainuber-go/fx (rare)
Accès aux donnéesSpring Data, JPAdatabase/sqlpgx, sqlc, GORM, ent
MigrationsFlyway, Liquibase—goose, golang-migrate, Atlas
Documentation de l’APIspringdoc—Huma, oapi-codegen, swaggo
ObservabilitéActuator, Micrometerlog/slog, pprofOpenTelemetry, Prometheus
TestsSpring Testtesting, httptesttestify, testcontainers-go

La colonne du milieu est presque pleine. En Go, « choisir un framework » veut donc dire choisir la couche qui transforme une requête HTTP en appel à votre code, et décider à quelle distance de la bibliothèque standard on la place. Seules exceptions : quelques plateformes complètes, comme Encore, qui prennent en charge une grande partie du tableau.

=== RUN TestPhilosophieDeGo --- PASS: TestPhilosophieDeGo (partie 2)

En Go, un framework ne vous apporte pas le serveur : il apporte du confort par-dessus la bibliothèque standard, à une distance plus ou moins grande. Pour comparer ces distances, et le reste, il faut des critères.

Partie 3 · Les critères de choix
Partie 3 · critères

Les critères pour choisir un framework Go

Pour comparer des outils aussi différents, il faut une grille. Huit critères la composent. Le premier domine tous les autres, parce qu’il découle directement de la philosophie du langage. Et celui qu’on cite toujours en premier, la vitesse, n’en fait presque pas partie.

  1. 1La distance à la bibliothèque standard
  2. 2La gestion des erreurs
  3. 3La lecture des entrées
  4. 4Le contrat et la documentation
  5. 5L’observabilité
  6. 6La maintenance
  7. 7Les protocoles
  8. 8L’équipe
  9. ✕La vitesse brute

1La distance à la bibliothèque standard, le critère par excellence

Go mise tout sur sa bibliothèque standard, et pour le web sur net/http. Un framework Go se juge donc d’abord à ce qu’il met entre votre code et elle :

Définition

La distance d’un framework à la bibliothèque standard, c’est tout ce qu’il remplace par ses propres types et ses propres conventions : la façon d’écrire un handler, le routeur, parfois même le serveur HTTP.

Cette distance a un prix, qu’on ne paie qu’au moment de partir :

Conséquence

Le coût de sortie, c’est ce qu’il faudrait réécrire le jour où l’on quitte le framework, pour revenir à la bibliothèque standard ou passer à un autre outil.

Ce jour arrive plus souvent qu’on ne le croit :

L’outil n’est plus maintenu Une version majeure casse votre code Vos besoins changent : gRPC, contrat publié, performances

Comment la mesurer : la signature

La distance se lit en une ligne, à l’étape 4 du trajet : la signature des handlers, c’est-à-dire ce qu’ils reçoivent et ce qu’ils renvoient. Plus elle s’éloigne de la prise standard, http.Handler, plus la distance est grande. Voici la signature d’un handler dans cinq outils :

func(w http.ResponseWriter, r *http.Request)net/httpChi
func(c *gin.Context)Gin
func(c *echo.Context) errorEcho v5
func(c fiber.Ctx) errorFiber v3
func(ctx context.Context, in *Input) (*Output, error)Huma

La première ligne est celle de la bibliothèque standard depuis Go 1.0. Chaque type qui remplace http.ResponseWriter et *http.Request apporte du confort… et une attache à son outil.

Voici ce que cela change pour deux projets identiques, l’un écrit avec Chi, l’autre avec Gin, au moment de les quitter :

à réécrire pour partirsurvit
Gin en fait plus pour vous, et c’est précisément pour ça que votre code lui est davantage lié. Le métier survit dans les deux cas, à condition de ne jamais importer le framework.
Plus un outil met de distance entre votre code et la bibliothèque standard, plus il en fait pour vous, et plus la sortie coûte.

Ce n’est pas un défaut : c’est un échange, confort contre liberté. Mais il se décide le premier jour, pas le jour du départ.

Les sept autres critères

Une fois la distance mesurée, reste à savoir ce que le framework vous donne en échange. Sept critères le disent.

2

La gestion des erreurs

Le critère le plus sous-estimé. Un handler qui renvoie une erreur (Echo Fiber Huma, ou un adaptateur maison sur Chi) centralise la traduction des erreurs en réponses : format JSON uniforme, codes cohérents, journalisation au même endroit. Un handler qui ne renvoie rien oblige chaque développeur à y penser, à chaque if err != nil.

À vérifier : comment une erreur métier devient-elle une réponse HTTP, et où ce code vit-il ?

3

La lecture et la validation des entrées

Gin et Echo lient le corps de la requête à une structure et la valident par étiquettes. Huma va plus loin et génère la validation à partir des types. La bibliothèque standard ne fournit que le décodage ; depuis Go 1.27, encoding/json repose sur la nouvelle implémentation v2.

À vérifier : une requête malformée produit-elle une erreur claire, au même format partout ?

4

Le contrat et la documentation

Trois approches cohabitent : générer la spécification depuis le code (Huma, ou des commentaires avec swaggo), générer le code depuis la spécification (oapi-codegen ogen), ou décrire l’API dans un langage dédié (Goa, Protobuf). Si d’autres équipes consomment votre API, c’est souvent ce critère qui doit choisir le framework, et non l’inverse.

À vérifier : la documentation peut-elle diverger du code ? Si oui, elle divergera.

5

L’observabilité

OpenTelemetry fournit otelhttp, qui instrumente n’importe quel http.Handler, et otelgin pour Gin. L’instrumentation d’Echo, elle, a été retirée du dépôt communautaire d’OpenTelemetry en octobre 2026, et vit désormais chez Echo ; celle de Fiber est maintenue par l’équipe de Fiber. Plus on s’éloigne de la bibliothèque standard, plus on dépend de l’écosystème propre du framework.

À vérifier : qui maintient l’instrumentation, et suit-elle les versions majeures du framework ?

6

La maintenance et la gouvernance

Gorilla a montré qu’un projet très utilisé peut s’arrêter. httprouter n’a plus publié depuis 2019, Revel depuis 2022. À l’inverse, 2026 a vu trois versions majeures (Echo v5 Fiber v3 Kratos v3) qui demandent une migration. La promesse de compatibilité de Go ne s’étend pas aux frameworks : c’est à vous de lire leur historique.

À vérifier : la date de la dernière version, le nombre de mainteneurs actifs, la fréquence des ruptures.

7

Les protocoles

HTTP/2, h2c derrière un répartiteur de charge, flux, WebSocket, gRPC sur le même port : tout cela suppose net/http ou un équivalent. fasthttp, le moteur de Fiber, ne parle pas HTTP/2. Si votre service doit un jour parler gRPC, restez proche de la bibliothèque standard : c’est le cas que je détaille dans mon article sur Go et Rust.

À vérifier : les protocoles dont vous aurez besoin dans deux ans, pas seulement aujourd’hui.

8

L’équipe

Le critère qui tranche souvent, et qu’on avoue rarement. Une équipe Go expérimentée sera à l’aise avec la bibliothèque standard ou Chi. Une équipe qui découvre Go sera productive plus vite avec Gin ou Echo, dont la documentation et les exemples sont abondants. Une équipe qui vient de Node.js sera tentée par Fiber, à cause de sa ressemblance avec Express : c’est une bonne raison de l’essayer, pas une raison suffisante de l’adopter.

À vérifier : qui maintiendra ce code dans trois ans, et ce que ces personnes connaissent déjà.

Et la vitesse ?

Ouvrez n’importe quel comparatif de frameworks Go : il commence par un graphique de requêtes par seconde. C’est le critère le plus visible et, pour la plupart des projets, le moins pertinent. Le banc d’essai de routeurs le plus cité, go-http-routing-benchmark, n’est plus mis à jour depuis 2020, et les résultats publiés dans son README ont été mesurés avec Go 1.3, en 2014. On y voit qu’un routeur rapide trouve une route en une centaine de nanosecondes, et un routeur lent en quelques microsecondes. La différence est réelle, mais il faut la remettre à sa place.

Schéma du temps passé par une requête d’API typique. Le routage n’occupe qu’un trait presque invisible. Les middlewares, le décodage et l’encodage JSON occupent de petites portions. La requête à la base de données et le réseau occupent l’essentiel de la barre. Où passe le temps d’une requête d’API typique schéma sans échelle : les proportions sont indicatives base de données, cache, services tiers réseau routage : ce que mesurent les benchmarks middlewares décodage et validation logique encodage de la réponse
Un routeur se mesure en nanosecondes ou en microsecondes, une requête à une base de données en millisecondes. Tant que votre service attend surtout des entrées-sorties, changer de framework pour gagner du temps de routage revient à alléger la poignée d’une valise pleine.

Le moteur HTTP, en revanche, peut compter, dans un cas précis : un serveur qui reçoit un très grand nombre de petites requêtes et ne fait presque rien de chacune (un proxy, une passerelle, un collecteur d’événements). La seule façon de le savoir, c’est de profiler votre service, avec pprof, sous votre charge. Si le serveur HTTP n’apparaît pas en haut du profil, le framework n’est pas votre problème de performance.

=== RUN TestCriteres --- PASS: TestCriteres (partie 3)

Huit critères, dont un qui domine, et une vitesse remise à sa place. On peut enfin poser la bonne question.

Partie 4 · La bonne question
Partie 4 · question

La bonne question

Avec ces critères en main, on peut reformuler la question de départ. Celle qu’on pose partout n’a pas de réponse ; celle qu’il faut se poser en a une, différente pour chaque projet.

La question qu’on pose partout Quel est le meilleur framework Go ?
La question à se poser De quoi mon équipe a-t-elle besoin que la bibliothèque standard ne donne pas, et combien coûtera la sortie si je dois changer d’avis ?

La première question n’a pas de réponse : les classements qui prétendent y répondre comparent surtout des microbenchmarks. La seconde en a une, différente pour chaque projet. Je dirai aussi lequel j’aime le plus, et pourquoi.

Chaque moitié de la bonne question se pèse avec les critères de la partie 3 :

Première moitié · ce qu’on gagne De quoi mon équipe a-t-elle besoin que la bibliothèque standard ne donne pas ?

Elle se pèse avec les critères 2 à 8 :

  • 2erreurs
  • 3entrées
  • 4contrat
  • 5observabilité
  • 6maintenance
  • 7protocoles
  • 8équipe
Seconde moitié · ce qu’on paiera Combien coûtera la sortie si je dois changer d’avis ?

Elle se lit avec le critère 1 :

  • 1la distance à la bibliothèque standard
La règle qui en découle : prenez l’outil le plus proche de la bibliothèque standard qui couvre vos besoins.

Pour l’appliquer, sept questions concrètes, à poser en équipe avant d’écrire la première route. Les réponses désignent presque toujours le bon candidat :

  1. Qu’est-ce que la bibliothèque standard ne me donne pas ?critères 2 à 8Faites-en la liste, par écrit. Si elle est courte, la bibliothèque standard et vingt lignes d’adaptateur suffisent peut-être.
  2. Qui consomme cette API ?critère 4Vos propres applications, ou des partenaires ? Si un contrat publié est un livrable, les outils « contrat d’abord » passent devant tous les autres.
  3. Quels protocoles dans deux ans ?critère 7HTTP/2, gRPC, flux, WebSocket : tout ce qui s’ajoute plus tard est plus simple près de la bibliothèque standard.
  4. Qui maintiendra ce code ?critère 8Pas l’équipe d’aujourd’hui : celle de dans trois ans. Ce qu’elle connaît déjà, et ce qu’elle trouvera facilement en ligne.
  5. Qui maintient l’outil, et à quel rythme casse-t-il ?critère 6Date de la dernière version, nombre de mainteneurs actifs, historique des versions majeures.
  6. Que faudrait-il réécrire pour partir ?critère 1Regardez la signature des handlers. C’est la réponse la plus honnête que vous obtiendrez.
  7. Mon code métier importe-t-il le framework ?critère 1La réponse doit être non, quel que soit l’outil choisi. C’est la seule règle qui vaut pour tous les candidats.

=== RUN TestLaBonneQuestion --- PASS: TestLaBonneQuestion (partie 4)

La question est posée, la grille est prête. Voyons ce que chaque candidat y répond.

Partie 5 · Les candidats, un par un
Partie 5 · candidats

Les candidats, un par un

Sept candidats, rangés du plus proche de la bibliothèque standard au plus lointain. Pour chacun : une présentation, sa position sur les huit critères, les situations où il est le plus à sa place, ses défis et ses risques, et mon verdict.

  1. Distance nullenet/http
  2. FaibleChi
  3. ModéréeHuma
  4. ÉlevéeGinEcho
  5. Très élevéeFiberEncore
Le critère 1, pour les sept candidats. Plus un outil s’éloigne de la bibliothèque standard, plus il en fait pour vous, et plus il y aura à réécrire pour le quitter. Un clic sur un nom mène à sa fiche.

net/http seul

Go 1.27 · aucune dépendancedistance nulle

Ne rien ajouter : le serveur, le routeur et les outils de test de la bibliothèque standard. Depuis Go 1.22, le routeur gère méthodes, paramètres et jokers, et refuse de démarrer si deux routes se recouvrent sans que l’une soit plus précise : l’erreur arrive au démarrage, pas en production.

func(w http.ResponseWriter, r *http.Request)

Sur les huit critères

1 · Distancenulle : rien à réécrire
2 · Erreursécrites à la main
3 · Entréesdécodage seul
4 · Contratrien
5 · Observabilitéotelhttp, slog, pprof
6 · Maintenancel’équipe de Go
7 · ProtocolesHTTP/1.1, HTTP/2
8 · Équipeconventions à poser
Là où il est à sa place
  • les petits services et les outils internes ;
  • les bibliothèques qui exposent un handler ;
  • les sites rendus côté serveur, avec html/template ou templ ;
  • le point de départ d’une passerelle, avec httputil.ReverseProxy.
Défis et risques
  • pas de groupes de routes ni de middlewares par sous-arbre ;
  • chaque erreur s’écrit à la main, suivie d’un return qu’on peut oublier ;
  • au-delà de quelques dizaines de routes, le code d’assemblage grossit.
Verdict

Le meilleur choix quand il suffit, et il suffit plus souvent qu’on ne le croit. Écrivez dès le premier jour un petit adaptateur pour que vos handlers renvoient une erreur : c’est vingt lignes, et cela évite d’en écrire mille.

Le code : GET /carnets/{id} avec la bibliothèque standard
mux := http.NewServeMux()
mux.HandleFunc("GET /carnets/{id}",
	func(w http.ResponseWriter, r *http.Request) {
		c, err := store.Carnet(r.Context(), r.PathValue("id"))
		if errors.Is(err, ErrIntrouvable) {
			http.Error(w, "carnet introuvable", http.StatusNotFound)
			return
		}
		if err != nil {
			http.Error(w, "erreur interne", http.StatusInternalServerError)
			return
		}
		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(c)
	})

Chi

v5.5 · routeur compatibleMon coup de cœurdistance faible

Un routeur « 100 % compatible avec net/http », sans dépendance externe. Il garde la signature standard et ajoute ce qui manque au routeur de Go pour organiser une grosse API : groupes, sous-routeurs, montage de routeurs écrits ailleurs, et un package de middlewares éprouvés.

func(w http.ResponseWriter, r *http.Request)

Une précision : c’est mon coup de cœur. Je n’aime pas Chi parce qu’il fait beaucoup. Je l’aime parce qu’il fait exactement ce qui manque à la bibliothèque standard, et rien d’autre : c’est la plus petite distance qu’on puisse prendre avec elle tout en gagnant vraiment quelque chose. Cinq raisons :

  1. La signature standard. Tout l’écosystème se branche sans adaptateur : otelhttp, httptest, CORS, et même un serveur gRPC monté sur une route.
  2. La composition. Route, Group et Mount : chaque module de l’application expose son propre routeur, que main assemble.
  3. Des middlewares sobres. Identifiant de requête, récupération des paniques, délais, compression : chacun tient en un fichier qu’on lit en cinq minutes.
  4. Aucune dépendance, une API stable. Sa v5 date de 2021 : mes routeurs écrits il y a des années compilent toujours.
  5. Il s’efface. Depuis la v5.0.12, il remplit r.PathValue : mes handlers n’importent pas Chi, qui ne vit que dans le fichier des routes.

Sur les huit critères

1 · Distancefaible : seules les routes
2 · Erreurspar un adaptateur maison
3 · Entréesà votre charge
4 · Contratvia Huma
5 · Observabilitéotelhttp, directement
6 · Maintenancev5 stable depuis 2021
7 · Protocolestous ceux de net/http
8 · Équipeconventions à poser
Là où il est à sa place
  • l’API d’un produit appelé à grandir, avec des zones aux règles différentes (public, connecté, administration) ;
  • un backend gRPC avec un peu de REST, grpc-go ou ConnectRPC monté sur le même routeur ;
  • un socle interne partagé par plusieurs équipes ;
  • un site rendu côté serveur aux nombreuses pages.
Défis et risques
  • il ne choisit rien pour vous : erreurs, validation et documentation restent à votre charge ;
  • sur un même routeur, les middlewares doivent être déclarés avant les routes, sinon il refuse de démarrer ;
  • middleware.RealIP fait confiance aux en-têtes X-Forwarded-For et X-Real-IP : à réserver aux services placés derrière un proxy de confiance.
Verdict

Mon point de départ pour une API qui va grandir. Décidez tôt de vos conventions d’erreurs, de validation et de journalisation, et écrivez-les une fois : mon squelette, ci-dessous, le fait en quelques lignes. Quand il faut un contrat OpenAPI, ne changez pas de routeur : posez Huma dessus.

gorilla/muxv1.8 · repris en 2023Même signature, très présent dans les projets existants, mais publie peu.
httprouterv1.3 · sans version depuis 2019Très rapide, ancêtre du routeur de Gin.
Le code : ce que Chi ajoute au routeur standard

Une route publique et un espace d’administration protégé, écrits des deux façons :

net/http seul
mux := http.NewServeMux()
mux.HandleFunc("GET /healthz", healthz)

admin := http.NewServeMux()
admin.HandleFunc("GET /admin/utilisateurs", lister)
admin.HandleFunc("POST /admin/utilisateurs/{id}/suspendre", suspendre)
mux.Handle("/admin/", exigerAdmin(admin))

// Les middlewares globaux s’empilent à la main,
// du plus intérieur au plus extérieur.
var h http.Handler = mux
h = journal(h)
h = récupérer(h)
http.ListenAndServe(":8080", h)
Chi
r := chi.NewRouter()
r.Use(middleware.RequestID, middleware.Recoverer)
r.Use(journal)

r.Get("/healthz", healthz)

r.Route("/admin", func(r chi.Router) {
	r.Use(exigerAdmin)
	r.Get("/utilisateurs", lister)
	r.Post("/utilisateurs/{id}/suspendre", suspendre)
})

http.ListenAndServe(":8080", r)

La version standard fonctionne, mais le sous-routeur répète le préfixe /admin, l’ordre des middlewares est inversé par rapport à la lecture, et rien ne relie visuellement les routes d’administration à leur protection. Avec Chi, la structure du code est la structure de l’API. À deux cents routes, c’est une question de sécurité : c’est en lisant le code qu’on vérifie qu’aucune route d’administration n’a échappé à son middleware.

Mon squelette : des handlers qui renvoient une erreur

Chi ne règle pas la question des erreurs, et c’est volontaire. Je la règle une fois pour toutes, avec un petit type qui permet aux handlers de renvoyer une erreur, comme chez Echo ou Huma, sans quitter la signature standard :

internal/web/handler.go
// Handler est un http.Handler qui renvoie une erreur.
type Handler func(w http.ResponseWriter, r *http.Request) error

func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if err := h(w, r); err != nil {
		écrireErreur(w, r, err) // un seul endroit pour les codes, le format JSON et les journaux
	}
}

// L’erreur métier devient une réponse HTTP ici, et nulle part ailleurs.
func écrireErreur(w http.ResponseWriter, r *http.Request, err error) {
	statut, message := http.StatusInternalServerError, "erreur interne"
	switch {
	case errors.Is(err, carnet.ErrIntrouvable):
		statut, message = http.StatusNotFound, "carnet introuvable"
	case errors.Is(err, carnet.ErrInvalide):
		statut, message = http.StatusBadRequest, err.Error()
	default:
		slog.ErrorContext(r.Context(), "requête en échec", "err", err, "chemin", r.URL.Path)
	}
	écrireJSON(w, statut, map[string]string{"erreur": message})
}

Le handler de Carnet, réécrit avec ce type, ne contient plus une seule ligne propre à Chi : Chi n’apparaît que dans la déclaration des routes.

internal/web/carnets.go
func (a *API) obtenirCarnet(w http.ResponseWriter, r *http.Request) error {
	c, err := a.carnets.Obtenir(r.Context(), r.PathValue("id"))
	if err != nil {
		return err
	}
	return écrireJSON(w, http.StatusOK, c)
}

func (a *API) Routes() http.Handler {
	r := chi.NewRouter()
	r.Use(middleware.RequestID, middleware.Recoverer)
	r.Method("GET", "/carnets/{id}", Handler(a.obtenirCarnet))
	return r
}

Huma

v2 · le contrat déduit du codedistance modérée

On écrit des types Go et des fonctions typées ; Huma en déduit la validation et une spécification OpenAPI 3.1 complète. Il ne remplace pas le routeur : il se pose sur celui de votre choix (Chi, bibliothèque standard, Gin, Echo, Fiber…). Le handler ne parle plus HTTP du tout : il ressemble à une fonction métier.

func(ctx context.Context, in *Entrée) (*Sortie, error)

Sur les huit critères

1 · Distancemodérée : les entrées-sorties
2 · Erreursrenvoyées
3 · Entréesvalidées d’après les types
4 · ContratOpenAPI générée
5 · Observabilitécelle du routeur
6 · Maintenancecommunauté plus petite
7 · Protocolesselon le routeur
8 · Équipeconventions à apprendre
Là où il est à sa place
  • les API publiques ou partenaires, où la spécification OpenAPI est un livrable ;
  • les organisations où plusieurs équipes consomment les mêmes services ;
  • une API sur Chi qui doit se documenter, route par route.
Défis et risques
  • toute la couche d’entrée-sortie passe par Huma : le quitter oblige à la réécrire ;
  • moins de liberté sur la forme des réponses ;
  • une spécification écrite à la main à côté finira par contredire le code : choisissez un seul sens.
Verdict

Le meilleur rapport entre ce qu’il apporte et la distance qu’il impose, parce qu’il laisse un routeur standard en dessous. Si le contrat est écrit d’abord, ou négocié avec d’autres équipes, prenez plutôt oapi-codegen.

oapi-codegenv2 · le contrat d’abordOn part d’un fichier OpenAPI, et l’outil génère les types et une interface serveur à implémenter, pour la plupart des routeurs. Son mode strict server génère des signatures typées « entrée → sortie », proches de celles de Huma.
ogenle contrat d’abordMême point de départ, mais ogen génère son propre serveur et son propre code de sérialisation, sans réflexion. Plus radical, plus loin de la bibliothèque standard.
Goav3 · le design d’abordOn décrit l’API dans un langage dédié écrit en Go, et Goa génère le transport HTTP, gRPC ou JSON-RPC, la documentation et les squelettes. Très puissant pour de nombreux services cohérents, plus exigeant à apprendre.
ConnectRPCv2 · Protobuf d’abordDes fichiers .proto, et des services qui parlent à la fois gRPC, gRPC-Web et un protocole HTTP simple, lisible avec curl. Projet de la CNCF depuis 2024 ; sa v2 (octobre 2026) découple son cœur de net/http tout en continuant à s’en servir.
Le code : GET /carnets/{id} avec Huma, posé sur Chi
type CarnetEntrée struct {
	ID string `path:"id" maxLength:"36" doc:"Identifiant du carnet"`
}
type CarnetSortie struct {
	Body Carnet
}

r := chi.NewRouter()
r.Get("/healthz", healthz) // les routes internes restent en Chi pur

// L’API publique passe par Huma, qui génère /openapi.json et la documentation
api := humachi.New(r, huma.DefaultConfig("Carnet", "1.0.0"))
huma.Get(api, "/carnets/{id}",
	func(ctx context.Context, in *CarnetEntrée) (*CarnetSortie, error) {
		c, err := store.Carnet(ctx, in.ID)
		if errors.Is(err, ErrIntrouvable) {
			return nil, huma.Error404NotFound("carnet introuvable")
		}
		if err != nil {
			return nil, err
		}
		return &CarnetSortie{Body: c}, nil
	})

Les deux cohabitent sur le même routeur : on peut migrer vers Huma route par route, en commençant par celles qui doivent être documentées.

Gin

v1.12 · le plus connudistance élevée

Le framework Go le plus connu. Le handler reçoit un contexte propre à Gin, qui regroupe la requête, la réponse et une boîte à outils : paramètres, liaison et validation du corps, rendu JSON en une ligne. Il reste construit sur net/http.

func(c *gin.Context)

Sur les huit critères

1 · Distanceélevée : routes, handlers, middlewares
2 · Erreursle handler ne renvoie rien
3 · Entréesliaison et validation
4 · Contratoutils tiers (swaggo)
5 · Observabilitéotelgin
6 · Maintenancetoujours en v1
7 · ProtocolesHTTP/2, HTTP/3 depuis la v1.11
8 · Équipele plus large vivier
Là où il est à sa place
  • l’équipe qui découvre Go et doit livrer vite, surtout si elle arrive d’Express, de Flask ou de Laravel ;
  • les projets qui préfèrent une convention partagée à une pile sur mesure ;
  • le recrutement : documentation et exemples à profusion.
Défis et risques
  • handlers, middlewares maison et une partie des tests sont écrits pour Gin ;
  • les middlewares standard passent par un adaptateur (gin.WrapH), dans un sens surtout ;
  • le handler ne renvoie pas d’erreur : même discipline qu’en standard, avec un vocabulaire propre à Gin.
Verdict

Un bon choix pour livrer vite avec une équipe qui découvre Go : le confort d’un contexte riche vaut ici plus que la pureté. Gardez-le dans la couche HTTP : un *gin.Context qui descend dans le métier, c’est le coût de sortie multiplié par dix.

Le code : GET /carnets/{id} avec Gin
r := gin.New()
r.Use(gin.Recovery())
r.GET("/carnets/:id", func(c *gin.Context) {
	carnet, err := store.Carnet(c.Request.Context(), c.Param("id"))
	if errors.Is(err, ErrIntrouvable) {
		c.JSON(http.StatusNotFound, gin.H{"erreur": "carnet introuvable"})
		return
	}
	if err != nil {
		c.AbortWithStatus(http.StatusInternalServerError)
		return
	}
	c.JSON(http.StatusOK, carnet)
})

Echo

v5.4 · les erreurs renvoyéesdistance élevée

De la même famille que Gin, celle des frameworks à contexte, avec une différence qui compte : ses handlers renvoient une erreur, qu’un gestionnaire central traduit en réponse. Plus de return oublié. Lui aussi est construit sur net/http.

func(c *echo.Context) error

Sur les huit critères

1 · Distanceélevée : routes, handlers, middlewares
2 · Erreursrenvoyées, centralisées
3 · Entréesliaison ; validateur à brancher
4 · Contratoutils tiers
5 · Observabilitémaintenue par Echo
6 · Maintenancela v5 a touché chaque handler
7 · Protocolestous ceux de net/http
8 · Équipedocumentation abondante
Là où il est à sa place
  • le même cas que Gin : une équipe qui découvre Go et doit livrer vite ;
  • les API où un format d’erreur uniforme compte beaucoup ;
  • les projets qui préfèrent une convention partagée.
Défis et risques
  • handlers, middlewares et tests sont écrits pour Echo : c’est eux qu’il faudra réécrire pour partir ;
  • la v5 (janvier 2026) a fait du contexte une structure : migrer depuis la v4 touche chaque handler, et la v4 ne reçoit de correctifs que jusqu’à fin 2026 ;
  • l’instrumentation OpenTelemetry a quitté le dépôt communautaire en octobre 2026 : elle dépend désormais de l’équipe d’Echo.
Verdict

Entre les deux frameworks à contexte : Gin pour son vivier, Echo pour ses handlers qui renvoient une erreur, un choix que j’apprécie. Partez directement sur la v5, et gardez-le, lui aussi, hors du métier.

Le code : GET /carnets/{id} avec Echo
e := echo.New()
e.Use(middleware.Recover())
e.GET("/carnets/:id", func(c *echo.Context) error {
	carnet, err := store.Carnet(c.Request().Context(), c.Param("id"))
	if errors.Is(err, ErrIntrouvable) {
		return echo.NewHTTPError(http.StatusNotFound, "carnet introuvable")
	}
	if err != nil {
		return err // le gestionnaire central répondra 500
	}
	return c.JSON(http.StatusOK, carnet)
})

Fiber

v3.6 · sur fasthttpdistance très élevée

Une API inspirée d’Express, posée sur fasthttp, un moteur conçu pour réduire au minimum les allocations mémoire. C’est le seul candidat qui remplace aussi l’étape 1 du trajet : tout en bas de la pile, net/http laisse la place à un autre moteur.

func(c fiber.Ctx) error

Sur les huit critères

1 · Distancetrès élevée : tout sauf le métier
2 · Erreursrenvoyées
3 · Entréesliaison ; validation à brancher
4 · Contratoutils tiers
5 · Observabilitémaintenue par Fiber
6 · Maintenancev3 en 2026, v2 maintenue
7 · Protocolespas d’HTTP/2
8 · Équipefamilier pour Node.js
Là où il est à sa place
  • les passerelles et les collecteurs à très fort trafic, quand le profilage montre que le serveur HTTP lui-même est le goulot ;
  • les équipes Node.js, pour une prise en main immédiate : une bonne raison de l’essayer, pas une raison suffisante de l’adopter.
Défis et risques
  • un écosystème à part : instrumentation, tests, middlewares, tout doit exister « version Fiber » ; la v3 accepte des handlers standard, mais les convertit à chaque requête ;
  • les valeurs lues dans le contexte ne sont valables que pendant le handler : les garder au-delà sans copie produit des bugs subtils ;
  • fasthttp ne parle pas HTTP/2.
Verdict

Rarement, mais réellement. Commencez par la bibliothèque standard et httputil.ReverseProxy, profilez avec pprof sous votre charge, et ne changez de moteur que si le serveur HTTP apparaît en haut du profil.

Hertzv0.10 · sur NetpollIssu de CloudWeGo, l’organisation open source de ByteDance, avec sa propre couche réseau. Il vise les très grands volumes de microservices et n’a pas encore de version 1.
Le code : GET /carnets/{id} avec Fiber
app := fiber.New()
app.Get("/carnets/:id", func(c fiber.Ctx) error {
	carnet, err := store.Carnet(c.Context(), c.Params("id"))
	if errors.Is(err, ErrIntrouvable) {
		return fiber.NewError(fiber.StatusNotFound, "carnet introuvable")
	}
	if err != nil {
		return err
	}
	return c.JSON(carnet)
})

Encore

l’infrastructure dans le codedistance très élevée

Le plus proche de ce qu’un développeur Java appelle un framework, et même au-delà. Une annotation déclare les API, et les ressources (bases, files de messages, tâches planifiées) se déclarent dans le code : Encore en déduit l’infrastructure locale et le déploiement, dans votre compte AWS ou GCP, ou sur vos propres serveurs.

//encore:api public · func(ctx context.Context, id string) (*Carnet, error)

Sur les huit critères

1 · Distancetrès élevée : une façon de penser
2 · Erreursrenvoyées
3 · Entréesvalidation intégrée
4 · Contratdocumentation et clients générés
5 · Observabilitétraces intégrées
6 · Maintenanceportée par une entreprise
7 · Protocolesceux de la plateforme
8 · Équipeune plateforme à apprendre
Là où il est à sa place
  • la petite équipe qui veut faire vivre un produit sans passer ses semaines dans la configuration du cloud ;
  • les projets où le temps de démarrage compte plus que la liberté.
Défis et risques
  • le choix le plus engageant de la liste : on adopte une façon de penser, pas une bibliothèque ;
  • une communauté plus petite, donc moins de réponses toutes faites ;
  • une dépendance forte à l’outil, et à l’entreprise qui le porte.
Verdict

Un accélérateur considérable pour la bonne équipe. Isolez soigneusement votre logique métier : c’est votre porte de sortie.

Encore n’est pas seul dans cette famille des plateformes complètes. Pour des dizaines de microservices et plusieurs équipes, go-zero et Kratos imposent une structure commune clé en main : une décision d’organisation autant que technique. L’autre voie, que je préfère quand l’équipe plateforme en a les moyens : un petit socle interne sur Chi ou ConnectRPC, plus vos conventions.

go-zerov1.10 · fichiers .apiSon outil goctl génère des microservices complets à partir de fichiers de description, protections comprises (limitation de débit, coupe-circuit).
Kratosv3 · ProtobufLa v3 (juin 2026) part de Protobuf pour exposer chaque service en HTTP et en gRPC.
Beegov2 · MVC completTrès répandu en Chine, toujours maintenu.
Le code : GET /carnets/{id} avec Encore
//encore:api public method=GET path=/carnets/:id
func Obtenir(ctx context.Context, id string) (*Carnet, error) {
	c, err := store.Carnet(ctx, id)
	if errors.Is(err, ErrIntrouvable) {
		return nil, &errs.Error{Code: errs.NotFound, Message: "carnet introuvable"}
	}
	return c, err
}

Ni routeur ni serveur à écrire : Encore les déduit de l’annotation. C’est exactement ce qui fait sa force, et sa distance.

Et les autres ? Iris Revel Buffalo bunrouter Flow… La liste est longue, et certains ont compté. Revel n’a plus publié de version depuis 2022 ; Buffalo, en revanche, publie encore. Avant d’adopter un outil hors de cette liste, regardez trois choses : la date de sa dernière version, le nombre de personnes qui fusionnent les contributions, et la signature de ses handlers.

=== RUN TestCandidats --- PASS: TestCandidats (partie 5)

Sept candidats, sept distances, et aucun qui gagne partout. Pour trouver le vôtre, place à la boussole.

Partie 6 · La boussole
Partie 6 · boussole

La boussole

Les fiches partent des candidats ; la boussole fait le chemin inverse. Elle part de votre projet, pose quatre questions et vous indique par quel candidat commencer. Elle ne remplace pas le raisonnement des parties précédentes : elle le résume.

Quatre questions, un point de départ

1. Vous construisez…
2. Votre équipe…
3. À terme, combien de routes ?
4. L’infrastructure…
Point de départ conseillé Répondez aux questions ci-dessus.

=== RUN TestBoussole --- PASS: TestBoussole (partie 6)

La boussole vous donne un point de départ. Reste à retenir l’essentiel.

Partie 7 · Ce qu’il faut retenir
Partie 7 · conclusion

Ce qu’il faut retenir

Si vous ne deviez garder qu’une page de cet article, ce serait celle-ci.

  1. En Go, la bibliothèque standard fait déjà l’essentiel

    Le serveur HTTP, le routeur, les tests : un framework n’y fournit pas le socle, seulement du confort par-dessus.

  2. Le critère par excellence : la distance

    Plus un outil s’éloigne de la bibliothèque standard, plus il en fait pour vous, et plus la sortie coûte. La signature de ses handlers le dit en une ligne.

  3. La plus petite distance qui vous suffit

    Listez ce que la bibliothèque standard ne vous donne pas, prenez l’outil le plus proche qui le couvre, et gardez le métier à l’abri du framework.

À vous de jouer

Je n’ai donné que ma lecture, et mes préférences. J’aimerais beaucoup connaître les vôtres.

Avez-vous déjà dû quitter un framework Go ? Qu’a-t-il fallu réécrire ?

Depuis Go 1.22, êtes-vous revenu à la bibliothèque standard ? Qu’est-ce qui vous manque encore ?

Avez-vous déjà vu le framework être le vrai goulot de performance, profil à l’appui ?

Code d’abord ou contrat d’abord : lequel tient le mieux dans la durée chez vous ?

Une plateforme comme go-zero, Kratos ou Encore vous a-t-elle fait gagner, ou perdre, du temps sur la longueur ?

Et vous, quel est votre coup de cœur, et dans quels cas ne le prenez-vous pas ?

Pour me répondre, écrivez-moi par e-mail ou sur LinkedIn. Je réponds avec plaisir, selon mon agenda, parfois avec un peu de retard, mais je réponds toujours.

Le mot de la fin
En Go, un framework ne se juge pas à ce qu’il ajoute, mais à la distance qu’il met entre votre code et la bibliothèque standard. Choisissez la plus petite distance qui vous suffit. Et gardez toujours une porte de sortie.
Rivel Babindamana
Développeur, octobre 2026

PS : le jour où le routeur standard saura grouper des routes et leur appliquer des middlewares, je quitterai Chi en réécrivant un seul fichier. C’est, au fond, tout ce que je lui demande.