# Fiber User Guide

**Fiber** is an [Express](https://github.com/expressjs/express)-inspired web framework for Go, built on top of [fasthttp](https://github.com/valyala/fasthttp) — one of the fastest HTTP engines available for Go. If you have ever built a web app or API in Node.js with Express, Fiber will feel immediately familiar: you create an app, attach routes and middleware, read the request and write the response through a single context object, and start listening.

This guide covers **Fiber v3** (module path `github.com/gofiber/fiber/v3`). It walks you through installing Fiber, defining routes, working with the request/response context, parsing incoming data, applying the built-in middleware, and shipping a production server. Every command, method, option, and default in this guide comes directly from the Fiber source code.

> [!NOTE]
> Fiber v3 is a major version with a different import path and API from v2. Code written for `github.com/gofiber/fiber/v2` will not compile against v3 unchanged. The most visible change is the handler signature — v3 uses `func(c fiber.Ctx) error` (a value, `fiber.Ctx`, not a pointer `*fiber.Ctx`). See [Migrating from v2](#migrating-from-v2).

---

## 1. Quick Start

Here is a complete Fiber program. It creates an app, defines one route, and serves it on port 3000.

```go
package main

import (
    "log"

    "github.com/gofiber/fiber/v3"
)

func main() {
    // Initialize a new Fiber app
    app := fiber.New()

    // Define a route for GET on the root path "/"
    app.Get("/", func(c fiber.Ctx) error {
        return c.SendString("Hello, World 👋!")
    })

    // Start the server on port 3000
    log.Fatal(app.Listen(":3000"))
}
```

Save it as `main.go`, then run:

```bash
go run main.go
```

Expected output:

```text

 ┌───────────────────────────────────────────────────┐
 │                   Fiber v3.x.x                     │
 │               http://127.0.0.1:3000               │
 │                                                   │
 │ Handlers ............. 2  Processes ........... 1 │
 │ Prefork ....... Disabled  PID ............. 12345 │
 └───────────────────────────────────────────────────┘

```

Visit `http://localhost:3000` in your browser, or:

```bash
curl http://localhost:3000
```

```text
Hello, World 👋!
```

> [!TIP]
> Every Fiber handler has the signature `func(c fiber.Ctx) error`. You read the request and write the response through `c`, and you **return** an error instead of writing to a separate `http.ResponseWriter`. Returning a non-nil error hands control to Fiber's [error handler](#10-error-handling).

---

## 2. Installation

Fiber requires **Go version 1.25 or higher**. Check your version with `go version`; if you need to install or upgrade, visit the [official Go download page](https://go.dev/dl/).

> [!IMPORTANT]
> Go 1.25+ is a hard requirement for Fiber v3. Fiber uses the `unsafe` package for zero-allocation optimizations and is tested against Go 1.25 and newer. Older Go toolchains will fail to build.

=== "New project"

    ```bash
    # Create and enter a project directory
    mkdir myapp && cd myapp

    # Initialize a Go module
    go mod init github.com/your/repo

    # Add Fiber
    go get -u github.com/gofiber/fiber/v3
    ```

=== "Existing module"

    ```bash
    go get -u github.com/gofiber/fiber/v3
    ```

The `go get` command fetches Fiber and records it in your `go.mod` and `go.sum`. Import it as `github.com/gofiber/fiber/v3` and you are ready to build.

> [!NOTE]
> The `/v3` suffix is part of the import path, not a folder you create. This is standard Go module versioning. In your code you still refer to the package as `fiber` (`fiber.New()`, `fiber.Ctx`, …).

---

## 3. Routing

Routing maps an HTTP method and URL path to a handler. Fiber gives you one method per HTTP verb on the app (and on groups and sub-routers).

### Basic routes

```go
app.Get("/",          handler)  // GET
app.Post("/users",    handler)  // POST
app.Put("/users/:id", handler)  // PUT
app.Patch("/users/:id", handler) // PATCH
app.Delete("/users/:id", handler) // DELETE
app.Head("/health",   handler)  // HEAD
app.Options("/users", handler)  // OPTIONS
```

You can also register a handler for **all** methods, or for a custom set:

```go
app.All("/any", handler)                 // every HTTP method
app.Add([]string{"GET", "POST"}, "/x", handler) // a specific set
```

> [!NOTE]
> By default Fiber automatically registers a `HEAD` route for every `GET` route, so `curl -I` works without extra code. You can disable this with the `DisableHeadAutoRegister` config option if you want to supply custom `HEAD` handlers.

### Route parameters

Capture parts of the path with `:name`. Read them with `c.Params("name")`.

```go
// GET /user/fenny
app.Get("/user/:name", func(c fiber.Ctx) error {
    return c.SendString("Hello, " + c.Params("name")) // "Hello, fenny"
})
```

Fiber supports several parameter styles:

| Pattern              | Matches                  | Example route             | Access                          |
| -------------------- | ------------------------ | ------------------------- | ------------------------------- |
| `:name`              | A single required segment | `/user/:name`             | `c.Params("name")`              |
| `:name?`             | An optional segment       | `/user/:name?`            | `c.Params("name")` (may be `""`) |
| `*`                  | A wildcard (greedy)       | `/api/*`                  | `c.Params("*")` or `c.Params("*1")` |
| `+`                  | A wildcard (one or more)  | `/files/+`                | `c.Params("+")`                 |
| `:from-:to`          | Multiple params in a segment | `/flights/:from-:to`   | `c.Params("from")`, `c.Params("to")` |
| `:file.:ext`         | Params split on `.`       | `/:file.:ext`             | `c.Params("file")`, `c.Params("ext")` |

```go
// GET /flights/LAX-SFO
app.Get("/flights/:from-:to", func(c fiber.Ctx) error {
    return c.SendString(c.Params("from") + " → " + c.Params("to")) // "LAX → SFO"
})

// GET /api/anything/here  →  c.Params("*") == "anything/here"
app.Get("/api/*", func(c fiber.Ctx) error {
    return c.SendString(c.Params("*"))
})
```

For multiple wildcards, address them by index: `c.Params("*1")`, `c.Params("*2")`.

> [!TIP]
> Use the generic helper `fiber.Params[T](c, key)` to parse a parameter directly into a typed value: `fiber.Params[int](c, "id")` returns an `int` (and `0` if the segment is missing or unparseable). Supported types: all int/uint sizes, `float32/64`, `bool`, `string`, and `[]byte`.

### Route constraints

Constrain a parameter so the route only matches when the value has the right shape. Append the constraint in angle brackets after the parameter name.

```go
// Only matches when :id is an integer
app.Get("/user/:id<int>", handler)

// Only matches a minimum length of 5
app.Get("/category/:name<minLen(5)>", handler)
```

Built-in constraints include `int`, `bool`, `float`, `alpha`, `guid`, `minLen(n)`, `maxLen(n)`, `len(n)`, `min(n)`, `max(n)`, `range(min,max)`, `datetime(format)`, and `regex(...)`. If a request does not satisfy the constraint, Fiber treats the route as not matched and continues searching.

### Naming routes and generating URLs

Give a route a name with `.Name()`, then build a URL to it later with `c.GetRouteURL()`:

```go
app.Get("/user/:id", handler).Name("user.show")

app.Get("/link", func(c fiber.Ctx) error {
    url, _ := c.GetRouteURL("user.show", fiber.Map{"id": 42})
    return c.SendString(url) // "/user/42"
})
```

`fiber.Map` is a convenience alias for `map[string]any`, used throughout Fiber for JSON bodies, view data, and route parameters.

---

## 4. The Context (`fiber.Ctx`)

The context, `fiber.Ctx`, is the single object you use inside every handler. It holds the incoming request and the outgoing response, and exposes methods for reading request data and writing the response.

> [!CAUTION]
> **Fiber reuses the context across requests for performance ("zero allocation").** Values returned by `Ctx` methods — `c.Params()`, `c.Query()`, `c.Body()`, `c.Get()`, and so on — are only valid **inside the handler**. Do not store these values or pass `c` itself to a goroutine that outlives the handler. If you need a value after the handler returns, copy it (e.g. `id := strings.Clone(c.Params("id"))`), or enable the `Immutable` config option. To carry a cancellation/deadline into a goroutine, use `c.Context()` — never `c` directly. See [How It Works](#15-how-it-works).

### Reading the request

| Method                       | Returns                                              |
| ---------------------------- | ---------------------------------------------------- |
| `c.Method()`                 | HTTP method, e.g. `"GET"`                             |
| `c.Path()`                   | URL path, e.g. `"/users"`                             |
| `c.OriginalURL()`            | Full original URL with query string                  |
| `c.BaseURL()`                | Protocol + host, e.g. `"https://example.com"`         |
| `c.Params("id")`             | A route parameter                                    |
| `c.Query("q")`               | A query-string value (`?q=...`)                       |
| `c.Queries()`                | All query parameters as `map[string]string`          |
| `c.Get("X-Header")`          | A request header (case-insensitive)                  |
| `c.Cookies("name")`          | A request cookie value                               |
| `c.Body()` / `c.BodyRaw()`   | The request body bytes (`Body()` auto-decompresses)  |
| `c.FormValue("name")`        | A form field value                                   |
| `c.FormFile("file")`         | An uploaded file (`*multipart.FileHeader`)           |
| `c.IP()` / `c.IPs()`         | Client IP / the `X-Forwarded-For` chain              |
| `c.Hostname()` / `c.Host()`  | Hostname / host (with port)                          |
| `c.Secure()`                 | `true` over TLS                                      |
| `c.Accepts("json")`          | Content negotiation against the `Accept` header      |

```go
app.Get("/search", func(c fiber.Ctx) error {
    term := c.Query("q")              // ?q=fiber  → "fiber"
    page := fiber.Query[int](c, "page", 1) // typed, with default 1
    return c.SendString(term + " page " + strconv.Itoa(page))
})
```

> [!TIP]
> Like `Params`, both `Query` and `Locals` have generic forms: `fiber.Query[int](c, "page")`, `fiber.Locals[string](c, "user")`. They parse and type-assert in one call and fall back to the zero value (or your supplied default) when the key is missing.

### Writing the response

| Method                          | Effect                                                        |
| ------------------------------- | ------------------------------------------------------------ |
| `c.SendString("hi")`            | Write a plain string body                                    |
| `c.Send([]byte{...})`           | Write raw bytes                                              |
| `c.JSON(data)`                  | Serialize to JSON, set `Content-Type: application/json`       |
| `c.XML(data)`                   | Serialize to XML                                             |
| `c.CBOR(data)` / `c.MsgPack(data)` | Serialize to CBOR / MessagePack (binary)                  |
| `c.SendStatus(code)`            | Set status code (and matching status text if body is empty)  |
| `c.Status(code)`                | Set status code (chainable — returns `c`)                    |
| `c.Set("X-Header", "v")`        | Set a response header                                        |
| `c.Type("json")`                | Set `Content-Type` from a file extension (chainable)         |
| `c.SendFile("./path")`          | Send a file with the right content type                      |
| `c.Download("./report.pdf")`    | Send a file as a download attachment                         |
| `c.Cookie(&fiber.Cookie{...})`  | Set a response cookie                                        |
| `c.Redirect().To("/other")`     | Issue an HTTP redirect                                       |
| `c.Render("view", data)`        | Render an HTML template                                      |

`Status` is chainable, so the idiomatic one-liner is:

```go
app.Get("/teapot", func(c fiber.Ctx) error {
    return c.Status(fiber.StatusTeapot).SendString("🍵 short and stout")
})
```

#### Returning JSON

```go
type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

app.Get("/user", func(c fiber.Ctx) error {
    return c.JSON(User{Name: "John", Age: 20})
    // => {"name":"John","age":20}
})

app.Get("/map", func(c fiber.Ctx) error {
    return c.JSON(fiber.Map{"success": true, "message": "Hi John!"})
})
```

You can pass a custom content type as a second argument, useful for RFC 7807 problem documents:

```go
return c.JSON(fiber.Map{
    "type":   "https://example.com/probs/out-of-credit",
    "title":  "You do not have enough credit.",
    "status": 403,
}, "application/problem+json")
```

#### Content negotiation

`c.AutoFormat(data)` picks a representation based on the request's `Accept` header — `text/plain`, `text/html`, `application/json`, `application/xml`, MsgPack, or CBOR — automatically:

```go
app.Get("/auto", func(c fiber.Ctx) error {
    return c.AutoFormat(User{Name: "John"})
    // Accept: application/json → {"name":"John"}
    // Accept: text/html        → <p>{John}</p>
})
```

For full control over which formats you offer, use `c.Format(...)` with explicit `fiber.ResFmt` handlers.

#### Setting cookies

```go
app.Get("/login", func(c fiber.Ctx) error {
    c.Cookie(&fiber.Cookie{
        Name:     "session",
        Value:    "randomvalue",
        Expires:  time.Now().Add(24 * time.Hour),
        HTTPOnly: true,
        Secure:   true,
        SameSite: "Lax",
    })
    return c.SendStatus(fiber.StatusOK)
})
```

> [!IMPORTANT]
> When you set `SameSite: "None"`, Fiber automatically forces `Secure: true`, as required by modern browsers (RFC 6265bis). A `SameSite=None` cookie without `Secure` is rejected by browsers, so Fiber corrects this for you.

To delete a cookie, set it again with the same attributes and `Expires: fasthttp.CookieExpireDelete`, or call `c.ClearCookie("name")`. Note that browsers only clear a cookie when the path/domain attributes match the original, so for path- or domain-scoped cookies you must re-issue with the same `Path`/`Domain`.

---

## 5. Reading & Binding Request Data

For anything beyond a single value, use Fiber's **binder** to decode the request directly into a Go struct. Access it with `c.Bind()`.

### Binding the body

`c.Bind().Body(&v)` inspects the request's `Content-Type` and decodes accordingly. Tag your struct fields for each format you want to accept:

```go
type Person struct {
    Name string `json:"name" xml:"name" form:"name" msgpack:"name"`
    Pass string `json:"pass" xml:"pass" form:"pass" msgpack:"pass"`
}

app.Post("/", func(c fiber.Ctx) error {
    p := new(Person)
    if err := c.Bind().Body(p); err != nil {
        return err
    }
    return c.JSON(p)
})
```

The content type determines which tag Fiber reads:

| `Content-Type`                       | Struct tag |
| ------------------------------------ | ---------- |
| `application/json`                   | `json`     |
| `application/xml`, `text/xml`        | `xml`      |
| `application/x-www-form-urlencoded`  | `form`     |
| `multipart/form-data`                | `form`     |
| `application/vnd.msgpack`            | `msgpack`  |

```bash
curl -X POST -H "Content-Type: application/json" \
  --data '{"name":"john","pass":"doe"}' localhost:3000
```

### Binding from other sources

Each request source has its own binder method and struct tag:

| Method                    | Source            | Tag          |
| ------------------------- | ----------------- | ------------ |
| `c.Bind().URI(&v)`        | Path parameters   | `uri`        |
| `c.Bind().Query(&v)`      | Query string      | `query`      |
| `c.Bind().Header(&v)`     | Request headers   | `header`     |
| `c.Bind().Cookie(&v)`     | Cookies           | `cookie`     |
| `c.Bind().RespHeader(&v)` | Response headers  | `respHeader` |
| `c.Bind().All(&v)`        | All of the above  | (mixed tags) |

`All` merges every source into one struct, with this precedence (later sources do **not** override earlier ones): **URI → Body → Query → Headers → Cookies**.

```go
type Req struct {
    ID    int    `uri:"id"`
    Name  string `query:"name" json:"name"`
    Role  string `header:"X-User-Role"`
}

app.Post("/users/:id", func(c fiber.Ctx) error {
    r := new(Req)
    if err := c.Bind().All(r); err != nil {
        return err
    }
    return c.JSON(r)
})
```

### Array query parameters

Fiber understands several array encodings in the query string:

| Format                   | Example                          | Needs `EnableSplittingOnParsers`? |
| ------------------------ | -------------------------------- | --------------------------------- |
| Repeated key             | `?colors=red&colors=blue`        | No                                |
| Bracket notation         | `?colors[]=red&colors[]=blue`    | No                                |
| Comma-separated          | `?colors=red,blue`               | **Yes**                           |
| Indexed bracket (nested) | `?posts[0][title]=Hello`         | No                                |

> [!NOTE]
> Comma-splitting is **off by default**. Without `EnableSplittingOnParsers: true` in your config, `?colors=red,blue` binds as a single element `["red,blue"]`, not two. Prefer repeated keys (`?colors=red&colors=blue`) if you do not want to change the global config.

### Default values

Set field defaults inline with the `default:` tag option:

```go
type Filter struct {
    Name     string   `query:"name,default:john"`
    Products []string `query:"products,default:shoe|hat"` // slice items joined by |
}
```

### Validation

Fiber does not ship a validator, but it integrates with any validator that implements the `StructValidator` interface. The popular choice is [`go-playground/validator`](https://github.com/go-playground/validator):

```go
import "github.com/go-playground/validator/v10"

type structValidator struct{ validate *validator.Validate }

func (v *structValidator) Validate(out any) error {
    return v.validate.Struct(out)
}

app := fiber.New(fiber.Config{
    StructValidator: &structValidator{validate: validator.New()},
})

type Person struct {
    Name string `json:"name" validate:"required"`
    Age  int    `json:"age"  validate:"gte=18,lte=60"`
}

app.Post("/", func(c fiber.Ctx) error {
    p := new(Person)
    if err := c.Bind().JSON(p); err != nil { // runs validation too
        return err // validation failures surface here
    }
    return c.JSON(p)
})
```

When a validator is configured, binding runs it automatically for struct destinations. Call `c.Bind().SkipValidation(true)` to bypass it for a single bind.

> [!TIP]
> By default the binder returns parse errors to you (`*fiber.BindError`, which carries the `Source` — `"uri"`, `"query"`, `"body"`, … — so you can return RFC-correct status codes). Call `c.Bind().WithAutoHandling()` to instead have Fiber convert any parse failure into a `400 Bad Request` automatically.

---

## 6. Middleware

Middleware are handlers that run before (and optionally after) your route handler. Register them with `app.Use(...)`. A middleware typically does some work, then calls `c.Next()` to pass control down the chain.

```go
app.Use(func(c fiber.Ctx) error {
    fmt.Println("Request:", c.Method(), c.Path())
    return c.Next() // continue to the next handler
})
```

Scope middleware to a path prefix by passing it first:

```go
app.Use("/api", authMiddleware) // only runs for paths under /api
```

### Built-in middleware

Fiber ships a comprehensive set of middleware in `github.com/gofiber/fiber/v3/middleware/*`. Import the one you need and mount its `New()` constructor. Each accepts an optional config struct.

| Middleware          | Import path suffix        | What it does                                                                 |
| ------------------- | ------------------------- | --------------------------------------------------------------------------- |
| `logger`            | `middleware/logger`       | Logs each request/response                                                   |
| `recover`           | `middleware/recover`      | Recovers from panics and routes them to the error handler                    |
| `cors`              | `middleware/cors`         | Cross-Origin Resource Sharing headers                                        |
| `csrf`              | `middleware/csrf`         | CSRF token protection for unsafe requests                                    |
| `basicauth`         | `middleware/basicauth`    | HTTP Basic authentication                                                    |
| `keyauth`           | `middleware/keyauth`      | API-key authentication                                                       |
| `limiter`           | `middleware/limiter`      | Rate limiting                                                                |
| `session`           | `middleware/session`      | Server-side sessions                                                         |
| `compress`          | `middleware/compress`     | gzip / brotli / zstd / deflate response compression                          |
| `cache`             | `middleware/cache`        | Caches responses                                                            |
| `etag`              | `middleware/etag`         | Adds `ETag` headers for conditional requests                                |
| `helmet`            | `middleware/helmet`       | Sets common security headers                                                |
| `encryptcookie`     | `middleware/encryptcookie`| Encrypts cookie values                                                      |
| `idempotency`       | `middleware/idempotency`  | Makes unsafe requests idempotent via a key                                  |
| `requestid`         | `middleware/requestid`    | Attaches a unique ID to each request                                        |
| `healthcheck`       | `middleware/healthcheck`  | Liveness / readiness probe endpoints                                        |
| `static`            | `middleware/static`       | Serves static files and directories                                        |
| `proxy`             | `middleware/proxy`        | Reverse-proxies requests to upstreams                                       |
| `redirect`          | `middleware/redirect`     | Path-based redirects                                                        |
| `rewrite`           | `middleware/rewrite`      | Rewrites the URL path by rules                                              |
| `timeout`           | `middleware/timeout`      | Enforces a per-request time limit                                          |
| `earlydata`         | `middleware/earlydata`    | Supports TLS 1.3 0-RTT early data                                           |
| `favicon`           | `middleware/favicon`      | Serves / ignores `/favicon.ico`                                             |
| `envvar`            | `middleware/envvar`       | Exposes selected environment variables as JSON                             |
| `expvar`            | `middleware/expvar`       | Exposes runtime metrics in `expvar` format                                  |
| `pprof`             | `middleware/pprof`        | Serves Go `pprof` profiling endpoints                                       |
| `paginate`          | `middleware/paginate`     | Parses page/offset/cursor pagination from the query string                  |
| `responsetime`      | `middleware/responsetime` | Writes request duration to a response header                                |
| `hostauthorization` | `middleware/hostauthorization` | Allowlists the `Host` header (anti DNS-rebinding)                       |
| `adaptor`           | `middleware/adaptor`      | Converts handlers to/from `net/http`                                        |
| `skip`              | `middleware/skip`         | Skips a wrapped handler when a predicate is true                            |
| `sse`               | `middleware/sse`          | Server-Sent Events helpers                                                  |

### Common middleware examples

**Logging and panic recovery** — the two you will almost always want:

```go
import (
    "github.com/gofiber/fiber/v3/middleware/logger"
    "github.com/gofiber/fiber/v3/middleware/recover"
)

app.Use(recover.New()) // catch panics
app.Use(logger.New())  // log requests
```

**CORS** — allow browser requests from other origins:

```go
import "github.com/gofiber/fiber/v3/middleware/cors"

app.Use(cors.New(cors.Config{
    AllowOrigins: []string{"https://example.com"},
    AllowMethods: []string{"GET", "POST"},
}))
```

> [!WARNING]
> With default config, `cors.New()` sets `AllowOrigins` to `["*"]` — it allows **every** origin. That is convenient for local development but is rarely what you want in production. Set an explicit `AllowOrigins` list. Also note: you **cannot** combine the `"*"` wildcard with `AllowCredentials: true`; Fiber rejects that combination because it is a security risk.

**Rate limiting** — cap requests per client:

```go
import "github.com/gofiber/fiber/v3/middleware/limiter"

app.Use(limiter.New(limiter.Config{
    Max:        20,               // 20 requests …
    Expiration: 30 * time.Second, // … per 30 seconds, per IP
}))
```

Defaults are `Max: 5` over `Expiration: 1 minute`, keyed by `c.IP()`. When the limit is hit, the client gets `429 Too Many Requests` plus `X-RateLimit-*` and `Retry-After` headers.

**Basic auth**:

```go
import "github.com/gofiber/fiber/v3/middleware/basicauth"

app.Use(basicauth.New(basicauth.Config{
    Users: map[string]string{"admin": "secret"},
}))
```

**Sessions**:

```go
import "github.com/gofiber/fiber/v3/middleware/session"

store := session.NewStore()

app.Get("/", func(c fiber.Ctx) error {
    sess, err := store.Get(c)
    if err != nil {
        return err
    }
    count, _ := sess.Get("count").(int)
    count++
    sess.Set("count", count)
    if err := sess.Save(); err != nil {
        return err
    }
    return c.SendString(fmt.Sprintf("visit #%d", count))
})
```

> [!CAUTION]
> The default session and CSRF cookies are **not** marked `Secure` or `HTTPOnly`. For any production deployment over HTTPS, set `CookieSecure: true` and `CookieHTTPOnly: true` in the respective middleware config. See the [Admin Guide](/gofiber/fiber/admin-guide/) security section for the hardened configuration.

---

## 7. Static Files

Serve a directory of assets with the `static` middleware. Register it on a wildcard route:

```go
import "github.com/gofiber/fiber/v3/middleware/static"

// Serve ./public at the site root
app.Get("/*", static.New("./public"))
// => /js/script.js, /css/style.css

// Serve under a prefix
app.Get("/assets*", static.New("./public"))

// Serve a single file (e.g. an SPA fallback) for any unmatched path
app.Get("*", static.New("./public/index.html"))
```

For one-off files inside a handler, use `c.SendFile()`:

```go
app.Get("/logo", func(c fiber.Ctx) error {
    return c.SendFile("./public/logo.png")
})
```

`SendFile` accepts an optional `fiber.SendFile` config for byte-range requests, compression, download mode, and cache duration:

```go
return c.SendFile("style.css", fiber.SendFile{
    Compress:  true,
    ByteRange: true,
})
```

---

## 8. Grouping Routes & Sub-apps

### Groups

Group routes that share a prefix and/or middleware with `app.Group()`:

```go
api := app.Group("/api", authMiddleware) // /api, with auth

v1 := api.Group("/v1")                   // /api/v1
v1.Get("/users", listUsers)              // GET /api/v1/users

v2 := api.Group("/v2")                   // /api/v2
v2.Get("/users", listUsersV2)
```

`app.Route()` does the same with a callback, and supports a name prefix:

```go
app.Route("/test", func(r fiber.Router) {
    r.Get("/foo", handler).Name("foo") // /test/foo (name: test.foo)
    r.Get("/bar", handler).Name("bar") // /test/bar (name: test.bar)
}, "test.")
```

### Chained routes

`app.RouteChain()` declares several verbs on one path, fluently:

```go
app.RouteChain("/events").
    All(func(c fiber.Ctx) error { return c.Next() }). // route-specific middleware
    Get(listEvents).
    Post(createEvent)
```

### Mounting a sub-app

You can mount one Fiber app inside another with `app.Use()`:

```go
app := fiber.New()
micro := fiber.New()

app.Use("/admin", micro) // requests to /admin/* go to micro

micro.Get("/dashboard", func(c fiber.Ctx) error {
    return c.SendString("Admin Dashboard")
})
```

> [!NOTE]
> Unlike Express, Fiber does **not** strip the mount prefix. Inside `micro`, `c.Path()` still returns the full request path (`/admin/dashboard`, not `/dashboard`). There is no `req.baseUrl` equivalent. Register a sub-app's routes **before** mounting it.

### Domain routing

Scope routes to a hostname with `app.Domain()`:

```go
app.Domain("api.example.com").Get("/users", handler)

// With a parameter
app.Domain(":tenant.example.com").Get("/", func(c fiber.Ctx) error {
    return c.SendString(fiber.DomainParam(c, "tenant") + "'s site")
})
```

---

## 9. Templates & Views

Fiber renders HTML through a view engine. With no engine configured it uses Go's standard `html/template`. To use another engine (Pug, Handlebars, Mustache, Amber, …), install one from the [gofiber/template](https://github.com/gofiber/template) collection and set it in the config:

```go
import "github.com/gofiber/template/html/v2"

engine := html.New("./views", ".html")

app := fiber.New(fiber.Config{Views: engine})

app.Get("/", func(c fiber.Ctx) error {
    return c.Render("index", fiber.Map{
        "Title": "Homepage",
        "Year":  2026,
    })
})
```

`c.Render(name, data, layouts...)` produces a `text/html` response. Use `c.ViewBind()` in middleware to add variables shared across all renders, and set `ViewsLayout` in the config for a global layout.

> [!TIP]
> During development, call `app.ReloadViews()` (e.g. from a debug-only route or file watcher) to pick up template changes without restarting the server.

---

## 10. Error Handling

Fiber centralizes error handling. Your handlers and middleware simply **return** an error; Fiber routes it to a single error handler that writes the response.

```go
app.Get("/", func(c fiber.Ctx) error {
    return c.SendFile("file-does-not-exist") // the error flows to the error handler
})
```

### Errors with status codes

Use `fiber.NewError(code, message)` to attach an HTTP status to an error. Fiber also predefines an error for every standard status (`fiber.ErrNotFound`, `fiber.ErrServiceUnavailable`, …):

```go
app.Get("/missing", func(c fiber.Ctx) error {
    return fiber.NewError(fiber.StatusNotFound, "user not found")
})

app.Get("/down", func(c fiber.Ctx) error {
    return fiber.ErrServiceUnavailable // 503 with standard text
})
```

### The default error handler

By default, a generic error becomes `500 Internal Server Error`; a `*fiber.Error` uses its embedded code and message, written as `text/plain`.

### A custom error handler

Set `ErrorHandler` in the config to return tailored responses — for example, JSON errors for an API:

```go
app := fiber.New(fiber.Config{
    ErrorHandler: func(c fiber.Ctx, err error) error {
        code := fiber.StatusInternalServerError
        var e *fiber.Error
        if errors.As(err, &e) {
            code = e.Code
        }
        return c.Status(code).JSON(fiber.Map{"error": err.Error()})
    },
})
```

> [!IMPORTANT]
> Fiber does **not** recover from panics by default — a panic in a handler will crash the worker. Add `app.Use(recover.New())` so panics are caught and forwarded to your error handler instead.

---

## 11. The HTTP Client

Fiber includes a fasthttp-backed HTTP client in `github.com/gofiber/fiber/v3/client` for making outbound requests:

```go
import "github.com/gofiber/fiber/v3/client"

cc := client.New()

resp, err := cc.Get("https://httpbin.org/get")
if err != nil {
    log.Fatal(err)
}
fmt.Println(resp.StatusCode())
fmt.Println(string(resp.Body()))
```

The client supports `Get`/`Post`/`Put`/`Delete`, JSON/XML/form bodies, query parameters, headers, cookies, a cookie jar, retries, and request/response hooks. It shares fasthttp's pooled, zero-allocation design with the server.

---

## 12. Going to Production

### Graceful shutdown

Stop accepting new connections and let in-flight requests finish:

```go
// In a goroutine, wait for a signal, then:
if err := app.ShutdownWithTimeout(10 * time.Second); err != nil {
    log.Printf("shutdown error: %v", err)
}
```

`app.Shutdown()` waits indefinitely; `ShutdownWithTimeout` force-closes after the deadline; `ShutdownWithContext` ties shutdown to a context. You can also pass a `GracefulContext` in the listen config to trigger shutdown automatically.

### TLS / HTTPS

Serve HTTPS by giving `Listen` a certificate and key:

```go
app.Listen(":443", fiber.ListenConfig{
    CertFile:    "./cert.pem",
    CertKeyFile: "./cert.key",
})
```

For automatic Let's Encrypt certificates, set an `AutoCertManager`; for full control, supply a `*tls.Config` via `TLSConfig`. See the Admin Guide for mTLS and ACME details.

### Prefork

`EnablePrefork` spawns one Go process per CPU core, all listening on the same port, for higher throughput on multi-core machines:

```go
app.Listen(":8080", fiber.ListenConfig{EnablePrefork: true})
```

> [!WARNING]
> Prefork processes do **not** share in-process memory. Any state you keep in a package variable (counters, in-memory caches, the default `limiter`/`session` memory store) is per-process and will not be consistent across workers. Use `app.SharedState()` or an external store (Redis, etc.) for shared state under prefork. Use `fiber.IsChild()` to tell parent and child processes apart.

---

## 13. Streaming & Real-Time

**Server-Sent Events** — stream events to the browser:

```go
app.Get("/sse", func(c fiber.Ctx) error {
    c.Set("Content-Type", "text/event-stream")
    c.Set("Cache-Control", "no-cache")
    c.Set("Connection", "keep-alive")

    return c.SendStreamWriter(func(w *bufio.Writer) {
        for i := 0; ; i++ {
            fmt.Fprintf(w, "data: message %d\n\n", i)
            if err := w.Flush(); err != nil {
                return // client disconnected
            }
            time.Sleep(time.Second)
        }
    })
})
```

**WebSockets** are provided by the external [gofiber/contrib/websocket](https://github.com/gofiber/contrib) package; check `c.IsWebSocket()` to detect an upgrade handshake.

**Streaming a large response body** with `c.SendStream(reader, size)` reads and writes asynchronously after the handler returns — pass `c.Context()` (not `c`) to anything that spawns goroutines.

---

## 14. Interoperating with `net/http`

Fiber's router accepts standard-library handlers directly — no wrapper needed:

```go
httpHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("served by net/http"))
})

app.Get("/legacy", httpHandler) // works as-is
```

It also adapts **Express-style** handlers that take `fiber.Req` and `fiber.Res`:

```go
app.Get("/", func(req fiber.Req, res fiber.Res) error {
    return res.SendString("Hello from Express-style handlers!")
})

app.Use(func(req fiber.Req, res fiber.Res, next func() error) error {
    if req.IP() == "192.168.1.254" {
        return res.SendStatus(fiber.StatusForbidden)
    }
    return next()
})
```

To convert entire applications or reuse `net/http` middleware chains in both directions, use the `adaptor` middleware.

> [!NOTE]
> Adapted `net/http` handlers run with standard-library semantics — they do not get `fiber.Ctx` features and pay a small compatibility overhead. Native `func(c fiber.Ctx) error` handlers are the fastest path.

---

## 15. How It Works

A few concepts explain Fiber's behavior and its performance rules.

**Built on fasthttp.** Fiber is a layer over [fasthttp](https://github.com/valyala/fasthttp), not Go's `net/http`. fasthttp avoids per-request allocations by **reusing** request, response, and context objects from pools. Fiber exposes the underlying objects when you need them: `c.RequestCtx()`, `c.Request()`, `c.Response()`, `app.Server()`, and `app.Handler()`.

**Context reuse is why the "valid only in the handler" rule exists.** Because the `Ctx` (and the fasthttp `RequestCtx` it wraps) is recycled for the next request, any slice or string it hands you may be overwritten once your handler returns. Copy values you need to keep, or enable `Immutable`.

**`fiber.Ctx` and `context.Context`.** `Ctx` implements `context.Context`, but `Deadline()`, `Done()`, and `Err()` are no-ops due to a fasthttp limitation. To propagate cancellation into goroutines, call `c.Context()` to get a real `context.Context` (set one first with `c.SetContext()` if you need values/deadlines in it).

**Routing.** Routes are compiled into a tree at registration time. Adding or removing routes at runtime is possible (`app.RebuildTree()`, `app.RemoveRoute()`) but is **not** thread-safe and is meant for development only.

For the full architecture, configuration reference, and internals, see the [Fiber Admin Guide](/gofiber/fiber/admin-guide/).

---

## 16. Migrating from v2

If you are porting a Fiber v2 app, these are the changes you will hit first:

- **Import path:** `github.com/gofiber/fiber/v2` → `github.com/gofiber/fiber/v3`.
- **Handler signature:** the context is now a value type. `func(c *fiber.Ctx) error` → `func(c fiber.Ctx) error`.
- **Go version:** v3 requires Go **1.25+**.
- **Binding:** the old `c.BodyParser(&v)`, `c.QueryParser(&v)`, etc. are replaced by the unified `c.Bind()` API — `c.Bind().Body(&v)`, `c.Bind().Query(&v)`, `c.Bind().All(&v)`.

> [!CAUTION]
> There is a community-reported migration pitfall: an automated v2→v3 conversion that rewrites `c.BodyParser(c)` into `c.Bind().Body(c)` — passing the **context** instead of a destination struct — can cause an out-of-memory loop. After any automated migration, verify that every `Bind()` call passes a pointer to your target struct, not `c`.

---

## 17. Troubleshooting

### Error: `package github.com/gofiber/fiber/v3 is not in std`

**Cause:** Fiber is not yet downloaded, or your `go.mod` does not require it.
**Solution:** Run `go get -u github.com/gofiber/fiber/v3` inside your module, then `go mod tidy`.
**Prevention:** Always `go mod init` before adding dependencies.

### Build error: `note: module requires Go 1.25`

**Cause:** Your Go toolchain is older than Fiber v3's minimum.
**Solution:** Upgrade Go to 1.25 or newer from [go.dev/dl](https://go.dev/dl/) and re-run `go build`.
**Prevention:** Check `go version` before starting a v3 project.

### Type error: `cannot use func(c *fiber.Ctx)... as fiber.Handler`

**Cause:** You wrote a v2-style handler with a pointer receiver `*fiber.Ctx`.
**Solution:** Change the signature to take a value: `func(c fiber.Ctx) error`.
**Prevention:** In v3 the context is always passed by value as `fiber.Ctx`.

### My CORS requests still fail in the browser

**Cause:** Either CORS middleware is not mounted before your routes, or you set `AllowCredentials: true` together with the `"*"` origin wildcard (Fiber rejects this combination).
**Solution:** Mount `cors.New(...)` with `app.Use(...)` early, and list explicit origins in `AllowOrigins` when using credentials.
**Prevention:** Never pair credentials with a wildcard origin.

### `c.IP()` returns the proxy's IP, not the client's

**Cause:** Your app is behind a reverse proxy but `TrustProxy` is not enabled, so Fiber returns the TCP remote IP.
**Solution:** Set `TrustProxy: true`, a `TrustProxyConfig` listing your proxy IPs/ranges, and `ProxyHeader: fiber.HeaderXForwardedFor`. Setting `ProxyHeader` alone has no effect.
**Prevention:** Configure proxy trust whenever you deploy behind Nginx, Traefik, a load balancer, or a CDN.

### A value read from `c` is garbled or changes unexpectedly

**Cause:** You stored a value returned by `c.Params()/Query()/Body()/Get()` and used it after the handler returned, or passed `c` to a goroutine. The context was recycled for another request.
**Solution:** Copy the value (e.g. `strings.Clone(c.Params("id"))`) before keeping it, or set `Immutable: true` in the config. For goroutines, pass `c.Context()`, not `c`.
**Prevention:** Treat everything from `c` as valid only inside the handler.

### Panic crashes the whole server

**Cause:** No recover middleware is installed; Fiber does not catch panics by default.
**Solution:** Add `app.Use(recover.New())` as one of your first middleware.
**Prevention:** Always mount `recover` in production apps.

### Rate limiter / session counts reset or are inconsistent

**Cause:** You enabled prefork, and the middleware uses the default in-memory store, which is per-process and not shared across the spawned workers.
**Solution:** Use an external store (e.g. a `gofiber/storage` Redis driver) or `app.SharedState()`, or disable prefork.
**Prevention:** Never rely on in-process memory for shared state under prefork.

### Comma-separated query array binds as one element

**Cause:** `EnableSplittingOnParsers` is off (the default), so `?colors=red,blue` is a single value.
**Solution:** Either enable `EnableSplittingOnParsers: true`, or use repeated keys (`?colors=red&colors=blue`) / bracket notation (`?colors[]=red&colors[]=blue`).
**Prevention:** Prefer repeated keys, which work regardless of config.

---

## 18. FAQ

**Q: Is Fiber based on `net/http`?**
No. Fiber is built on fasthttp, a separate high-performance HTTP engine. That is the source of both its speed and its "values are valid only in the handler" rule. You can still interoperate with `net/http` handlers — the router accepts them directly.

**Q: Why is my response data corrupted when I use it in a goroutine?**
Because `fiber.Ctx` is reused across requests. Copy any value you need outside the handler, and pass `c.Context()` (not `c`) into goroutines.

**Q: Which Go version do I need?**
Go 1.25 or higher for Fiber v3.

**Q: How do I read JSON into a struct?**
`c.Bind().Body(&v)` (it auto-detects JSON from `Content-Type`), or `c.Bind().JSON(&v)` explicitly. Tag fields with `json:"..."`.

**Q: How do I validate input?**
Fiber has no built-in validator. Implement the `StructValidator` interface (commonly with `go-playground/validator`) and set it as `StructValidator` in the config; binding then validates automatically.

**Q: How do I serve static files?**
Mount `static.New("./public")` on a wildcard route, e.g. `app.Get("/*", static.New("./public"))`. For a single file in a handler, use `c.SendFile(...)`.

**Q: How do I return a specific status code?**
`return c.SendStatus(code)` for a bare status, or `return c.Status(code).JSON(...)` / `.SendString(...)` to set the status and a body.

**Q: How do I handle errors in one place?**
Return errors from your handlers and set a custom `ErrorHandler` in the config. Use `fiber.NewError(code, msg)` to attach a status code.

**Q: How do I add a template engine?**
Install one from [gofiber/template](https://github.com/gofiber/template), set it as `Views` in the config, and call `c.Render(name, data)`.

**Q: Does Fiber support WebSockets?**
Yes, via the external `gofiber/contrib/websocket` package. Detect upgrades with `c.IsWebSocket()`.

**Q: How do I run multiple processes for more throughput?**
Enable prefork: `app.Listen(":8080", fiber.ListenConfig{EnablePrefork: true})`. Remember that in-process state is not shared between workers.

**Q: Where is the full configuration reference?**
The complete `fiber.Config` and `ListenConfig` field reference, plus deployment, security hardening, and internals, is in the [Fiber Admin Guide](/gofiber/fiber/admin-guide/).

---

_Generated June 2026 from gofiber/fiber at dec796e. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
