Goの話になると、「標準ライブラリだけで十分です」と言う人がいます。あれは半分スローガンで、半分は普通に事実です。PythonやNode.jsだと、先にフレームワーク名から考える場面が多いですが、Goは逆です。まず net/http で書いて、足りなくなったところだけ後から足せます。
最近も、30分で書いた小さなCLIをまだ使っています。こういう雑な道具が残りやすいのは、Goが最初から過剰な約束を持ち込まないからだと思います。HTTPの小さなサーバーも同じです。ルーティング、JSON、ログ出力くらいなら、標準ライブラリだけで気持ちよく終わります。
先に標準ライブラリを触っても形になります
この記事用に、最小のHTTPサーバーを Docker で確認しました。Go 1.25 の ServeMux はメソッド付きルートをそのまま書けるので、昔よりかなり素直です。これくらいなら、フレームワークを選ぶ会議より先にコードが動きます。
docker run --rm -v "$PWD":/src -w /src golang:1.25 go test ./...
package main
import (
"encoding/json"
"log"
"net/http"
"time"
)
type response struct {
Message string `json:"message"`
}
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 %s", r.Method, r.URL.Path, time.Since(start))
})
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("ok"))
})
mux.HandleFunc("GET /hello", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(response{Message: "hello"})
})
log.Fatal(http.ListenAndServe(":8080", logging(mux)))
}
フレームワークが不要なのではなく、後回しにできます
ここで大事なのは、Goにはフレームワークがいらないと言いたいわけではないことです。認証、ORM、OpenAPI、管理画面まで欲しくなれば、別の道具を足せば済みます。ただ、疎通確認用のAPIや社内向けの薄いサービスで、最初から大きな枠組みを背負う必要はあまりありません。
このあたりはAIとも相性がいいです。handler を一つ増やす、JSON を返す、ログを差し込む。そのくらいのボイラープレートはAIに任せても困りません。人間は、エンドポイントをどう切るか、失敗をどう返すか、どこまでを公開面に出すかを考えるほうが大事です。フレームワークの流儀を覚えるより、そちらのほうがずっと面白いです。
Goは今のC言語にかなり近いです
私はGoを現代のC言語みたいなものだと思っています。メモリ管理の顔つきは違いますが、OSに近い層を触るときの素直さが似ています。ファイル、ソケット、標準入力、プロセス。このあたりを変に飾らず扱える言語は、結局長く残ります。
Web開発の流行はすぐ変わります。それでも net/http の小さなサーバーは、数年後に見返してもまだ読めます。自分のPCや自分のサービスを自分で触りたいなら、この鈍くなさはかなり助かります。少なくとも私は、新しい抽象化を足す前に、まず標準ライブラリだけでどこまで行けるかを試します。