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.

Create a free account to save this page, take notes and track your progress. Sign in

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.

One handler, three routers
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.Context into 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/http compatibility (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.

Was this page helpful?