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/httphttp.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 的主要價值,是幫助你在「要不要引入框架」和「引入哪個」這兩個問題上做出有依據的判斷。


延伸閱讀