Go 的特殊情況:stdlib 本來就夠強
Go 和其他語言不一樣的地方是:net/http 本身就夠用。一個 production-ready 的 Go HTTP server 可以完全不用任何框架:
mux := http.NewServeMux()
mux.HandleFunc("/users", handleUsers)
mux.HandleFunc("/users/{id}", handleUserById) // Go 1.22+ 支援 path param
log.Fatal(http.ListenAndServe(":8080", mux))Go 1.22 之後,net/http 的路由支援了 path parameter 和 method-based routing,再加上 middleware 用函式包裝,小到中型的 API 其實不需要框架。
這讓 Go 的 framework 選型問題和 Node.js / Python 不同:你不是在問「用哪個框架」,而是在問「stdlib 夠不夠、我需要哪個 ergonomics 的提升」。
Gin:Go 生態最主流的選擇
Gin(2014)是 Go 生態 star 數最高的 web framework。它解的主要是三個問題:
r := gin.New()
r.Use(gin.Logger(), gin.Recovery())
// 路由 ergonomics:grouping 很自然
v1 := r.Group("/api/v1")
{
v1.GET("/users", UserController.Index)
v1.POST("/users", UserController.Store)
v1.GET("/users/:id", UserController.Show)
}
r.Run(":8080")Binding + Validation:Gin 的 ShouldBindJSON 讓 request body 的 parse 和 validation 一步完成:
type CreateUserInput struct {
Name string `json:"name" binding:"required,min=1,max=100"`
Email string `json:"email" binding:"required,email"`
Age int `json:"age" binding:"min=0,max=150"`
}
func createUser(c *gin.Context) {
var input CreateUserInput
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// input.Name, input.Email, input.Age 已驗證
}底層用 go-playground/validator,annotation tag 的語法和 NestJS class-validator 類似。
缺點:
gin.Context是 Gin 自己的型別,測試時要構造這個 context 比較麻煩- Gin 的更新速度偏慢(v1.9 已穩定很久),不是最積極維護的框架
適合:需要 binding + validation 一體、路由 grouping 清楚、生態最大的 Go API。
Echo:比 Gin 更嚴謹的 API 設計
Echo 和 Gin 在功能上高度類似,但 API 設計更嚴謹一些:
e := echo.New()
e.Use(middleware.Logger())
e.Use(middleware.Recover())
g := e.Group("/api/v1")
g.GET("/users", getUsers)
g.POST("/users", createUser)
e.Logger.Fatal(e.Start(":8080"))Echo 的 echo.Context 介面讓測試更容易 mock,而且 Echo 的 middleware 設計和 Koa 類似(洋蔥模型),c.Next() 之後可以繼續執行:
func LogMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
start := time.Now()
err := next(c) // ← 執行 handler
// handler 執行後繼續
log.Printf("← %s %s %v", c.Request().Method, c.Request().URL, time.Since(start))
return err
}
}缺點:生態比 Gin 小,相關套件和社群資源少。
適合:偏好比 Gin 更清晰 API 設計、需要洋蔥 middleware 模型的場景。
Chi:最接近 stdlib 的 composable router
Chi 的設計哲學和 Gin / Echo 不同——它不是一個完整的框架,而是一個只做路由的 composable router:
r := chi.NewRouter()
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Route("/api/v1", func(r chi.Router) {
r.Get("/users", getUsers)
r.Post("/users", createUser)
r.Route("/users/{id}", func(r chi.Router) {
r.Get("/", getUser)
r.Put("/", updateUser)
r.Delete("/", deleteUser)
})
})
http.ListenAndServe(":8080", r)Chi 最大的優點是它完全基於 net/http 的 http.Handler interface——你的 handler 不需要改,middleware 也不需要改,只是換了一個更強的 router。這讓 Chi 和任何基於 net/http 的套件完全相容。
缺點:沒有內建 binding / validation,你要自己接 encoding/json + validator。
適合:不想被框架 lock-in、想用 stdlib-compatible handler 但需要更好路由 ergonomics 的場景;monolith 到 microservice 的拆分過程中特別好用,因為 handler 不需要改寫。
Fiber:效能最高,但犧牲了 stdlib 相容性
Fiber 以 Fastify(Node.js)為設計參考,底層基於 fasthttp 而不是 net/http。這讓它在 benchmark 上表現極佳——比 Gin 快 2-3 倍。
app := fiber.New()
app.Use(logger.New())
app.Get("/users/:id", func(c *fiber.Ctx) error {
id := c.Params("id")
return c.JSON(fiber.Map{"id": id})
})
app.Listen(":8080")代價:fasthttp 的 *fiber.Ctx 和標準的 http.Handler interface 完全不相容。你無法把任何基於 net/http 的 middleware 直接用在 Fiber 上——每個 middleware 都要有 Fiber 版本。
缺點:不相容 net/http 生態是很大的代價,很多 Go 套件假設你用 net/http。
適合:I/O 密集的高 QPS API,對效能有極致要求,且不需要 net/http 生態相容性的場景。
選擇決策
在乎最大生態 + binding/validation 一體?
→ Gin
偏好乾淨 API 設計 + 洋蔥 middleware?
→ Echo
想要 stdlib 相容 + 最小框架依賴?
→ Chi
需要極致效能,不在乎 net/http 相容性?
→ Fiber
只是一個小型 API / internal service?
→ net/http stdlib 就夠了
Go 後端的特殊性在於:不用框架本身也是一個有效選擇。了解 Gin / Echo / Chi / Fiber 的主要價值,是幫助你在「要不要引入框架」和「引入哪個」這兩個問題上做出有依據的判斷。
