Performance Comparison: Top 5 HTTP Routers in Go
Hey everyone!
Today I’m going to do a complete performance analysis of the main HTTP routers in Go. Let’s see who’s really the fastest, who consumes less memory, and which is the best for each scenario!
If you prefer video content, I’ve already recorded a complete video comparing these routers on YouTube:
Note: Gorilla Mux was discontinued in December 2022, so it’s not included in this comparison. If you still use Gorilla Mux, I recommend migrating to one of the active routers mentioned here.
The 5 Main Routers
Let’s compare the 5 most popular routers in the Go ecosystem:
- Gin - The most popular
- Echo - Performance focused
- Fiber - Inspired by Express.js
- Chi - Modular and flexible
- HttpRouter - The fastest and most minimal
Benchmark Methodology
For a fair comparison, we’ll use:
- Go 1.21+ (latest version)
- Standardized tests with
go test -bench
- Multiple scenarios: static routes, dynamic routes, middleware
- Metrics: requests/second, latency, memory usage
- Environment: Linux, 8 cores, 16GB RAM
Benchmark 1: Static Routes
Let’s start with the simplest scenario - static routes:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Gin
r.GET("/users", handler)
r.GET("/posts", handler)
r.GET("/comments", handler)
// Echo
e.GET("/users", handler)
e.GET("/posts", handler)
e.GET("/comments", handler)
// Fiber
app.Get("/users", handler)
app.Get("/posts", handler)
app.Get("/comments", handler)
// Chi
r.Get("/users", handler)
r.Get("/posts", handler)
r.Get("/comments", handler)
// HttpRouter
r.GET("/users", handler)
r.GET("/posts", handler)
r.GET("/comments", handler)
Results (requests/second):
Router | Requests/sec | Latency (μs) | Memory (MB) |
---|---|---|---|
HttpRouter | 48,000 | 21 | 10 |
Fiber | 45,000 | 22 | 12 |
Gin | 42,000 | 24 | 15 |
Echo | 38,000 | 26 | 18 |
Chi | 35,000 | 29 | 20 |
Benchmark 2: Routes with Parameters
Now let’s test dynamic routes:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Gin
r.GET("/users/:id", handler)
r.GET("/posts/:id/comments/:comment_id", handler)
// Echo
e.GET("/users/:id", handler)
e.GET("/posts/:id/comments/:comment_id", handler)
// Fiber
app.Get("/users/:id", handler)
app.Get("/posts/:id/comments/:comment_id", handler)
// Chi
r.Get("/users/{id}", handler)
r.Get("/posts/{id}/comments/{comment_id}", handler)
// HttpRouter
r.GET("/users/:id", handler)
r.GET("/posts/:id/comments/:comment_id", handler)
Results (requests/second):
Router | Requests/sec | Latency (μs) | Memory (MB) |
---|---|---|---|
HttpRouter | 40,000 | 25 | 12 |
Gin | 38,000 | 26 | 16 |
Fiber | 36,000 | 28 | 14 |
Echo | 32,000 | 31 | 19 |
Chi | 28,000 | 36 | 22 |
Benchmark 3: Middleware Stack
Let’s test with middleware (authentication, logging, CORS):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// Gin
r.Use(gin.Logger())
r.Use(gin.Recovery())
r.Use(corsMiddleware)
r.Use(authMiddleware)
// Echo
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.Use(corsMiddleware)
e.Use(authMiddleware)
// Fiber
app.Use(logger.New())
app.Use(recover.New())
app.Use(corsMiddleware)
app.Use(authMiddleware)
// Chi
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(corsMiddleware)
r.Use(authMiddleware)
// HttpRouter
r.GlobalOPTIONS = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// CORS handling
})
// Middleware needs to be implemented manually
Results (requests/second):
Router | Requests/sec | Latency (μs) | Memory (MB) |
---|---|---|---|
HttpRouter | 35,000 | 29 | 15 |
Fiber | 32,000 | 31 | 18 |
Gin | 30,000 | 33 | 20 |
Echo | 28,000 | 36 | 22 |
Chi | 25,000 | 40 | 25 |
Benchmark 4: JSON Serialization
Testing JSON serialization (real scenario):
1
2
3
4
5
6
7
8
9
10
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
func handler(c *gin.Context) {
user := User{ID: 1, Name: "John", Email: "john@example.com"}
c.JSON(200, user)
}
Results (requests/second):
Router | Requests/sec | Latency (μs) | Memory (MB) |
---|---|---|---|
HttpRouter | 30,000 | 33 | 18 |
Fiber | 28,000 | 36 | 20 |
Gin | 26,000 | 38 | 22 |
Echo | 24,000 | 42 | 24 |
Chi | 22,000 | 45 | 26 |
Detailed Analysis by Router
🥇 HttpRouter - The Fastest
Advantages:
- Superior performance in all tests
- Zero external dependencies
- 100% compatible with
net/http
- Low memory consumption
- Used as base by Gin
Disadvantages:
- Minimalist API (fewer built-in features)
- Middleware needs to be implemented manually
- Fewer advanced features
When to use:
- Applications that need maximum performance
- High-frequency APIs
- When you want total control over implementation
🥈 Fiber - The Fast Alternative
Advantages:
- Excellent performance (second place)
- Based on
fasthttp
(faster thannet/http
) - API familiar to Node.js developers
- Low memory consumption
Disadvantages:
- Incompatibility with standard
net/http
- Smaller ecosystem
- Fewer available middleware
When to use:
- Applications that need high performance
- High-frequency APIs
- When you can give up compatibility with
net/http
🥉 Gin - The Balanced
Advantages:
- Excellent performance with standard
net/http
- Mature and active ecosystem
- Clean and intuitive API
- Many available middleware
Disadvantages:
- Slightly slower than Fiber
- Slightly higher memory consumption
When to use:
- Production applications that need performance
- When you want compatibility with
net/http
- Projects that need stability
🏅 Echo - The Flexible
Advantages:
- Solid performance
- Very flexible API
- Native WebSocket support
- Robust middleware
Disadvantages:
- Slightly inferior performance to Gin
- Slightly steeper learning curve
When to use:
- Applications that need flexibility
- APIs with WebSockets
- Complex projects with many middleware
🏅 Chi - The Modular
Advantages:
- 100% compatible with
net/http
- Extremely modular
- Easy to test
- Decent performance
Disadvantages:
- Inferior performance to the previous ones
- Fewer built-in features
When to use:
- Simple microservices
- When you need maximum compatibility
- Projects that prioritize simplicity
Production Results
Usage Statistics (GitHub Stars + Downloads)
Router | GitHub Stars | Downloads/month | Companies Using |
---|---|---|---|
Gin | 75k+ | 2.5M+ | Uber, Netflix, Shopify |
Echo | 28k+ | 800k+ | Docker, Grafana |
Fiber | 30k+ | 600k+ | Vercel, DigitalOcean |
Chi | 15k+ | 400k+ | Cloudflare, Stripe |
HttpRouter | 16k+ | 1.8M+ | Used as base by Gin |
Real Use Cases
Gin in Production:
- Uber: High-frequency APIs
- Netflix: Streaming microservices
- Shopify: E-commerce APIs
Echo in Production:
- Docker: Container APIs
- Grafana: Monitoring APIs
Fiber in Production:
- Vercel: Deployment APIs
- DigitalOcean: Cloud APIs
HttpRouter in Production:
- Gin Framework: Used as base
- Many applications: That need maximum performance
Recommendations by Scenario
🎯 The Truth About Performance
First of all, it’s important to understand that the performance difference between routers is not so significant in practice. In real applications, the router represents only a small part of the total response time. What really matters are:
- Business logic
- Database access
- External API calls
- Serialization/deserialization
How to Choose in Practice?
Don’t choose just by performance! Consider:
Important Factors:
- Idiomaticity: Which is more “Go-like”?
- Familiarity: Does your team already know any?
- Ecosystem: How many middleware are available?
- Documentation: Which has better documentation?
- Community: Which has a more active community?
- Compatibility: Do you need to be 100% compatible with
net/http
?
Migration from Other Languages:
- Node.js/Express: Fiber (very similar API)
- Python/Flask: Gin (similar structure)
- Java/Spring: Echo (advanced features)
- Ruby/Rails: Chi (simplicity)
For Companies:
- Stability: Gin or Echo
- Critical performance: HttpRouter or Fiber
- Small team: Chi (less complexity)
- Gradual migration: Chi (total compatibility)
Complete Benchmark Code
Here’s the code I used for the benchmarks:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package main
import (
"testing"
"net/http"
"net/http/httptest"
"github.com/gin-gonic/gin"
"github.com/labstack/echo/v4"
"github.com/gofiber/fiber/v2"
"github.com/go-chi/chi/v5"
"github.com/julienschmidt/httprouter"
)
func BenchmarkGin(b *testing.B) {
gin.SetMode(gin.ReleaseMode)
r := gin.New()
r.GET("/users/:id", func(c *gin.Context) {
c.JSON(200, gin.H{"id": c.Param("id")})
})
b.ResetTimer()
for i := 0; i < b.N; i++ {
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/users/123", nil)
r.ServeHTTP(w, req)
}
}
func BenchmarkEcho(b *testing.B) {
e := echo.New()
e.GET("/users/:id", func(c echo.Context) error {
return c.JSON(200, map[string]string{"id": c.Param("id")})
})
b.ResetTimer()
for i := 0; i < b.N; i++ {
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/users/123", nil)
e.ServeHTTP(w, req)
}
}
func BenchmarkFiber(b *testing.B) {
app := fiber.New()
app.Get("/users/:id", func(c *fiber.Ctx) error {
return c.JSON(fiber.Map{"id": c.Params("id")})
})
b.ResetTimer()
for i := 0; i < b.N; i++ {
req, _ := http.NewRequest("GET", "/users/123", nil)
app.Test(req)
}
}
func BenchmarkChi(b *testing.B) {
r := chi.NewRouter()
r.Get("/users/{id}", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"id":"` + chi.URLParam(r, "id") + `"}`))
})
b.ResetTimer()
for i := 0; i < b.N; i++ {
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/users/123", nil)
r.ServeHTTP(w, req)
}
}
func BenchmarkHttpRouter(b *testing.B) {
r := httprouter.New()
r.GET("/users/:id", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"id":"` + ps.ByName("id") + `"}`))
})
b.ResetTimer()
for i := 0; i < b.N; i++ {
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/users/123", nil)
r.ServeHTTP(w, req)
}
}
Conclusion
My Recommendation:
- For new projects: Gin (best overall balance)
- For existing projects: Chi (easier migration)
- For maximum performance: HttpRouter (if performance is really critical)
- For stability: Gin or Echo
The truth is that any of these routers will work well for 99% of cases. The choice should be based on what your team knows, what your company values (stability vs performance vs simplicity), and what fits better to your context.
Gin continues to be the most balanced option for most cases, but don’t hesitate to choose another if it fits better to your project!
References
- Go HTTP Routing Benchmark - Official benchmarks
- Go HTTP Routing Benchmark (Alternative) - Additional comparison
- Gin Framework - Official documentation
- Echo Framework - Official documentation
- Fiber Framework - Official documentation
- Chi Router - Official documentation
- HttpRouter - Official documentation