Go Web Frameworks: net/http, Chi, Gin, Echo and Fiber · Building Production APIs in Go
Choosing a Go Framework (or None)
Compare net/http, Chi, Gin, Echo and Fiber on what actually matters: compatibility, features, error handling, ecosystem and team familiarity. Raw benchmark speed rarely decides it.
The problem
Every new Go service starts with the same debate: Gin or Echo? Fiber is fastest, isn't it? Or should we "just use the standard library"? Teams lose days to it, and the answer is usually less important than they think, because most API time is spent in the database, not the router.
Real-world analogy
Choosing a framework is like choosing a car for a city commute. A sports car (the benchmark winner) is exciting, but parking, running costs and how many people can drive it matter more day to day. Pick for the trips you'll actually make.
The idea
| net/http (1.22+) | Chi | Gin | Echo | Fiber | |
|---|---|---|---|---|---|
| Built on net/http | yes | yes | yes | yes | no (fasthttp) |
| Standard middleware work as-is | yes | yes | via wrappers | via wrappers | via adaptor |
| Path params and methods | yes | yes | yes | yes | yes |
| Route groups | manual | yes | yes | yes | yes |
| Binding + validation | manual | manual | built in | built in (pluggable validator) | built in |
| Error style | write responses | write responses | write responses | return errors | return errors |
| Dependencies | none | tiny | moderate | moderate | moderate |
| Feels like | Go itself | Go itself | its own Context | its own Context | Express.js |
A practical guide:
- Small services, libraries, maximum longevity:
net/http. - You like the standard library but want groups and middleware: Chi.
- A big ecosystem, the most examples and hiring familiarity: Gin.
- Central error handling and a clean API: Echo.
- A team coming from Express, or very high request rates with simple handlers: Fiber, accepting that it isn't
net/http.
Whatever you choose, keep your business logic outside handlers (services and repositories with plain Go types). Then switching frameworks touches only a thin HTTP layer.
The code
This program shows why the net/http compatibility column matters: one plain handler and one standard middleware work unchanged in the standard mux and Chi, and in Gin through gin.WrapH.
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"github.com/gin-gonic/gin"
"github.com/go-chi/chi/v5"
)
// Plain net/http code: no framework imports.
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hello from %s", r.URL.Path)
}
func poweredBy(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Powered-By", "plain net/http middleware")
next.ServeHTTP(w, r)
})
}
func main() {
std := http.NewServeMux()
std.Handle("GET /hello", poweredBy(http.HandlerFunc(hello)))
c := chi.NewRouter()
c.Use(poweredBy)
c.Get("/hello", hello)
gin.SetMode(gin.ReleaseMode)
g := gin.New()
g.GET("/hello", gin.WrapH(poweredBy(http.HandlerFunc(hello))))
for _, r := range []struct {
name string
router http.Handler
}{{"net/http", std}, {"chi", c}, {"gin", g}} {
rec := httptest.NewRecorder()
r.router.ServeHTTP(rec, httptest.NewRequest("GET", "/hello", nil))
fmt.Printf("%-9s %d %q (%s)\n", r.name, rec.Code, rec.Body.String(), rec.Header().Get("X-Powered-By"))
}
}
Output
net/http 200 "hello from /hello" (plain net/http middleware) chi 200 "hello from /hello" (plain net/http middleware) gin 200 "hello from /hello" (plain net/http middleware)
Run this on your computer — see “Get Started” for setup.
Common mistakes
- Choosing by benchmarks. A router that's 3× faster saves microseconds on requests that wait milliseconds for the database.
- Business logic inside handlers, which locks you into a framework and makes testing harder.
- Mixing frameworks' context types into domain code. Pass plain values and
context.Contextinto services. - Ignoring the team. The framework people already know (or can hire for) is usually the productive choice.
- Not checking maintenance and versions. Prefer actively maintained projects and current major versions.
Key points
- Most APIs are fine with any of the five; decide quickly and move on.
net/httpcompatibility (std, Chi, Gin, Echo) means shared middleware and tools; Fiber trades that for speed and Express ergonomics.- Gin = ecosystem, Echo = returned errors, Chi = standard-library style, Fiber = Express style.
- Keep business logic framework-free so switching stays cheap.
Practice questions
How would you choose between net/http, Chi, Gin, Echo and Fiber for a new service?
Answer
Start from needs: small services can use net/http (Go 1.22+ routing); Chi if you want standard-library style with groups and middleware; Gin for ecosystem and familiarity; Echo for returned errors and a central error handler; Fiber for Express-style ergonomics or extreme throughput, accepting it isn't net/http. Team familiarity and maintenance matter more than router benchmarks.
How do you keep a Go service from being locked into one web framework?
Answer
Keep business logic in plain Go services and repositories that take context.Context and plain types, and keep handlers thin: they parse input, call the service and write output. Only that thin layer depends on the framework.
Open in the app to mark lessons complete, take notes and track your progress.