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.

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

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); standard http.Handler middleware needs an adapter, and some libraries expect net/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 Immutable config 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

Fiber v3 routes, binding and app.Test
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/http middleware 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.Ctx interface), 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.

Was this page helpful?