Skip to main content

Fiber: High-Performance Web Framework for Go

Published: May 8, 2026 Updated: August 29, 2026 Larry Qu 5 min read

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 than net/http for CPU-bound throughput, but incompatible with net/http middleware
  • Set fiber.Config.BodyLimit, ReadTimeout, WriteTimeout, and ErrorHandler at startup
  • Route groups + middleware slice apply authorization to subsets of routes cleanly
  • c.Locals(key, val) stores request-scoped data (user ID, claims); retrieve with c.Locals(key)
  • BodyParser handles JSON, form data, and multipart — validate separately with go-playground/validator
  • Choose Fiber for very high-throughput JSON APIs or Express.js migrations; choose Gin or standard library for broader ecosystem compatibility

Resources

Comments

👍 Was this article helpful?