Fiber is an Express-inspired web framework for Go, built on top of 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/v2will not compile against v3 unchanged. The most visible change is the handler signature — v3 usesfunc(c fiber.Ctx) error(a value,fiber.Ctx, not a pointer*fiber.Ctx). See 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.
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:
go run main.go
Expected output:
┌───────────────────────────────────────────────────┐
│ 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:
curl http://localhost:3000
Hello, World 👋!
Tip
Every Fiber handler has the signature
func(c fiber.Ctx) error. You read the request and write the response throughc, and you return an error instead of writing to a separatehttp.ResponseWriter. Returning a non-nil error hands control to Fiber's error handler.
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.
Important
Go 1.25+ is a hard requirement for Fiber v3. Fiber uses the
unsafepackage for zero-allocation optimizations and is tested against Go 1.25 and newer. Older Go toolchains will fail to build.
New project
# 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
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
/v3suffix 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 asfiber(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
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:
app.All("/any", handler) // every HTTP method
app.Add([]string{"GET", "POST"}, "/x", handler) // a specific set
Note
By default Fiber automatically registers a
HEADroute for everyGETroute, socurl -Iworks without extra code. You can disable this with theDisableHeadAutoRegisterconfig option if you want to supply customHEADhandlers.
Route parameters
Capture parts of the path with :name. Read them with c.Params("name").
// 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") |
// 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 anint(and0if 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.
// 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():
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
Ctxmethods —c.Params(),c.Query(),c.Body(),c.Get(), and so on — are only valid inside the handler. Do not store these values or passcitself 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 theImmutableconfig option. To carry a cancellation/deadline into a goroutine, usec.Context()— nevercdirectly. See 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 |
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, bothQueryandLocalshave 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:
app.Get("/teapot", func(c fiber.Ctx) error {
return c.Status(fiber.StatusTeapot).SendString("🍵 short and stout")
})
Returning JSON
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:
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:
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
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 forcesSecure: true, as required by modern browsers (RFC 6265bis). ASameSite=Nonecookie withoutSecureis 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:
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 |
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.
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: truein your config,?colors=red,bluebinds 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:
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:
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 theSource—"uri","query","body", … — so you can return RFC-correct status codes). Callc.Bind().WithAutoHandling()to instead have Fiber convert any parse failure into a400 Bad Requestautomatically.
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.
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:
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:
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:
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()setsAllowOriginsto["*"]— it allows every origin. That is convenient for local development but is rarely what you want in production. Set an explicitAllowOriginslist. Also note: you cannot combine the"*"wildcard withAllowCredentials: true; Fiber rejects that combination because it is a security risk.
Rate limiting — cap requests per client:
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:
import "github.com/gofiber/fiber/v3/middleware/basicauth"
app.Use(basicauth.New(basicauth.Config{
Users: map[string]string{"admin": "secret"},
}))
Sessions:
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
SecureorHTTPOnly. For any production deployment over HTTPS, setCookieSecure: trueandCookieHTTPOnly: truein the respective middleware config. See the 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:
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():
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:
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():
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:
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:
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():
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 noreq.baseUrlequivalent. Register a sub-app's routes before mounting it.
Domain routing
Scope routes to a hostname with app.Domain():
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 collection and set it in the config:
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.
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, …):
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:
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:
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:
// 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:
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:
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/sessionmemory store) is per-process and will not be consistent across workers. Useapp.SharedState()or an external store (Redis, etc.) for shared state under prefork. Usefiber.IsChild()to tell parent and child processes apart.
13. Streaming & Real-Time
Server-Sent Events — stream events to the browser:
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 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:
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:
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/httphandlers run with standard-library semantics — they do not getfiber.Ctxfeatures and pay a small compatibility overhead. Nativefunc(c fiber.Ctx) errorhandlers 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, 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.
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 unifiedc.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)intoc.Bind().Body(c)— passing the context instead of a destination struct — can cause an out-of-memory loop. After any automated migration, verify that everyBind()call passes a pointer to your target struct, notc.
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 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, 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.