Go-Web-Frameworks 2026: net/http, chi, Gin, Echo und Fiber im Vergleich

Fünf Go-HTTP-Stacks, ein Benchmark-Setup, echte Zahlen.

Inhaltsverzeichnis

Go liefert einen produktionsreifen HTTP-Server in seiner Standardbibliothek mit, und seit Go 1.22 übernimmt der eingebaute Router selbstständig die Methodenabgleichung und Pfadparameter. Die Frage nach dem Framework hat sich verengt, ist aber nicht verschwunden.

Fünf Stacks decken nahezu jede Go-Backend-Entscheidung in 2026 ab: die Standardbibliothek net/http, chi, Gin, Echo und Fiber. Jeder nimmt eine andere Position im Hinblick auf denselben Kompromiss ein: Was die Bibliothek einem gibt, im Verhältnis zu dem, was man ihr an Abhängigkeiten, Konventionen und Ökosystem-Kompatibilität schuldet. Dieser Artikel vergleicht sie anhand von Routing-Leistungsfähigkeit, Handler-Ergonomie, Middleware und gemessener Leistung und endet mit einer Entscheidungstabelle statt einem einzelnen Urteil.

Wer bei Null anfangen und den kompletten Build-Prozess um seine Wahl herum durchlaufen möchte – Validierung, Authentifizierung, Tests, Bereitstellung –, findet die Schritt-für-Schritt-Version in REST-APIs in Go bauen. Hier liegt der Fokus auf den Stacks selbst.

fünf 3D-Glasblöcke, verbunden durch leuchtende Linien, einer pro Go-HTTP-Stack

Der ServeMux-Neustart nach 1.22

Der Grund, warum sich die Diskussion über Frameworks verändert hat, ist eine Ergänzung in der Standardbibliothek. Go 1.22 verschaffte http.ServeMux methodenbewusste Muster und Pfadparameter:

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

Vor diesem Update erforderte jede API, die GET /users/{id} benötigte, einen Router von Dritten, wodurch Gin, chi und Echo zu den Standards wurden. Im Jahr 2026 deckt die Standardbibliothek statische Routen, Methodenabgleichung, Ein-Segment- und Wildcard-Parameter sowie Middleware durch einfaches Funktions-Wrapping ab. Was sie noch nicht liefert: Routengruppen, komponierbare Middleware-Ketten, Struktur-Binding oder pro-Routen-Parametertypen.

Die Kontrahenten

net/http (Standardbibliothek) chi v5.3.2 Gin v1.12.0 Echo v4.16.0 Fiber v2.52.15
HTTP-Engine net/http net/http net/http net/http fasthttp
Handler-Signatur func(w, r) func(w, r) func(c *gin.Context) func(c echo.Context) error func(c *fiber.Ctx) error
http.Handler-kompatibel ja ja ja ja nein
Middleware-Ökosystem wachsend, Standardbibliotheks-geprägt Standardbibliotheks-geprägt größtes, Gin-spezifisch groß, Echo-spezifisch wachsend, fasthttp-spezifisch
Binding/Validierung manuell manuell eingebaute Tags eingebaut über Hilfsfunktionen
HTTP/2 ja ja ja ja nein
Abhängigkeiten 0 1 ~6 ~8 ~4

Alle fünf verwenden Trie- oder Radix-basierte Router mit Pfadparametern und gruppierten Routen. Die strukturelle Trennlinie liegt in der letzten Zeile der Geschichte über die Handler-Signatur: chi und die Standardbibliothek sprechen reines http.Handler; Gin, Echo und Fiber kapseln Anfragen in ihrem eigenen Kontexttyp. Fiber ist erneut der Außenseiter, da er auf fasthttp basiert und nicht auf net/http.

Was ich gemessen habe

Anstatt synthetische Router-Benchmarks zu zitieren, habe ich denselben Service auf allen fünf Stacks ausgeführt. Das Messaufbau-Setup:

  • 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, kein TLS
  • Drei Routen pro Stack: GET /health, GET /users/{id}, GET /api/v1/users/{uid}/orders/{oid}
  • Last: 8 Keep-Alive-Worker für 3 Sekunden gegen die Route mit zwei Parametern, nach einer Warmup-Passage
  • Jeder Handler serialisiert pro Anfrage ein kleines JSON-Dokument mit encoding/json – die Zahlen umfassen also Serialisierung, nicht nur Routing

Die Standardbibliotheks-Version der Hot-Route:

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

und das Fiber-Äquivalent:

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

Zwei Hinweise zur Ehrlichkeit vor den Zahlen. Erstens ist dies eine localhost-Mikrolast: keine Datenbank, kein TLS-Handshake, keine Middleware-Kette. Reale Dienste stoßen bei der Eingabe/Ausgabe an Grenzen – der Anteil des Frameworks an einer Anfrage, die PostgreSQL über GORM, Ent, Bun oder sqlc abfragt, ist ein Rundenfehler. Zweitens erhielt jeder Stack seinen eigenen Serverprozess und eine identische Handler-Form, sodass der Vergleich zwischen Stacks und nicht zwischen manuell optimierten Konfigurationen liegt.

Ergebnisse

Durchsatz auf der dynamischen Route mit zwei Parametern:

Stack Anfragen/Sekunde Durchschnittliche Latenz
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

Die vier auf net/http basierenden Stacks liegen innerhalb von 4 % voneinander. Die fasthttp-Engine von Fiber ist bei dieser localhost-Last etwa 21 % voraus – real, aber die Art von Lücke, die ein einzelner Datenbank-Roundtrip (0,5–5 ms) in der Produktion irrelevant macht.

Um zu sehen, wo die Kosten pro Anfrage tatsächlich hinführen, habe ich auch die Handler-Aufrufe ohne Netzwerkkomponente gemessen, indem ich jeden Router direkt über http.Handler.ServeHTTP aufrief (Fiber über seinen fasthttp-Handler):

Stack Zeit/Operation Bytes/Operation Allokationen/Operation
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

* Die Fiber-Zahl enthält Harness-Overhead – der Test muss die Anfrage jede Iteration in den wiederverwendbaren Kontext von fasthttp kopieren, was der echte Server umgeht, indem er direkt vom Draht liest. Man sollte dies als obere Grenze betrachten; der serverseitige Durchsatz oben ist der faire Vergleich für Fiber.

Das Allokationsprofil ist die nachhaltigere Geschichte. Gin und Echo poolen ihre Anfragekontexte, sodass eine Anfrage über sie 3 Allokationen kostet. Die 5 Allokationen der Standardbibliothek gehen auf die eigentliche, anfrageweise Verwaltung zurück. chi speichert Routenparameter in context.Context der Anfrage, was sich in 897 B und 7 Allokationen pro Anfrage niederschlägt – der Preis dafür, ein reines http.Handler mit reichen Routeninformationen zu bleiben.

Fiber und der fasthttp-Kompromiss

Fiber ist der schnellste Stack im Server-Benchmark, und die Geschwindigkeit kommt mit einer Kompatibilitätsrechnung. fasthttp ist eine HTTP-Implementierung von Grund auf mit eigenen Anfrage-/Antworttypen, daher:

  • net/http-Middleware lässt sich nicht anhängen. Jede Bibliothek, die ein http.Handler oder eine *http.Request erwartet, benötigt eine Konvertierung oder ein Fiber-natives Port.
  • Kein HTTP/2. fasthttp implementiert dies nicht; Go’s net/http erhält h2 über TLS kostenlos aus der Standardbibliothek.
  • WebSocket, Streaming und http.Flusher-ähnliche Tricks nehmen alle den fasthttp-Weg, der sich vom Standardbibliotheks-Verhalten unterscheidet, das die meisten Go-Dokumentationen voraussetzen.

Was Fiber im Gegenzug kauft: die schnellsten Zahlen in der Tabelle oben, eine Express-ähnliche API und niedrigere Allokationen als die Framework-Benchmarks nahelegen, sobald der Vorteil beim Draht-Parsing berücksichtigt wird. Es passt, wenn Antworten klein sind, Routing dominiert, HTTP/2 upstream terminiert wird und die benötigte Middleware im Fiber-Ökosystem existiert. Diese Bedingungen gelten für einige Edge-Dienste und für fast nichts anderes.

chi: der langweilige Mittelweg

chi ist ein Router und eine Middleware-Pipeline, nichts weiter. Handler sind func(w, r), Middleware ist func(http.Handler) http.Handler, und alles, was für die Standardbibliothek geschrieben wurde, hängt sich ohne Adapter an:

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

Das ist der gesamte Verkaufsargument: Routengruppen und ein Middleware-Stack auf Basis der Standard-Schnittstelle. Die Allokationstabelle preist dies ehrlich an – reicheren Routenkontext pro Anfrage als die Standardbibliothek allein, im Austausch gegen komponierbare Middleware und verschachtelte Routen. Für Dienste, die Struktur ohne Framework-Identität wollen, ist chi plus net/http die Konfiguration, auf die sich die meisten Go-Teams einigen.

Gin vs. Echo, im Code

Gin und Echo sind in der Leistungszu nahezu Zwillinge – innerhalb des Rauschens in beiden Benchmarks –, daher liegt die Wahl zwischen ihnen im API-Geschmack und der Philosophie der Fehlerbehandlung.

Echo zentralisiert Fehler in einem HTTPErrorHandler. Handler geben error zurück, und eine Funktion weist Fehler Antworten zu:

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

Gin-Handler geben nichts zurück; Fehler sammeln sich im Kontext an, und man renderst sie, wo man es für richtig hält:

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

Das Modell von Echo lässt sich sauber auf Go-Fehlerbehandlungsarchitektur mit gewickelten Fehlern und zentraler Grenzbearbeitung abbilden – an einem Ort werden die Ergebnisse von errors.Is in Statuscodes übersetzt. Das Modell von Gin entspricht dem häufigeren Muster, pro Handler zu entscheiden. Neben Fehlern sind die entscheidenden Vorteile von Gin die Größe des Ökosystems und das eingebaute Struktur-Binding mit Validierungs-Tags; bei Echo sind es eine konsistente Kontext-API und stärkere eingebaute Middleware. Beide integrieren die Swagger-Generierung über ihre eigenen Adapter – die einrichtungsbezogene Konfiguration für jedes Framework wird in Swagger zu Ihrer Go-API hinzufügen behandelt.

Eine weitere Achse: Kontext-Propagation. Gin und Echo kapseln context.Context in ihren eigenen Kontexttypen, sodass das Übergeben von Abbrüchen in Handler einen Extraktionsschritt erfordert. Die Muster, um Abbruch, Timeouts und Werte über all diese Wrapper hinweg gerad zu halten, finden sich in Go context.Context richtig gemacht.

Die Timeouts, die niemand setzt

Jeder Stack in diesem Vergleich lässt die Server-Timeouts auf Null-Standardwerte, es sei denn, man setzt sie selbst – und Null bedeutet kein Limit. Ein Client, der eine Verbindung öffnet und nichts sendet, hält einen Goroutine und eine Datei-Deskriptor endlos fest:

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 ist das Erste, was man setzen sollte – es begrenzt Slowloris-artige Verbindungsermüdung, ohne lange Uploads zu unterbrechen, wie ein pauschales ReadTimeout könnte. WriteTimeout muss die langsamste legitime Antwort abdecken, was es zu einer politichen Entscheidung im Zusammenhang mit den eigenen Timeout-Budgets macht, nicht zu einer Copy-Paste-Konstante. Denselben Disziplin gilt pro Framework: Gin, Echo und Fiber akzeptieren alle serverseitige Timeout-Konfiguration, und keines von ihnen aktiviert sie standardmäßig.

Wählen

flowchart TD A[Neuer Go-HTTP-Dienst] --> B[Interne Tool, Plugin,
oder Null-Abhängigkeits-Anforderung?] B -- ja --> C[net/http ServeMux] B -- nein --> D[Eingebautes Binding,
Validierung, große Middleware-Katalog gewünscht?] D -- nein --> E[chi + net/http] D -- ja --> F[Team-Konvention?] F -- Fehler-returnende Handler --> G[Echo] F -- größtes Ökosystem, Gin-Idiome --> H[Gin] A --> I[Kleine Antworten, extremer Durchsatz,
kein HTTP/2, Upstream-TLS-Terminierung?] I -- alle wahr --> J[Fiber] I -- nein --> E
Situation Empfehlung
Interner Dienst, CLI-Endpunkt, Plugin, Bibliothek net/http allein
Die meisten APIs – Struktur ohne Framework-Lock-in chi über net/http
Öffentliche API, größeres Team, Validierung und Binding aus der Schachtel Gin
Öffentliche API, zentralisierte Fehlerübersetzung, konsistente Kontext-API Echo
Bewiesener Routing/Serialisierungs-Engpass, winziges JSON, kein HTTP/2 Fiber

Wenn der ehrliche Standard für einen neuen Dienst in 2026 benannt werden muss: Beginnen Sie mit net/http, fügen Sie chi hinzu, wenn Middleware oder Routengruppen zu drücken beginnen, und wechseln Sie nur zu Gin oder Echo, wenn Binding, Validierung oder Team-Konvention eine Framework-Identität rechtfertigen. Fiber bleibt ein Spezialwerkzeug – es lohnt sich, es zu benchmarken, wenn der Durchsatz als Problem bewiesen ist, und man den Preis für die Kompatibilitätsrechnung abwägt.

Migration zwischen Stacks

Die Migrationskosten spiegeln die Trennlinie der Handler-Signatur wider. Standardbibliothek ↔ chi ist mechanisch: Handler behalten dieselbe Signatur, Routen wechseln von mux.HandleFunc-Mustern zu r.Get, Middleware hängt sich an den chi-Stack. Gin ↔ Echo ist eine Kontexttyp-Übersetzung – c.Param, c.JSON und Middleware-Logik lassen sich fast zeilenweise portieren. Alles, was zu oder von Fiber migriert wird, ist ein Neuschreiben der HTTP-Schicht, da *http.Request-basierter Code, net/http-Middleware und h2-Voraussetzungen die fasthttp-Grenze nicht überleben; der am wenigsten schmerzhafte Weg ist, die Geschäftslogik in frameworkfreien Paketen zu halten und die Transportschicht neu zu generieren, was auch die Struktur ist, die die nächste Migration günstig macht.

Abonnieren

Neue Beiträge zu Systemen, Infrastruktur und KI-Engineering.