Biblioteka standardowa Go jest wyjątkowa

W ekosystemie większości języków programowania budowanie aplikacji webowej bez frameworka brzmi jak szaleństwo. Kto pisałby serwer HTTP od zera w Pythonie czy Javie? Ale Go to inny przypadek. Pakiet net/http w bibliotece standardowej Go to w pełni funkcjonalny, wydajny i dobrze zaprojektowany serwer HTTP — nie prototyp, nie zabawka, lecz narzędzie produkcyjne.

Wszystkie nasze serwisy w LinWork — strony firmowe, kalkulatory, systemy proxy — działają na czystym net/http bez żadnego frameworka. To świadomy wybór, który po latach pracy się sprawdza. Oto dlaczego.

Routing w Go 1.22+

Przez lata największą słabością standardowego routera w Go był brak obsługi parametrów ścieżki i metod HTTP. Chcąc obsłużyć /users/{id}, trzeba było ręcznie parsować URL lub sięgać po zewnętrzny router typu gorilla/mux czy chi.

Go 1.22 (luty 2024) zmieniło to fundamentalnie. Nowy http.ServeMux obsługuje wzorce z parametrami i metodami HTTP:

mux := http.NewServeMux()
mux.HandleFunc("GET /users/{id}", getUser)
mux.HandleFunc("POST /users", createUser)
mux.HandleFunc("GET /articles/{slug...}", getArticle)

Parametry odczytujemy przez r.PathValue("id"). Wzorzec {slug...} dopasowuje pozostałą część ścieżki. To wystarczający routing dla zdecydowanej większości aplikacji webowych.

Porównajmy z popularnymi frameworkami. Gin: r.GET("/users/:id", getUser). Chi: r.Get("/users/{id}", getUser). Echo: e.GET("/users/:id", getUser). Różnica w składni jest minimalna, a w zamian nie dodajemy żadnej zewnętrznej zależności.

Middleware bez magii

Middleware to funkcja, która opakowuje handler HTTP, dodając logikę wykonywaną przed lub po właściwym handlerze. W standardowym Go middleware to po prostu funkcja przyjmująca http.Handler i zwracająca http.Handler:

func logging(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next.ServeHTTP(w, r)
        log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
    })
}

Łączenie middleware'ów to zwykła kompozycja funkcji: logging(auth(rateLimiter(mux))). Nie potrzebujemy specjalnych mechanizmów rejestracji, łańcuchów ani pipeline'ów — standardowa kompozycja funkcji Go wystarczy.

Typowe middleware'y, które implementujemy w naszych projektach:

  • Logowanie — czas odpowiedzi, metoda, ścieżka, kod statusu
  • Odzyskiwanie po panic — przechwycenie paniki i zwrócenie HTTP 500 zamiast crashu
  • CORS — nagłówki Cross-Origin Resource Sharing
  • Rate limiting — ograniczenie liczby żądań z jednego IP
  • Kompresja gzip — transparentna kompresja odpowiedzi

Każdy z nich to 15-30 linii kodu. Proste, czytelne, łatwe do zrozumienia i modyfikacji.

Szablony HTML

Pakiet html/template w Go to silnik szablonów z automatycznym escapowaniem HTML, zabezpieczający przed atakami XSS. Obsługuje dziedziczenie layoutów, częściowe szablony (partials), funkcje niestandardowe i warunkowe renderowanie.

Typowa struktura szablonów w naszych projektach:

templates/
  layout.html      -- bazowy layout z nagłówkiem i stopką
  home.html         -- strona główna
  blog/
    list.html       -- lista artykułów
    article.html    -- pojedynczy artykuł

Layout definiuje bloki, które strony dziedziczące wypełniają treścią. Mechanizm template.ParseGlob ładuje wszystkie szablony jednorazowo przy starcie aplikacji. Od Go 1.16 dyrektywa //go:embed pozwala wbudować szablony w plik binarny, eliminując zależność od systemu plików.

Czy html/template jest tak elastyczny jak Jinja2, Blade czy Twig? Nie — celowo. Go preferuje prostotę. Brak złożonej logiki w szablonach wymusza przygotowanie danych w kodzie Go, co prowadzi do czystszej architektury.

Obsługa plików statycznych

Serwowanie CSS, JavaScript, obrazów i innych zasobów statycznych to jedna linijka:

mux.Handle("GET /static/", http.StripPrefix("/static/",
    http.FileServer(http.Dir("static"))))

Lub z wbudowanymi zasobami:

//go:embed static
var staticFiles embed.FS
mux.Handle("GET /static/", http.FileServerFS(staticFiles))

Standardowy file server obsługuje nagłówki ETag, Last-Modified, Content-Type, range requests i kompresję. W produkcji zazwyczaj pliki statyczne serwujemy przez Nginx (z cache'owaniem i HTTP/2 push), ale serwer Go jest w pełni wystarczający dla mniejszych projektów.

Obsługa JSON API

Budowanie API REST w czystym Go jest równie proste:

func getUser(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")
    user, err := db.FindUser(id)
    if err != nil {
        http.Error(w, "Not found", http.StatusNotFound)
        return
    }
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(user)
}

Pakiet encoding/json obsługuje serializację i deserializację struktur Go. Tagi json:"name" kontrolują nazwy pól w JSON. Dla bardziej zaawansowanych przypadków dostępny jest json.Decoder z obsługą streamingu.

Dlaczego nie framework

Frameworki Go jak Gin, Echo czy Fiber są dobrymi narzędziami. Ale każda zewnętrzna zależność to kompromis:

  • Aktualizacje i bezpieczeństwo — musisz śledzić CVE i aktualizować zależności
  • Kompatybilność — nowe wersje mogą łamać API
  • Krzywa uczenia się — nowy członek zespołu musi poznać API frameworka oprócz standardowej biblioteki Go
  • Rozmiar binarki — każda zależność zwiększa wynikowy plik

Standardowa biblioteka Go jest wersjonowana razem z językiem, testowana przez tysiące kontrybutorów i gwarantuje kompatybilność wsteczną zgodnie z obietnicą Go 1 Compatibility Promise. Kod napisany w Go 1.16 kompiluje się i działa na Go 1.25 bez zmian.

Kiedy framework ma sens

Uczciwie — są sytuacje, gdy framework upraszcza pracę. Walidacja złożonych formularzy, automatyczne bindowanie parametrów, wbudowany websocket, generowanie dokumentacji OpenAPI — to funkcje, które w czystym Go wymagają więcej kodu. Jeśli budujesz duże API z dziesiątkami endpointów i złożoną walidacją, framework może zaoszczędzić czas.

Ale dla stron firmowych, blogów, landing page'ów, prostych API i narzędzi wewnętrznych — stdlib Go jest więcej niż wystarczający. Mniej kodu, mniej zależności, mniej problemów.

Chcesz zobaczyć, jak wygląda strona firmowa zbudowana w czystym Go? Właśnie na niej jesteś. Masz pytania o naszą architekturę? Skontaktuj się z nami.