Fiber is a web framework built on top of fasthttp — a high-performance HTTP engine that replaces Go’s standard net/http for maximum throughput. Fiber’s API is deliberately similar to Express.js, making it approachable for developers coming from Node.js.
The tradeoff: fasthttp uses a request context model incompatible with the standard context.Context — Fiber’s *fiber.Ctx is not an http.Request. Middleware and libraries designed for net/http don’t work with Fiber without adaptation. For services that need maximum JSON throughput or are migrating from Express.js, Fiber is a strong choice. For services that need broad ecosystem compatibility, Gin or the standard library is safer.
For a comparison framework see Go Gin framework. For the standard library approach see Go HTTP client and server.
Installation and Basic Setup
go get github.com/gofiber/fiber/v2
Fiber creates its own server with fiber.New(). The configuration struct lets you set timeouts, body size limits, and error handlers upfront — rather than setting them imperatively after creation:
app := fiber.New(fiber.Config{
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
BodyLimit: 4 * 1024 * 1024, // 4 MB max request body
ErrorHandler: func(c *fiber.Ctx, err error) error {
code := fiber.StatusInternalServerError
var fe *fiber.Error
if errors.As(err, &fe) {
code = fe.Code
}
return c.Status(code).JSON(fiber.Map{"error": err.Error()})
},
})
app.Use(logger.New()) // access log
app.Use(recover.New()) // recover from panics
app.Listen(":8080")
Fiber’s ErrorHandler centralizes error handling — handlers return errors, the handler converts them to responses. This keeps handler functions clean.
Routing
Fiber’s router is one of the fastest available — it uses a radix tree and avoids allocations on the hot path. Route parameters, wildcards, and optional segments work as in other frameworks:
// Static routes
app.Get("/health", handleHealth)
// Path parameters — c.Params("id") retrieves them
app.Get("/users/:id", getUser)
app.Put("/users/:id", updateUser)
app.Delete("/users/:id", deleteUser)
// Wildcard — matches /files/a/b/c
app.Get("/files/*", serveFile)
// Optional parameter — :name? is present or absent
app.Get("/greet/:name?", greet)
func getUser(c *fiber.Ctx) error {
id := c.Params("id")
user, err := db.GetUser(c.Context(), id) // c.Context() returns a context.Context
if errors.Is(err, ErrNotFound) {
return fiber.ErrNotFound // returns a *fiber.Error with code 404
}
if err != nil {
return err // caught by ErrorHandler
}
return c.JSON(user)
}
Route Groups and Middleware
Groups organize routes and apply middleware to subsets of routes:
api := app.Group("/api/v1")
// Public endpoints — no auth
api.Post("/auth/login", handleLogin)
api.Post("/auth/register", handleRegister)
// Protected — authMiddleware runs first
protected := api.Group("", authMiddleware)
protected.Get("/users", listUsers)
protected.Get("/users/:id", getUser)
protected.Post("/users", createUser)
// Admin-only — both middlewares run
admin := api.Group("/admin", authMiddleware, requireRole("admin"))
admin.Get("/stats", getStats)
admin.Delete("/users/:id", deleteUser)
Middleware in Fiber is a func(*fiber.Ctx) error. Call c.Next() to pass to the next handler; return an error or skip c.Next() to short-circuit:
func authMiddleware(c *fiber.Ctx) error {
token := c.Get("Authorization") // c.Get reads a request header
if token == "" {
return fiber.ErrUnauthorized
}
claims, err := validateToken(strings.TrimPrefix(token, "Bearer "))
if err != nil {
return fiber.NewError(fiber.StatusUnauthorized, "invalid token")
}
// Store claims for downstream handlers
c.Locals("userID", claims.UserID)
c.Locals("role", claims.Role)
return c.Next()
}
func requireRole(role string) fiber.Handler {
return func(c *fiber.Ctx) error {
if c.Locals("role") != role {
return fiber.ErrForbidden
}
return c.Next()
}
}
c.Locals is the Fiber equivalent of context.WithValue — stores request-scoped data for the duration of the request.
Request Parsing
Fiber’s BodyParser decodes JSON, form data, or multipart form based on Content-Type:
type CreateUserRequest struct {
Name string `json:"name" form:"name" validate:"required,min=2"`
Email string `json:"email" form:"email" validate:"required,email"`
Age int `json:"age" validate:"min=0,max=150"`
}
func createUser(c *fiber.Ctx) error {
var req CreateUserRequest
if err := c.BodyParser(&req); err != nil {
return fiber.NewError(fiber.StatusBadRequest, "invalid request body")
}
// Validate after parsing
if err := validate.Struct(&req); err != nil {
return fiber.NewError(fiber.StatusUnprocessableEntity, err.Error())
}
user, err := userService.Create(c.Context(), req.Name, req.Email)
if err != nil {
return err
}
return c.Status(fiber.StatusCreated).JSON(user)
}
Query parameters and path parameters have dedicated accessors:
// GET /search?q=golang&page=2&per_page=20
func search(c *fiber.Ctx) error {
query := c.Query("q")
page := c.QueryInt("page", 1) // with default
perPage := c.QueryInt("per_page", 20)
results, err := searchService.Search(c.Context(), query, page, perPage)
if err != nil {
return err
}
return c.JSON(fiber.Map{
"results": results,
"page": page,
"per_page": perPage,
})
}
When Fiber Gives a Real Advantage
Fiber’s fasthttp backend allocates less per request than net/http, which helps in two specific scenarios:
Very high JSON throughput — if your service handles 50k+ small JSON requests per second and is CPU-bound on serialization, Fiber’s zero-copy request reading and reduced allocation pressure can meaningfully improve throughput.
Migrating from Node.js/Express — the API is intentionally similar. c.Params, c.Query, c.Body, c.JSON, c.Status, c.Send — all follow Express conventions.
For most services, the difference is not measurable under production conditions. Benchmark your specific workload before choosing Fiber over Gin or standard library.
Key constraint: Fiber’s *fiber.Ctx is not compatible with Go’s standard http.Handler interface. If your code needs to use net/http middleware (CORS, rate limiting, tracing libraries that wrap http.Handler), you’ll need to find Fiber-specific versions or write adapters.
File Uploads
func uploadFile(c *fiber.Ctx) error {
file, err := c.FormFile("file")
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, "no file provided")
}
// Validate size (also set app BodyLimit for early rejection)
if file.Size > 10<<20 { // 10 MB
return fiber.NewError(fiber.StatusRequestEntityTooLarge, "file too large")
}
// Sanitize filename — prevent path traversal
filename := filepath.Base(file.Filename)
dst := filepath.Join("uploads", filename)
if err := c.SaveFile(file, dst); err != nil {
return err
}
return c.JSON(fiber.Map{"filename": filename, "size": file.Size})
}
Summary
- Fiber is built on
fasthttp— faster thannet/httpfor CPU-bound throughput, but incompatible withnet/httpmiddleware - Set
fiber.Config.BodyLimit,ReadTimeout,WriteTimeout, andErrorHandlerat startup - Route groups + middleware slice apply authorization to subsets of routes cleanly
c.Locals(key, val)stores request-scoped data (user ID, claims); retrieve withc.Locals(key)BodyParserhandles JSON, form data, and multipart — validate separately withgo-playground/validator- Choose Fiber for very high-throughput JSON APIs or Express.js migrations; choose Gin or standard library for broader ecosystem compatibility
Comments