Post

Go web frameworks

Some benchmarks of Go's standard library against popular frameworks.

I’ve been using Go for a while now and I had this question in the beginning - should I use the standard library or pick a framework? So I decided to stop guessing and actually benchmark these things properly.

This isn’t going to be one of those posts where I tell you that it depends and leave you hanging. Well I hope not. We’ll see real numbers from my machine, and I’ll try to explain what they actually mean. Also will try to add code snippets wherever possible.

The contenders

Here’s what we’re comparing:

  • net/http — Go’s standard library. No dependencies, no magic. Docs here.
  • Fiber — Built on top of fasthttp. Claims to be the fastest. Docs here.
  • Gin — Probably the most popular Go web framework. Docs here.
  • Echo — Another popular choice, known for being minimal. Docs here.

Let’s do some analysis.

My setup

Here’s what I’m running:

1
2
3
4
5
Model Name: MacBook Pro
Model Identifier: Mac16,8
Chip: Apple M4 Pro
Total Number of Cores: 14 (10 performance and 4 efficiency)
Memory: 48 GB

For benchmarking HTTP, I’m using wrk!

If you are on mac and happen to use brew

  • you can install it using brew install wrk
  • here’s the GitHub repo for wrk
1
wrk -t12 -c400 -d30s http://localhost:3000/ping

That’s 12 threads, 400 concurrent connections, running for 30 seconds. Decently fine kind of load where differences actually show up.

The setup

Each server does the exact same thing:

  • Return “pong” when we hit /ping
  • And yeah - no JSON parsing, no middleware, no database calls
  • Just raw throughput :)

One important thing I wanna highlight is that this benchmark only measures raw routing overhead. Like how fast the framework can accept a request and send a response. Real-world performance depends on a lot more: JSON serialization, database queries, middleware chains (logging, auth, CORS), number of routes, connection pooling, etc. Take these numbers as a comparison of framework overhead, not as a predictor of a Go app’s actual performance.

Standard Library (net/http)

1
2
3
4
5
6
7
8
9
10
11
12
package main

import (
    "net/http"
)

func main() {
    http.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("pong"))
    })
    http.ListenAndServe(":3000", nil)
}

Nothing fancy. This is what your code looks like when you refuse to import anything :P (good preference tbh)

Fiber

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import (
    "log"
    "github.com/gofiber/fiber/v3"
)

func main() {
    app := fiber.New()
    app.Get("/ping", func(c fiber.Ctx) error {
        return c.SendString("pong!")
    })
    log.Fatal(app.Listen(":3000"))
}

Fiber’s syntax always reminded me of Express.js lol.

Gin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import (
    "github.com/gin-gonic/gin"
)

func main() {
    gin.SetMode(gin.ReleaseMode)
    r := gin.New()
    r.GET("/ping", func(c *gin.Context) {
        c.String(200, "pong")
    })
    r.Run(":3000")
}

Note: I’m using gin.New() instead of gin.Default() to skip the default logger and recovery middleware. Fairer comparison that way.

Echo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import (
    "net/http"
    "github.com/labstack/echo/v4"
)

func main() {
    e := echo.New()
    e.HideBanner = true
    e.GET("/ping", func(c echo.Context) error {
        return c.String(http.StatusOK, "pong")
    })
    e.Start(":3000")
}

Echo feels a lot like Gin. The API is slightly different but the vibe is the same.

Results!!!!

Alright, here’s what happened when I ran wrk against each server:

Framework Requests/sec Avg Latency Transfer/sec
Fiber 163,150 2.23ms 18.83 MB
Gin 90,474 4.34ms 10.35 MB
net/http 90,157 4.35ms 10.32 MB
Echo 89,452 4.38ms 10.24 MB

Let me break this down:

Fiber is genuinely fast

Fiber sitting at the top with ~163k req/sec — almost 1.8x faster than the rest. It is built on fasthttp, which takes a completely different approach than net/http. The fasthttp library reuses request/response objects aggressively and avoids allocations wherever possible. That matters a lot under heavy load.

But Fiber’s speed comes with a tradeoff. It’s not compatible with the standard net/http interfaces. All those nice middleware packages built for standard Go? Yeah, they won’t work. You’re locked into Fiber’s ecosystem.

Gin, net/http, and Echo are basically tied

This is the interesting part. Gin, net/http, and Echo all hover around 90k req/sec. The difference between them is honestly just noise. And we’re talking about ~1% variance.

Gin slightly edging out net/http was unexpected tbh. Shows that a well-optimized router does not necessarily add overhead. Gin’s radix tree routing is pretty efficient.

The gap exists

Fiber is clearly in a different league here. If raw throughput is your main concern and you’re okay with the fasthttp ecosystem, Fiber is the obvious choice.

But for net/http-based frameworks (Gin, Echo), you’re not losing anything significant. The overhead is basically zero. In exchange, you get:

  • a cleaner routing syntax
  • built-in parameter extraction
  • middleware chains
  • request binding and validation

For most apps, that tradeoff is worth it. Atleast I would like to think so.

What about memory?

Raw throughput is only part of the story. I also watched memory usage during the benchmarks:

Framework Baseline Under Load Stable After Load
net/http ~8 MB ~45 MB ~15 MB
Fiber ~10 MB ~55 MB ~18 MB
Gin ~12 MB ~52 MB ~20 MB
Echo ~11 MB ~50 MB ~19 MB

All of these are completely reasonable. The numbers spike during the benchmark (because goroutines, connection buffers, etc.) and then settle back down. Go’s garbage collector does its job.

Fiber uses slightly more memory because fasthttp maintains its own connection pools. Not a big deal unless you’re running on a very constrained system.

Some observations

The framework rarely matters for performance.

Imo, in a real application, your request handler isn’t just returning “pong” of course, rather it is:

  • parsing JSON
  • validating input
  • calling a database
  • maybe calling other services
  • serializing a response

Any of these operations will dwarf the overhead of your framework. A single database query is 1-10ms. And the framework’s overhead, maybe 10 microseconds.

That’s a 100x-1000x difference.

If your service is slow, it’s almost never because of Gin vs Echo. It’s because of:

  1. slow database queries
  2. missing indexes
  3. n+1 query problems — here’s a nice goprisma article on that
  4. network calls that didn’t get cached
  5. JSON serialization of massive payloads

So what should we use imo?

After all this benchmarking, here’s my honest take:

Use net/http if:

  • You want zero dependencies
  • Your routing needs are simple
  • You’re building libraries or packages others will import
  • You enjoy the pure Go aesthetic

Use Fiber if:

  • Raw throughput is genuinely your bottleneck
  • You’re okay with the fasthttp ecosystem
  • If you’re from node background and coming from Express.js and want something familiar
  • You’re doing something weird like a proxy or API gateway

Use Gin or Echo if:

  • You’re building a typical REST API
  • You want help with routing, validation, and middleware
  • You want to use existing middleware from the community
  • Developer productivity matters more than squeezing out 20% more throughput

For most people reading this? Gin or Echo. Seriously. They’re fast enough, they have great ecosystems, and they’ll save you time.


I am not really a number person to be honest (very relevant from the fact that I ride a Royal Enfield lmao and drive a Honda). I just had some time to spare and I wanted to see if there’s a huge difference or not. Oh and the image is generated from Google Gemini.

This post is licensed under CC BY 4.0 by the author.