Go Web Frameworks: net/http, Chi, Gin, Echo and Fiber · The Standard Library and the Big Four
Fiber: Express-Style Go on fasthttp
Fiber feels like Express.js and is built on fasthttp for raw speed. Build the books API with Fiber v3, and learn the trade-offs: it isn't net/http, and its context values are reused between requests.
What you'll build
The books API with Fiber v3, tested with app.Test, plus a demonstration of the one Fiber rule that bites newcomers: values from the request context are only valid during the request.
The idea
Fiber (github.com/gofiber/fiber/v3) has an Express-like API (app.Get, c.Params, c.JSON) that's comfortable for developers coming from Node.js. It runs on fasthttp instead of net/http, which is very fast because it reuses memory aggressively.
The trade-offs:
- Not
net/http. Handlers and middleware have their own signatures (func(c fiber.Ctx) error); standardhttp.Handlermiddleware needs an adapter, and some libraries expectnet/http. - Context reuse. Fiber reuses the context and its buffers for the next request. Strings you read (params, headers, body) may change after your handler returns, so copy anything you keep (store in a map, pass to a goroutine). Fiber v3 has an
Immutableconfig option that copies everything at a small cost. - HTTP/2 isn't supported by fasthttp directly; put a proxy in front if you need it.
In v3, fiber.Ctx is an interface, binding uses c.Bind().Body(&in), and app.Test(req) runs a request in memory.
The code
package main
import (
"fmt"
"io"
"net/http/httptest"
"strings"
"github.com/gofiber/fiber/v3"
)
type Book struct {
Title string `json:"title"`
Author string `json:"author"`
}
func main() {
app := fiber.New()
saved := map[string]Book{}
api := app.Group("/api/v1")
api.Get("/books/:id", func(c fiber.Ctx) error {
b, ok := saved[c.Params("id")]
if !ok {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "book not found"})
}
return c.JSON(b)
})
api.Post("/books/:id", func(c fiber.Ctx) error {
var in Book
if err := c.Bind().Body(&in); err != nil || in.Title == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "title is required"})
}
id := strings.Clone(c.Params("id")) // copy: Fiber reuses the context's memory after this request
saved[id] = in
return c.Status(fiber.StatusCreated).JSON(fiber.Map{"id": id})
})
send := func(method, path, body string) {
req := httptest.NewRequest(method, path, strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
res, err := app.Test(req)
if err != nil {
fmt.Println("error:", err)
return
}
out, _ := io.ReadAll(res.Body)
fmt.Printf("%s %s -> %d %s\n", method, path, res.StatusCode, out)
}
send("POST", "/api/v1/books/7", `{"title":"Let's Go","author":"Alex Edwards"}`)
send("GET", "/api/v1/books/7", "")
send("GET", "/api/v1/books/8", "")
send("POST", "/api/v1/books/9", `{"author":"nobody"}`)
}
Output
POST /api/v1/books/7 -> 201 {"id":"7"}
GET /api/v1/books/7 -> 200 {"title":"Let's Go","author":"Alex Edwards"}
GET /api/v1/books/8 -> 404 {"error":"book not found"}
POST /api/v1/books/9 -> 400 {"error":"title is required"}Run this on your computer — see “Get Started” for setup.
Remove the strings.Clone and the code still passes this small test, which is exactly why the bug is dangerous: under real load, keys stored from reused buffers can silently change.
Common mistakes
- Keeping references to request data after the handler returns (map keys, goroutines). Copy them, or enable
Immutable. - Assuming any
net/httpmiddleware plugs in. Use Fiber's middleware or the adaptor package. - Choosing Fiber only for benchmark numbers. Most APIs are limited by the database, not the router; pick for ergonomics and ecosystem.
- Mixing v2 and v3 code. v3 changed handler signatures (
fiber.Ctxinterface), binding (c.Bind()), and several APIs; check which version an example uses.
Key points
- Fiber: Express-like API on fasthttp, very fast.
- Not
net/http-compatible; adapters exist but add friction. - Request-scoped values are reused: copy what you keep, or use
Immutable. - v3:
func(c fiber.Ctx) error,c.Bind().Body(&in),app.Test(req).
Further reading: Fiber documentation, fiber v3 on pkg.go.dev.
Practice questions
What is the main architectural difference between Fiber and Gin, Echo or Chi?
Answer
Fiber is built on fasthttp, not net/http. Its handlers use a Fiber-specific context and signature, so standard http.Handler middleware and libraries need an adapter, while Gin, Echo and Chi all build on net/http.
Why must you copy strings from a Fiber context if you keep them after the handler returns?
Answer
fasthttp reuses request memory for performance, so strings from params, headers or the body may point into buffers that are overwritten by the next request. Copy them (strings.Clone) or enable Fiber's Immutable setting.
Open in the app to mark lessons complete, take notes and track your progress.