
Vorab: Der gesamte Code über den ich spreche ist hier veröffentlicht: https://github.com/trakora/production-go-api-template
Pull Requests und Issues sind willkommen.
Der Ausgangspunkt
Ich kam aus der JavaScript‑Welt. Wenn ich eine API brauchte, tippte ich:
npm install express
node index.js
Schnell gemockt, schnell deployed. Für jedes Problem gab es eine schöne fertige Libary, die alles übernahm. Das ging so lange gut, bis ich irgendwann keinen Schimmer mehr davon hatte, was eigentlich im Hintergrund ablief.
JS (und, ja, auch TypeScript) verführt einen zur schnellen Lösung und zu schlampigem Code, bei mir jedenfalls. Viele meiner Projekte haben keine 10 Entwickler und Architekten.
Mir war klar: Ich brauche einen Perspektivwechsel.
Ich hatte die Wahl zwischen Go und Rust. Da jedoch Rust eine sehr viel höhere Lernkurve hatte, war Go die Wahl. Ob ich damit hundertprozentig glücklich bin? Keine Ahnung. Aber eins kann ich sagen: Es macht mir deutlich mehr Spaß als das alte Node-Gewusel.
Erste Begegnung mit Go HTTP
Als ich loslegte, gab es in der Standard-Lib praktisch nichts Komfortables. Mit der Version Go 1.22 konnte ich dann so richtig starten.
Nach „Hello, world“ stolperte ich aber über Begriffe wie ServeMux
und HandlerFunc
.
In Node hatte ich app.get("/posts/:id")
.
In Go musste ich mir das so hinbasteln:
mux := http.NewServeMux()
mux.HandleFunc("/posts/", postHandler)
Alle Artikel, die ich las, nutzten Libraries wie chi
odergorilla/mux
. Ich wollte aber diesmal wissen, wie weit ich allein mit der Standardbibliothek komme und mich nicht dauerhaft auf externe Projekte verlassen.
Drei Artikel, die mir weitergeholfen haben
Ich habe die drei Artikel gefunden, die bei mir die Grundlage für den Einstieg geboten haben.
- Grafana – How I write HTTP services in Go after 13 years
- Fly.io Blog – Backend Basics
- Eli Bendersky – REST servers in Go – Part 1 (std lib)
Alle drei sagen im Kern: mach erst mal alles mit net/http
, bevor du nach einer Library greifst.
Mein Minimal‑Prototyp
package main
import (
"log"
"net/http"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("pong"))
})
log.Println("listening on :8080")
log.Fatal(http.ListenAndServe(":8080", mux))
}
Kein Routing‑Framework, kein JSON‑Helper. Das Ding läuft, und ich verstehe jede Zeile. Durch den Artikel Backend Basics konnte ich auch zum ersten Mal tief eintauchen und die Funktionsweise von HandleFunc
nachvollziehen.
Der Code der Standardbibliothek ist hervorragend geschrieben und vermittelt direkt, was passiert.
Vom Prototyp zum Template‑Repo
Mehr Endpunkte → mehr Chaos.
Als mehr Endpunkte dazukamen, brauchte ich Struktur: Config, Logging, Middleware.
Daraus entstand das Production‑Go‑API‑Template. Ordner wie /cmd
, /api
, /pkg
und eine saubere Trennung in Handler → Service → Repository.
Warum ich so wenig Libraries einsetze
Wenn ich auf externe Libraries verzichte, gewinne ich drei Dinge – und zwar spürbar:
Kontrolle
Ich kenne jede Zeile, die auf meinem Server läuft. Keine Magie, keine versteckten Side-Effects. Tritt ein Bug auf, kann ich ihn nachverfolgen, statt erst einmal den Issues-Tab eines GitHub-Projekts zu durchforsten.
Weniger Upgrades
Bei npm install
verstehe ich nicht einmal die tausend Upgrades, die ich täglich durchführen müsste.
Meine Build-Pipeline bleibt schlank, und ich muss nicht alle paar Wochen prüfen, ob Version 3.4.2 eines Frameworks wieder einen Breaking Change mit sich bringt.
Besseres Lernen
Wer sich auf die Standardbibliothek einlässt, bekommt Go quasi im Quelltext erklärt. Jeder Handler, jedes Interface steht schwarz auf weiß vor mir. Das zwingt zum Begreifen – und sorgt dafür, dass ich später tatsächlich beurteilen kann, wann sich eine zusätzliche Library lohnt und wann nicht.
Kurz: Weniger Plug-and-Play, mehr Durchblick. Und genau das macht am Ende den Code robuster und mich als Entwickler ein Stück souveräner.
Fazit
Express war gemütlich, aber Go zwingt mich, bewusst zu entscheiden: Welche Abhängigkeit ist wirklich nötig? Die Standardbibliothek deckt locker 80 Prozent ab. Für den Rest wähle ich Libraries gezielt aus. Mein Template‑Repo wächst organisch, ohne dass ich alle paar Wochen ein Framework upgraden muss. Ich bin glücklich mit der Entscheidung und werde diese Doktrin künftig auch bei anderen Projekten anwenden.