← back to section

The standard net/http is convenient and compatible with the whole ecosystem, but the price is built in: every incoming request gets a brand-new http.Request object with fresh header maps and body slices. On typical loads you never notice — the Go garbage collector handles it. But when a service must answer hundreds of thousands of small responses per second, allocations become the main expense: work for the GC and pressure on the CPU cache.

For that case there is another path — the fasthttp library, and the Fiber framework on top of it. Let's see how they win their speed and what you pay for it.

How fasthttp wins: reuse instead of allocation

The idea of fasthttp is simple and radical: the request is not rebuilt — it is parsed straight into a reusable buffer. Bytes from the socket are not copied into fresh structures — the parser puts headers and body into memory allocated in advance, which is handed to the next request after the response. The result is almost zero allocations on the hot path, and that is what gives Fiber its benchmark numbers.

// what it looks like in fasthttp: the handler receives objects that
// belong to the server and live only while this call runs
func handler(ctx *fasthttp.RequestCtx) {
    name := ctx.QueryArgs().Peek("name")
    ctx.SetBodyString("hello, " + string(name))
}

Note the consequence from which all limitations grow: the memory where the request is parsed gets reused. The next request writes its headers into the same place. So a pointer to ctx or to the request body must not escape the handler — the data will be silently and unpredictably overwritten.

net/http: a new object per request fasthttp: one buffer for all requests Request 1 Request 2 Request 3 heap grows GC collects later an allocation per request — work for the collector request buffer parse request 1 → respond → buffer free almost no allocations on the hot path request 1 overwritten by request 2 a pointer to ctx after return — garbage or someone else's data the price: request lives only while the handler runs

Three beats of one idea: net/http pays allocations for convenience, fasthttp pays compatibility for speed.

Fiber: a familiar API on top of fasthttp

Fiber is a router and a set of middleware over fasthttp, stylistically copying Express from the Node.js world. If you have seen app.get("/", handler) in JavaScript — Fiber looks the same, and that is exactly why it is popular with teams coming from the frontend:

app := fiber.New()

app.Get("/hello", func(c *fiber.Ctx) error {
    return c.JSON(fiber.Map{"message": "hello"})
})

app.Listen(":3000")

A typical stack for such a service: Fiber v2, pgx for PostgreSQL, goose or migrate for migrations, Kubernetes on the outside. In essence it is the same layered controller/usecase/repository service as on chi — only the transport layer changes.

The price: three limitations interviews ask about

Incompatibility with net/http. fiber.Ctx is not http.ResponseWriter and *http.Request. The whole ecosystem built on http.Handler — standard middleware, httptest, otelhttp instrumentation, pprof handlers — is either unavailable or needs adapters that eat part of the win. This is the main architectural consequence, and it is not cured by "just plug in a library".

Request lifetime. Recall the animation: the buffer is reused. So the classic mistake is to store the context, hand the request body to a goroutine, or put it into a cache:

// don't: ctx and its body will be overwritten by the next request
app.Post("/orders", func(c *fiber.Ctx) error {
    go processAsync(c) // c stops being valid right after return
    return c.SendStatus(fiber.StatusAccepted)
})
// do: copy the data you need into your own memory before returning
app.Post("/orders", func(c *fiber.Ctx) error {
    body := bytes.Clone(c.Body()) // fresh memory, belongs to us
    go processAsync(body)
    return c.SendStatus(fiber.StatusAccepted)
})

Team ramp-up. Debugging, profiling and the usual tools assume the standard library. Moving to fasthttp, the team must understand its memory model, otherwise data races and corruption will only be found in production.

Let's check how net/http contrasts: there you can safely store the request, the object belongs to you. This example runs by itself:

live example

package main

import (
    "fmt"
    "net/http"
    "net/http/httptest"
)

func main() {
    req := httptest.NewRequest("GET", "/hello", nil)
    rec := httptest.NewRecorder()
    handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprint(w, "hello")
    })
    handler.ServeHTTP(rec, req)

    saved := req // the object stays valid after handling
    fmt.Println(saved.Method, rec.Body.String())
}
Run

Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →

Deeper: how to read the benchmarks

In synthetic "hello world" tests Fiber outperforms net/http several times over in requests per second. Treat such numbers like an engine spec at idle: in a real service time goes to the database, JSON serialization and the network, while the transport is one or two percent of the budget. Fiber is justified where the transport itself is the real bottleneck: push gateways, proxies, game backends, APIs serving identical small responses to millions of clients. For a typical business service with SQL and network calls the difference will be within the noise — while the compatibility cost stays.

Short version

  • fasthttp wins by reusing buffers — almost zero allocations per request.
  • You cannot store fiber.Ctx: request memory is overwritten by the next one — copy data before return.
  • Fiber is incompatible with http.Handler: the net/http ecosystem, httptest and otelhttp are not directly available.
  • People pick Fiber for the Express-like API and maximum RPS on small responses; a typical business service will not feel the advantage.
  • Rule of thumb: start with chi over net/http, move to fasthttp only when measurements demand it.
  • Routing in Go: net/http and chi — the baseline choice to start with, where Gin and Echo are mentioned.
  • Middleware — wrapper chains around http.Handler, which Fiber replaces with its own analogs.
  • Load testing — how to measure whether the transport is actually your bottleneck.