# Fiber > Fiber is an Express-inspired web framework written in Go. This file contains all documentation content in a single document following the llmstxt.org standard. ## 👋 Welcome ![Fiber](https://raw.githubusercontent.com/gofiber/contrib/master/.github/logo-dark.svg#gh-dark-mode-only) ![Fiber](https://raw.githubusercontent.com/gofiber/contrib/master/.github/logo.svg#gh-light-mode-only) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Linter](https://github.com/gofiber/contrib/actions/workflows/lint.yml/badge.svg) Repository for third party middlewares and service implementations, with dependencies. > **Go version support:** We only support the latest two versions of Go. Visit [https://go.dev/doc/devel/release](https://go.dev/doc/devel/release) for more information. ### 📑 Middleware Implementations * [casbin](./casbin/README.md) ![casbin workflow status](https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-casbin.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) * [circuitbreaker](./circuitbreaker/README.md) ![circuitbreaker workflow status](https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-circuitbreaker.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) * [coraza](./coraza/README.md) ![coraza workflow status](https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-coraza.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) * [fgprof](./fgprof/README.md) ![fgprof workflow status](https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-fgprof.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) * [i18n](./i18n/README.md) ![i18n workflow status](https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-i18n.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) * [sentry](./sentry/README.md) ![sentry workflow status](https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-sentry.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) * [zap](./zap/README.md) ![zap workflow status](https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-zap.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) * [zerolog](./zerolog/README.md) ![zerolog workflow status](https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-zerolog.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) * [hcaptcha](./hcaptcha/README.md) ![hcaptcha workflow status](https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-hcaptcha.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) * [jwt](./jwt/README.md) ![jwt workflow status](https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-jwt.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) * [loadshed](./loadshed/README.md) ![loadshed workflow status](https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-loadshed.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) * [new relic](./newrelic/README.md) ![new relic workflow status](https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-newrelic.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) * [monitor](./monitor/README.md) ![monitor workflow status](https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-monitor.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) * [prometheus](./prometheus/README.md) ![prometheus workflow status](https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-prometheus.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) * [uptime](./uptime/README.md) ![uptime workflow status](https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-uptime.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) * [open policy agent](./opa/README.md) ![OPA workflow status](https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-opa.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) * [otel (opentelemetry)](./otel/README.md) ![otel workflow status](https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-otel.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) * [paseto](./paseto/README.md) ![paseto workflow status](https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-paseto.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) * [socket.io](./socketio/README.md) ![socket.io workflow status](https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-socketio.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) * [spnego](./spnego/README.md) ![spnego workflow status](https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-spnego.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) * [swaggo](./swaggo/README.md) ![swaggo workflow status](https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-swaggo.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) _(formerly `gofiber/swagger`)_ * [swaggerui](./swaggerui/README.md) ![swaggerui workflow status](https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-swaggerui.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) _(formerly `gofiber/contrib/swagger`)_ * [websocket](./websocket/README.md) ![websocket workflow status](https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-websocket.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) ### 🥡 Service Implementations * [testcontainers](./testcontainers/README.md) ![testcontainers workflow status](https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-testcontainers.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) --- ## Casbin ![Release](https://img.shields.io/github/v/tag/gofiber/contrib?filter=*casbin*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://github.com/gofiber/contrib/workflows/Test%20casbin/badge.svg) Casbin middleware for Fiber. **Compatible with Fiber v3.** > This middleware targets **Casbin v3**. Casbin v2 is no longer supported here; > if you still need it, pin the previous major > (`github.com/gofiber/contrib/v3/casbin` at its last `v1` tag). Migrating from > Casbin v2 to v3 is mostly an import-path change; see the > [Casbin upgrade guide](https://casbin.org/docs/upgrade). ### Go version support We only support the latest two versions of Go. Visit [https://go.dev/doc/devel/release](https://go.dev/doc/devel/release) for more information. ### Install ```sh go get -u github.com/gofiber/fiber/v3 go get -u github.com/gofiber/contrib/v3/casbin/v2 ``` choose an adapter from [here](https://casbin.org/docs/adapters) ```sh go get -u github.com/casbin/gorm-adapter/v3 ``` ### Signature ```go casbin.New(config ...casbin.Config) *casbin.Middleware ``` ### Config | Property | Type | Description | Default | |:--------------|:--------------------------|:-----------------------------------------|:--------------------------------------------------------------| | ModelFilePath | `string` | Model file path | `"./model.conf"` | | PolicyAdapter | `persist.Adapter` | Database adapter for policies | `./policy.csv` | | Enforcer | `*casbin.Enforcer` | Custom casbin enforcer | `Middleware generated enforcer using ModelFilePath & PolicyAdapter` | | Lookup | `func(fiber.Ctx) string` | Look up for current subject | `""` | | Unauthorized | `func(fiber.Ctx) error` | Response body for unauthorized responses | `Unauthorized` | | Forbidden | `func(fiber.Ctx) error` | Response body for forbidden responses | `Forbidden` | #### Examples - [Gorm Adapter](https://github.com/svcg/-fiber_casbin_demo) - [File Adapter](https://github.com/gofiber/contrib/tree/master/v3/casbin/example) ### CustomPermission ```go package main import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/contrib/v3/casbin/v2" _ "github.com/go-sql-driver/mysql" gormadapter "github.com/casbin/gorm-adapter/v3" ) func main() { app := fiber.New() adapter, _ := gormadapter.NewAdapter("mysql", "root:@tcp(127.0.0.1:3306)/") authz := casbin.New(casbin.Config{ ModelFilePath: "path/to/rbac_model.conf", PolicyAdapter: adapter, Lookup: func(c fiber.Ctx) string { return "" // fetch authenticated user subject }, }) app.Post("/blog", authz.RequiresPermissions([]string{"blog:create"}, casbin.WithValidationRule(casbin.MatchAllRule)), func(c fiber.Ctx) error { // your handler }, ) app.Delete("/blog/:id", authz.RequiresPermissions([]string{"blog:create", "blog:delete"}, casbin.WithValidationRule(casbin.AtLeastOneRule)), func(c fiber.Ctx) error { // your handler }, ) app.Listen(":8080") } ``` ### RoutePermission ```go package main import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/contrib/v3/casbin/v2" _ "github.com/go-sql-driver/mysql" gormadapter "github.com/casbin/gorm-adapter/v3" ) func main() { app := fiber.New() adapter, _ := gormadapter.NewAdapter("mysql", "root:@tcp(127.0.0.1:3306)/") authz := casbin.New(casbin.Config{ ModelFilePath: "path/to/rbac_model.conf", PolicyAdapter: adapter, Lookup: func(c fiber.Ctx) string { return "" // fetch authenticated user subject }, }) // check permission with Method and Path app.Post("/blog", authz.RoutePermission(), func(c fiber.Ctx) error { // your handler }, ) app.Listen(":8080") } ``` ### RoleAuthorization ```go package main import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/contrib/v3/casbin/v2" _ "github.com/go-sql-driver/mysql" gormadapter "github.com/casbin/gorm-adapter/v3" ) func main() { app := fiber.New() adapter, _ := gormadapter.NewAdapter("mysql", "root:@tcp(127.0.0.1:3306)/") authz := casbin.New(casbin.Config{ ModelFilePath: "path/to/rbac_model.conf", PolicyAdapter: adapter, Lookup: func(c fiber.Ctx) string { return "" // fetch authenticated user subject }, }) app.Put("/blog/:id", authz.RequiresRoles([]string{"admin"}), func(c fiber.Ctx) error { // your handler }, ) app.Listen(":8080") } ``` --- ## Circuit Breaker ![Release](https://img.shields.io/github/v/tag/gofiber/contrib?filter=*circuitbreaker*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://github.com/gofiber/contrib/workflows/Test%20CircuitBreaker/badge.svg) A **Circuit Breaker** is a software design pattern used to prevent system failures when a service is experiencing high failures or slow responses. It helps improve system resilience by **stopping requests** to an unhealthy service and **allowing recovery** once it stabilizes. **Compatible with Fiber v3.** ### Go version support We only support the latest two versions of Go. Visit [https://go.dev/doc/devel/release](https://go.dev/doc/devel/release) for more information. ### How It Works 1. **Closed State:** - Requests are allowed to pass normally. - Failures are counted. - If failures exceed a defined **threshold**, the circuit switches to **Open** state. 2. **Open State:** - Requests are **blocked immediately** to prevent overload. - The circuit stays open for a **timeout period** before moving to **Half-Open**. - Recovery is derived from the clock rather than scheduled in the background, so it is applied by the first request or state read that arrives after the timeout has elapsed. A circuit with no traffic keeps reporting `open` until something asks. 3. **Half-Open State:** - Allows a limited number of requests to test service recovery. - `HalfOpenMaxConcurrent` bounds how many of those probes run at once; the rest are refused by `OnHalfOpen`. - If enough requests **succeed** (`SuccessThreshold`), the circuit resets to **Closed**. - If a single request **fails**, the circuit returns to **Open**. ### Benefits of Using a Circuit Breaker ✅ **Prevents cascading failures** in microservices. ✅ **Improves system reliability** by avoiding repeated failed requests. ✅ **Reduces load on struggling services** and allows recovery. ### Install ```bash go get -u github.com/gofiber/fiber/v3 go get -u github.com/gofiber/contrib/v3/circuitbreaker ``` ### Signature ```go // Build a circuit breaker, then mount it in front of the routes it protects. circuitbreaker.New(config circuitbreaker.Config) *circuitbreaker.CircuitBreaker circuitbreaker.Middleware(cb *circuitbreaker.CircuitBreaker) fiber.Handler ``` `Middleware` admits a request, runs the handler chain, reports the outcome and releases the half-open slot it took, so none of that has to be paired up by hand. `AllowRequest`, `ReleaseSemaphore`, `ReportSuccess` and `ReportFailure` remain available for callers that already drive the circuit themselves, but they are **deprecated**: a half-open slot taken through `AllowRequest` is held until `ReleaseSemaphore` returns it or the next state change reclaims it. ### Config | Property | Type | Description | Default | |:---------|:-----|:------------|:--------| | FailureThreshold | `int` | Number of failures required to open the circuit | `5` | | Timeout | `time.Duration` | How long the circuit stays open before a probe is allowed | `5 * time.Second` | | SuccessThreshold | `int` | Number of successful probes required to close the circuit | `1` | | HalfOpenMaxConcurrent | `int` | Max concurrent probes in half-open state | `1` | | Interval | `time.Duration` | Period after which failure counts reset in closed state. Zero means failures accumulate until the circuit opens. | `0` | | IsFailure | `func(c fiber.Ctx, err error) bool` | Decides whether a served request counts as a failure | `err != nil \|\| Status >= 500` | | Clock | `func() time.Time` | Reads the current time. Recovery is derived from it, so substituting a clock drives the circuit through every state without waiting. | `time.Now` | | OnOpen | `func(fiber.Ctx) error` | Answers a request **refused because the circuit is open** | `503 response` | | OnHalfOpen | `func(fiber.Ctx) error` | Answers a request **refused because half-open is already at `HalfOpenMaxConcurrent`** | `429 response` | | OnClose | `func(fiber.Ctx) error` | Runs after the successful probe that **closed** the circuit, once the handler has already answered. Its return value is discarded. | `no-op` | #### About the callbacks `OnOpen` and `OnHalfOpen` write the response for a request the circuit breaker refused itself; `OnClose` writes nothing. None of them is a transition observer, and none fires when the circuit merely changes state: - `OnOpen` fires for each request *refused while* open — not at the moment the circuit opens. - `OnHalfOpen` fires for each probe *refused while* half-open — not when half-open is entered. - `OnClose` fires once, after the probe whose success closed the circuit — and it is a **notification, not a response writer**. The protected handler has already written the response by then, and Fiber writes eagerly, so a `Send`, `JSON` or `Status` call in `OnClose` **replaces the handler's answer** even though the error it returns is discarded. Record the recovery; do not write to the response, and do not advance the handler chain. ### Operator controls | Method | Effect | |:-------|:-------| | `GetState()` / `IsOpen()` | Current state, with any due recovery applied first | | `Metrics()` / `GetStateStats()` | Counters, and the thresholds and timestamps behind them | | `HealthHandler()` | A Fiber handler answering 503 while open, 200 otherwise | | `ForceOpen()` | Opens the circuit **and keeps it open** | | `ForceClose()` / `Reset()` | Returns the circuit to closed and starts a new failure-count window | | `SetTimeout(d)` | Changes the recovery timeout, including for a circuit that is already open | `ForceOpen` is sticky: unlike a circuit that opened on failures, a forced-open circuit does **not** recover on its own when `Timeout` elapses. Call `ForceClose` or `Reset` to end it. ### Circuit Breaker Usage in Fiber (Example) This guide explains how to use a Circuit Breaker in a Fiber application at different levels, from basic setup to advanced customization. #### 1. Basic Setup A **global** Circuit Breaker protects all routes. **Example: Applying Circuit Breaker to All Routes** ```go package main import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/contrib/v3/circuitbreaker" ) func main() { app := fiber.New() // Create a new Circuit Breaker with custom configuration cb := circuitbreaker.New(circuitbreaker.Config{ FailureThreshold: 3, // Max failures before opening the circuit Timeout: 5 * time.Second, // Wait time before retrying SuccessThreshold: 2, // Required successes to move back to closed state }) // Apply Circuit Breaker to ALL routes app.Use(circuitbreaker.Middleware(cb)) // Sample Route app.Get("/", func(c fiber.Ctx) error { return c.SendString("Hello, world!") }) // Optional: Expose health check endpoint app.Get("/health/circuit", cb.HealthHandler()) // Optional: Expose metrics about the circuit breaker: app.Get("/metrics/circuit", func(c fiber.Ctx) error { return c.JSON(cb.GetStateStats()) }) app.Listen(":3000") } ``` > The circuit breaker starts nothing in the background, so it needs no shutdown step. > `cb.Stop()` is **deprecated** and does nothing; it is kept only so existing callers > still compile. #### 2. Route & Route-Group Specific Circuit Breaker Apply the Circuit Breaker **only to specific routes**. ```go app.Get("/protected", circuitbreaker.Middleware(cb), func(c fiber.Ctx) error { return c.SendString("Protected service running") }) ``` Apply the Circuit Breaker **only to specific routes groups**. ```go app := route.Group("/api") app.Use(circuitbreaker.Middleware(cb)) // All routes in this group will be protected app.Get("/users", getUsersHandler) app.Post("/users", createUserHandler) ``` #### 3. Circuit Breaker with Custom Failure Handling Customize the response when the circuit **opens**. ```go cb := circuitbreaker.New(circuitbreaker.Config{ FailureThreshold: 3, Timeout: 10 * time.Second, OnOpen: func(c fiber.Ctx) error { return c.Status(fiber.StatusServiceUnavailable). JSON(fiber.Map{"error": "Circuit Open: Service unavailable"}) }, OnHalfOpen: func(c fiber.Ctx) error { return c.Status(fiber.StatusTooManyRequests). JSON(fiber.Map{"error": "Circuit Half-Open: Retrying service"}) }, // OnClose runs after the protected handler has answered, so writing here // would replace its response. Record the recovery instead. OnClose: func(c fiber.Ctx) error { log.Printf("circuit closed: %s recovered", c.Path()) return nil }, }) // Apply to a specific route app.Get("/custom", circuitbreaker.Middleware(cb), func(c fiber.Ctx) error { return c.SendString("This service is protected by a Circuit Breaker") }) ``` ✅ Now, when failures exceed the threshold, ***custom error responses** will be sent. #### 4. Circuit Breaker for External API Calls Use a Circuit Breaker **when calling an external API.** ```go app.Get("/external-api", circuitbreaker.Middleware(cb), func(c fiber.Ctx) error { // Simulating an external API call resp, err := fiber.Get("https://example.com/api") if err != nil { return fiber.NewError(fiber.StatusInternalServerError, "External API failed") } return c.SendString(resp.Body()) }) ``` ✅ If the external API fails repeatedly, **the circuit breaker prevents further calls.** #### 5. Circuit Breaker with Concurrent Requests Handling **Limit how many probes run at once** while the circuit is recovering. ```go cb := circuitbreaker.New(circuitbreaker.Config{ FailureThreshold: 3, Timeout: 5 * time.Second, SuccessThreshold: 2, HalfOpenMaxConcurrent: 2, // Allow only 2 concurrent probes in half-open }) app.Get("/half-open-limit", circuitbreaker.Middleware(cb), func(c fiber.Ctx) error { time.Sleep(2 * time.Second) // Simulating slow response return c.SendString("Half-Open: Limited concurrent requests") }) ``` ✅ When in **half-open** state, only **2 concurrent requests are allowed**. #### 6. Circuit Breaker with Custom Metrics Integrate **Prometheus metrics** and **structured logging**. ```go cb := circuitbreaker.New(circuitbreaker.Config{ FailureThreshold: 5, Timeout: 10 * time.Second, OnOpen: func(c fiber.Ctx) error { log.Println("Circuit Breaker Opened!") prometheus.Inc("circuit_breaker_open_count") return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{"error": "Service Down"}) }, }) ``` ✅ Logs when the circuit opens & increments Prometheus metrics. #### 7. Circuit Breaker with Failure Count Reset Interval Use `Interval` to reset the failure count once the interval has elapsed in the closed state. The reset is applied lazily: the next failure reported after the interval has elapsed starts a fresh count instead of carrying the old one over. Without `Interval`, failures accumulate indefinitely in the closed state until the threshold is reached. ```go cb := circuitbreaker.New(circuitbreaker.Config{ FailureThreshold: 5, Timeout: 10 * time.Second, Interval: 30 * time.Second, // Reset failure count every 30 seconds }) app.Use(circuitbreaker.Middleware(cb)) ``` ✅ If 4 failures occur and the next failure is reported more than 30 seconds after the window started, the count restarts at 1 instead of reaching the threshold. The circuit only opens when 5 failures accumulate within one 30-second window. #### 8. Advanced: Multiple Circuit Breakers for Different Services Use different Circuit Breakers for different services. ```go dbCB := circuitbreaker.New(circuitbreaker.Config{FailureThreshold: 5, Timeout: 10 * time.Second}) apiCB := circuitbreaker.New(circuitbreaker.Config{FailureThreshold: 3, Timeout: 5 * time.Second}) app.Get("/db-service", circuitbreaker.Middleware(dbCB), func(c fiber.Ctx) error { return c.SendString("DB service request") }) app.Get("/api-service", circuitbreaker.Middleware(apiCB), func(c fiber.Ctx) error { return c.SendString("External API service request") }) ``` ✅ Each service has its own failure threshold & timeout. --- ## Coraza ![Release](https://img.shields.io/github/v/tag/gofiber/contrib?filter=*coraza*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://github.com/gofiber/contrib/workflows/Test%20Coraza/badge.svg) [Coraza](https://coraza.io/) WAF middleware for Fiber. **Compatible with Fiber v3.** ### Go version support We only support the latest two versions of Go. Visit [https://go.dev/doc/devel/release](https://go.dev/doc/devel/release) for more information. ### Install ```sh go get github.com/gofiber/fiber/v3 go get github.com/gofiber/contrib/v3/coraza ``` ### Signature ```go coraza.New(config ...coraza.Config) fiber.Handler coraza.NewEngine(config coraza.Config) (*coraza.Engine, error) ``` ### Config | Property | Type | Description | Default | |:--|:--|:--|:--| | Next | `func(fiber.Ctx) bool` | Defines a function to skip this middleware when it returns true | `nil` | | BlockHandler | `func(fiber.Ctx, coraza.InterruptionDetails) error` | Custom handler for blocked requests | `nil` | | ErrorHandler | `func(fiber.Ctx, coraza.MiddlewareError) error` | Custom handler for middleware failures | `nil` | | DirectivesFile | `[]string` | Coraza directives files loaded in order | `nil` | | RootFS | `fs.FS` | Optional filesystem used to resolve `DirectivesFile` | `nil` | | BlockMessage | `string` | Message returned by the built-in block handler | `"Request blocked by Web Application Firewall"` | | LogLevel | `fiberlog.Level` | Middleware lifecycle log level | `fiberlog.LevelInfo` in `coraza.ConfigDefault` | | RequestBodyAccess | `bool` | Enables request body inspection | `true` in `coraza.ConfigDefault` | | MetricsCollector | `coraza.MetricsCollector` | Optional custom metrics collector | `nil` (falls back to the built-in collector) | If you want the defaults, start from `coraza.ConfigDefault` and override the fields you need. For zero-value-backed settings such as `RequestBodyAccess: false`, `LogLevel: fiberlog.LevelTrace`, or resetting `MetricsCollector` to the built-in default, use `ConfigDefault` or the helper methods `WithRequestBodyAccess`, `WithLogLevel`, and `WithMetricsCollector` so the choice remains explicit. By default, the middleware starts without external rule files. Set `DirectivesFile` to load your Coraza or CRS ruleset. Request body size follows the Fiber app `BodyLimit`. Wildcard entries in `DirectivesFile` are expanded before Coraza initializes. If a wildcard matches no files, initialization fails with an error and the middleware does not start. ### Usage ```go package main import ( "log" "github.com/gofiber/contrib/v3/coraza" "github.com/gofiber/fiber/v3" ) func main() { app := fiber.New() cfg := coraza.ConfigDefault cfg.DirectivesFile = []string{"./conf/coraza.conf"} app.Use(coraza.New(cfg)) app.Get("/", func(c fiber.Ctx) error { return c.SendString("ok") }) log.Fatal(app.Listen(":3000")) } ``` ### Advanced usage with Engine Use `NewEngine` when you need explicit lifecycle control, reload support, or observability data. ```go engineCfg := coraza.ConfigDefault engineCfg.DirectivesFile = []string{"./conf/coraza.conf"} engine, err := coraza.NewEngine(engineCfg) if err != nil { log.Fatal(err) } app.Use(engine.Middleware(coraza.MiddlewareConfig{ Next: func(c fiber.Ctx) bool { return c.Path() == "/healthz" }, BlockHandler: func(c fiber.Ctx, details coraza.InterruptionDetails) error { return c.Status(details.StatusCode).JSON(fiber.Map{ "blocked": true, "message": "request blocked by security policy", }) }, })) ``` For production deployments, avoid returning rule identifiers or detailed match data to clients. Prefer a generic error body and log the matched rule metadata server-side when needed. Note that the built-in block handler sets an `X-WAF-Blocked: true` response header and a message naming the Web Application Firewall. If you want to avoid WAF fingerprinting, provide a custom `BlockHandler` (or `BlockMessage`) that returns a neutral response. ### Engine observability The middleware does not open operational routes for you, but `Engine` exposes data-oriented methods that can be used to build your own endpoints: - `engine.Reload()` - `engine.MetricsSnapshot()` - `engine.Snapshot()` - `engine.Report()` `Snapshot()` and `Report()` include server filesystem paths (directive files) and raw initialization error details. Only expose such endpoints internally or behind authentication, never to untrusted clients. `Reload()` swaps in the new WAF instance immediately and then waits until requests still being inspected by the previous instance finish inspection before closing it. Inspection ends before downstream handlers run, so the wait is bounded by WAF processing time and it is safe to call `Reload()` from a handler running behind the middleware. `BlockRate` is cumulative since process start or the most recent collector reset. `RecentLatencyMs` and `RecentBlockRate` are EWMA-based recent-trend metrics with a fixed alpha of `0.2`. The EWMA is weighted per request, not per time unit: roughly the last 20 requests dominate the value, so under high traffic "recent" covers a very short time span and under low traffic a long one. The measured latency covers the full downstream handler chain, not just WAF inspection. ### Reverse proxy / trusted proxy notes When running behind Nginx, Caddy, Traefik, Cloudflare, or any other reverse proxy, make sure Fiber trusted proxy settings are configured correctly before relying on `c.IP()` or Coraza rules that depend on `REMOTE_ADDR`. If trusted proxy validation is not configured correctly, spoofed forwarding headers may cause Coraza to evaluate rules against attacker-controlled client IP values. ### Notes - Request headers and request bodies are inspected. - Request body size follows the Fiber app `BodyLimit`. - Response body inspection is not supported. - `coraza.New()` starts successfully without external rule files, but it does not load any rules until `DirectivesFile` is configured. - Invalid configuration causes `coraza.New(...)` to panic during startup, which allows applications to fail fast. ### References - [Coraza Docs](https://coraza.io/) - [OWASP Core Rule Set](https://coraza.io/docs/tutorials/coreruleset) --- ## Fgprof ![Release](https://img.shields.io/github/v/tag/gofiber/contrib?filter=*fgprof*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://github.com/gofiber/contrib/workflows/Test%20Fgprof/badge.svg) [fgprof](https://github.com/felixge/fgprof) support for Fiber. **Compatible with Fiber v3.** ### Go version support We only support the latest two versions of Go. Visit [https://go.dev/doc/devel/release](https://go.dev/doc/devel/release) for more information. ### Install Using fgprof to profiling your Fiber app. ```sh go get -u github.com/gofiber/fiber/v3 go get -u github.com/gofiber/contrib/v3/fgprof ``` ### Config | Property | Type | Description | Default | |----------|---------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------|---------| | Next | `func(c fiber.Ctx) bool` | A function to skip this middleware when returned `true`. | `nil` | | Prefix | `string`. | Prefix defines a URL prefix added before "/debug/fgprof". Note that it should start with (but not end with) a slash. Example: "/federated-fiber" | `""` | ### Example ```go package main import ( "log" "github.com/gofiber/contrib/v3/fgprof" "github.com/gofiber/fiber/v3" ) func main() { app := fiber.New() app.Use(fgprof.New()) app.Get("/", func(c fiber.Ctx) error { return c.SendString("OK") }) log.Fatal(app.Listen(":3000")) } ``` ```bash go tool pprof -http=:8080 http://localhost:3000/debug/fgprof ``` --- ## HCaptcha ![Release](https://img.shields.io/github/v/tag/gofiber/contrib?filter=*hcaptcha*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://github.com/gofiber/contrib/workflows/Test%20hcaptcha/badge.svg) A simple [HCaptcha](https://hcaptcha.com) middleware to prevent bot attacks. :::note Requires Go **1.26** and above ::: **Compatible with Fiber v3.** ### Go version support We only support the latest two versions of Go. Visit [https://go.dev/doc/devel/release](https://go.dev/doc/devel/release) for more information. ### Install :::caution This middleware only supports Fiber **v3**. ::: ```shell go get -u github.com/gofiber/fiber/v3 go get -u github.com/gofiber/contrib/v3/hcaptcha ``` ### Signature ```go hcaptcha.New(config hcaptcha.Config) fiber.Handler ``` ### Config | Property | Type | Description | Default | |:----------------|:-----------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------------------------------------| | SecretKey | `string` | The secret key you obtained from the HCaptcha admin panel. This field must not be empty. | `""` | | ResponseKeyFunc | `func(fiber.Ctx) (string, error)` | ResponseKeyFunc should return the token that the captcha provides upon successful solving. By default, it gets the token from the body by parsing a JSON request and returns the `hcaptcha_token` field. | `hcaptcha.DefaultResponseKeyFunc` | | SiteVerifyURL | `string` | This property specifies the API resource used for token authentication. | `https://api.hcaptcha.com/siteverify` | | ValidateFunc | `func(success bool, c fiber.Ctx) error` | Optional custom validation hook called after siteverify completes. Parameters: `success` (hCaptcha verification result), `c` (Fiber context). Return `nil` to continue, or return an `error` to stop request processing. If unset, middleware defaults to blocking unsuccessful verification. For secure bot protection, reject when `success == false`. | `nil` | ### Example ```go package main import ( "errors" "log" "github.com/gofiber/contrib/v3/hcaptcha" "github.com/gofiber/fiber/v3" ) const ( TestSecretKey = "0x0000000000000000000000000000000000000000" TestSiteKey = "20000000-ffff-ffff-ffff-000000000002" ) func main() { app := fiber.New() captcha := hcaptcha.New(hcaptcha.Config{ // Must set the secret key. SecretKey: TestSecretKey, // Optional custom validation handling. ValidateFunc: func(success bool, c fiber.Ctx) error { if !success { if err := c.Status(fiber.StatusForbidden).JSON(fiber.Map{ "error": "HCaptcha validation failed", "details": "Please complete the captcha challenge and try again", }); err != nil { return err } return errors.New("custom validation failed") } return nil }, }) app.Get("/api/", func(c fiber.Ctx) error { return c.JSON(fiber.Map{ "hcaptcha_site_key": TestSiteKey, }) }) // Middleware order matters: place hcaptcha middleware before the final handler. app.Post("/api/submit", captcha, func(c fiber.Ctx) error { return c.SendString("You are not a robot") }) log.Fatal(app.Listen(":3000")) } ``` --- ## I18n ![Release](https://img.shields.io/github/v/tag/gofiber/contrib?filter=*i18n*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://github.com/gofiber/contrib/workflows/Test%20i18n/badge.svg) [go-i18n](https://github.com/nicksnyder/go-i18n) support for Fiber. **Compatible with Fiber v3.** ### Go version support We only support the latest two versions of Go. Visit [https://go.dev/doc/devel/release](https://go.dev/doc/devel/release) for more information. ### Install ```sh go get -u github.com/gofiber/fiber/v3 go get -u github.com/gofiber/contrib/v3/i18n ``` ### API | Name | Signature | Description | |----------------------|--------------------------------------------------------------------------|-----------------------------------------------------------------------------| | New | `New(config ...*i18n.Config) *i18n.I18n` | Create a reusable, thread-safe localization container. | | (*I18n).Localize | `Localize(ctx fiber.Ctx, params interface{}) (string, error)` | Returns a localized message. `params` must be a message ID string or `*goi18n.LocalizeConfig`. Returns an error if the message is not found, the param type is unsupported, or `params` is nil. | | (*I18n).MustLocalize | `MustLocalize(ctx fiber.Ctx, params interface{}) string` | Like `Localize` but panics on any error. | ### Types | Name | Description | |------------------|-----------------------------------------------------------------------------------------------------| | `Loader` | Interface for loading message files. Implement `LoadMessage(path string) ([]byte, error)`. | | `LoaderFunc` | Adapter to use a plain function as a `Loader`. | | `EmbedLoader` | `Loader` implementation backed by an `embed.FS`. Use with Go's `//go:embed` directive. | ### Config | Property | Type | Description | Default | |------------------|---------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------| | RootPath | `string` | The i18n template folder path. | `"./example/localize"` | | AcceptLanguages | `[]language.Tag` | A collection of languages that can be processed. | `[]language.Tag{language.Chinese, language.English}` | | FormatBundleFile | `string` | The type of the template file. | `"yaml"` | | DefaultLanguage | `language.Tag` | The default returned language type. | `language.English` | | Loader | `Loader` | The implementation of the Loader interface, which defines how to read the file. We provide both os.ReadFile and embed.FS.ReadFile. | `LoaderFunc(os.ReadFile)` | | UnmarshalFunc | `i18n.UnmarshalFunc` | The function used for decoding template files. | `yaml.Unmarshal` | | LangHandler | `func(ctx fiber.Ctx, defaultLang string) string` | Used to get the kind of language handled by fiber.Ctx and defaultLang. | Retrieved from the request header `Accept-Language` or query parameter `lang`. | ### Example ```go package main import ( "log" contribi18n "github.com/gofiber/contrib/v3/i18n" "github.com/gofiber/fiber/v3" goi18n "github.com/nicksnyder/go-i18n/v2/i18n" "golang.org/x/text/language" ) func main() { translator := contribi18n.New(&contribi18n.Config{ RootPath: "./example/localize", AcceptLanguages: []language.Tag{language.Chinese, language.English}, DefaultLanguage: language.Chinese, }) app := fiber.New() app.Get("/", func(c fiber.Ctx) error { localize, err := translator.Localize(c, "welcome") if err != nil { return c.Status(fiber.StatusInternalServerError).SendString(err.Error()) } return c.SendString(localize) }) app.Get("/:name", func(ctx fiber.Ctx) error { return ctx.SendString(translator.MustLocalize(ctx, &goi18n.LocalizeConfig{ MessageID: "welcomeWithName", TemplateData: map[string]string{ "name": ctx.Params("name"), }, })) }) log.Fatal(app.Listen(":3000")) } ``` ### Migration from middleware usage The package now exposes a global, thread-safe container instead of middleware. To migrate existing code: 1. Remove any `app.Use(i18n.New(...))` calls—the translator no longer registers middleware. 2. Instantiate a shared translator during application startup with `translator := i18n.New(...)`. 3. Replace package-level calls such as `i18n.Localize`/`i18n.MustLocalize` with the respective methods on your translator (`translator.Localize`, `translator.MustLocalize`). 4. Drop any manual interaction with `ctx.Locals("i18n")`; all state is managed inside the translator instance. The translator instance is safe for concurrent use across handlers and reduces per-request allocations by reusing the same bundle and localizer map. --- ## JWT ![Release](https://img.shields.io/github/v/tag/gofiber/contrib?filter=*jwt*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://github.com/gofiber/contrib/workflows/Test%20jwt/badge.svg) JWT returns a JSON Web Token (JWT) auth middleware. For valid token, it sets the token in Ctx.Locals (and in the underlying `context.Context` when `PassLocalsToContext` is enabled) and calls next handler. For invalid token, it returns "401 - Unauthorized" error. For missing token, it returns "400 - Bad Request" error. Either way the response carries the `WWW-Authenticate` challenge required by [RFC 9110](https://www.rfc-editor.org/rfc/rfc9110#section-15.5.2) and [RFC 6750](https://www.rfc-editor.org/rfc/rfc6750#section-3). See [Standards compliance](#standards-compliance). Special thanks and credits to [Echo](https://echo.labstack.com/middleware/jwt) **Compatible with Fiber v3.** ### Go version support We only support the latest two versions of Go. Visit [https://go.dev/doc/devel/release](https://go.dev/doc/devel/release) for more information. ### Install ```bash go get -u github.com/gofiber/fiber/v3 go get -u github.com/gofiber/contrib/v3/jwt go get -u github.com/golang-jwt/jwt/v5 ``` ### Signature ```go jwtware.New(config ...jwtware.Config) func(fiber.Ctx) error jwtware.FromContext(ctx any) *jwt.Token // jwt "github.com/golang-jwt/jwt/v5" ``` `FromContext` accepts a `fiber.Ctx`, `fiber.CustomCtx`, `*fasthttp.RequestCtx`, or a standard `context.Context` (e.g. the value returned by `c.Context()` when `PassLocalsToContext` is enabled). It returns a `*jwt.Token` from `github.com/golang-jwt/jwt/v5`. ### Config | Property | Type | Description | Default | |:-------------------|:-------------------------------------|:--------------------------------------------------------------------------------------|:-----------------------------| | Next | `func(fiber.Ctx) bool` | Defines a function to skip this middleware when it returns true | `nil` | | SuccessHandler | `func(fiber.Ctx) error` | Executed when a token is valid. | `c.Next()` | | ErrorHandler | `func(fiber.Ctx, error) error` | ErrorHandler defines a function which is executed for an invalid token. | `401 Invalid or expired JWT` | | Realm | `string` | Protected area named in the `WWW-Authenticate` challenge sent with a rejection. | `"Restricted"` | | SigningKey | `SigningKey` | Signing key used to validate the token. Used as a fallback if `SigningKeys` is empty. | `nil` | | SigningKeys | `map[string]SigningKey` | Map of signing keys used to validate tokens via the `kid` header. | `nil` | | Claims | `jwt.Claims` | Claims are extendable claims data defining token content. | `jwt.MapClaims{}` | | Extractor | `Extractor` | Function used to extract the token from the request. | `FromAuthHeader("Bearer")` | | TokenProcessorFunc | `func(token string) (string, error)` | TokenProcessorFunc processes the token extracted using the Extractor. | `nil` | | KeyFunc | `jwt.Keyfunc` | User-defined function that supplies the public key for token validation. | `nil` (uses internal default)| | JWKSetURLs | `[]string` | List of JSON Web Key (JWK) Set URLs used to obtain signing keys for parsing JWTs. | `nil` | | ParserOptions | `[]jwt.ParserOption` | List of [`jwt.ParserOption`](https://pkg.go.dev/github.com/golang-jwt/jwt/v5#ParserOption), provides additional options for JWT parsing. | `nil` | | KnownCriticalHeaders | `[]string` | JWS `crit` header parameters this application understands and processes itself. | `nil` | ### Available Extractors JWT middleware uses the shared Fiber extractors (github.com/gofiber/fiber/v3/extractors) and provides several helpers for different token sources. Import them with: ```go import "github.com/gofiber/fiber/v3/extractors" ``` For an overview and additional examples, see the Fiber Extractors guide: - https://docs.gofiber.io/guide/extractors - `extractors.FromAuthHeader(prefix string)` - Extracts token from the Authorization header using the given scheme prefix (e.g., "Bearer"). **This is the recommended and most secure method.** - `extractors.FromHeader(header string)` - Extracts token from the specified HTTP header - `extractors.FromQuery(param string)` - Extracts token from URL query parameters - `extractors.FromParam(param string)` - Extracts token from URL path parameters - `extractors.FromCookie(key string)` - Extracts token from cookies - `extractors.FromForm(param string)` - Extracts token from form data - `extractors.Chain(extrs ...extractors.Extractor)` - Tries multiple extractors in order until one succeeds #### Security Considerations ⚠️ **Security Warning**: When choosing an extractor, consider the security implications: - **URL-based extractors** (`FromQuery`, `FromParam`): Tokens can leak through server logs, browser referrer headers, proxy logs, and browser history. Use only for development or when security is not a primary concern. - **Form-based extractors** (`FromForm`): Similar risks to URL extractors, especially if forms are submitted via GET requests. - **Header-based extractors** (`FromAuthHeader`, `FromHeader`): Most secure as headers are not typically logged or exposed in referrers. - **Cookie-based extractors** (`FromCookie`): Secure for web applications but requires proper cookie security settings (HttpOnly, Secure, SameSite). **Recommendation**: Use `FromAuthHeader("Bearer")` (the default) for production applications unless you have specific requirements that necessitate alternative extractors. ### Standards compliance #### What the middleware enforces - **The signing algorithm comes from your configuration, not from the token.** When `SigningKey.JWTAlg` is set - or when every entry of `SigningKeys` sets it - the parser rejects any other `alg` before a key is looked up, as [RFC 8725 Section 3.1](https://www.rfc-editor.org/rfc/rfc8725#section-3.1) asks. Pass `jwt.WithValidMethods` in `ParserOptions` to pin the algorithms yourself; a value you pass wins over the derived one. Configurations that leave the algorithm open (`KeyFunc`, `JWKSetURLs`, or a `SigningKey` without `JWTAlg`) are only as strict as the key material and the JWK `alg` allow, so pinning is worth it. - **`alg: none` is refused**, and keys are never read out of the token: the `jwk`, `jku`, `x5u` and `x5c` header parameters are ignored, per [RFC 8725 Sections 3.4 and 3.5](https://www.rfc-editor.org/rfc/rfc8725#section-3.4). This describes the built-in key lookup - `SigningKey`, `SigningKeys` and `JWKSetURLs`. A `KeyFunc` of your own is handed the parsed token and can read whatever it likes out of the header, and a `SigningKey.Key` set to `jwt.UnsafeAllowNoneSignatureType` accepts `alg: none` by design; neither is something the middleware overrides. - **Critical header parameters** (`crit`) are checked as [RFC 7515 Section 4.1.11](https://www.rfc-editor.org/rfc/rfc7515#section-4.1.11) requires: a token that marks a header parameter critical is rejected unless the parameter is listed in `KnownCriticalHeaders`. A `crit` value that is not a non-empty array of names, repeats a name, names a registered JOSE parameter such as `alg`, or names a parameter the header does not contain, is rejected as well. Nothing else uses `crit`, so leaving `KnownCriticalHeaders` unset is the safe default. `b64` ([RFC 7797](https://www.rfc-editor.org/rfc/rfc7797)) cannot be declared: it changes how the payload is encoded, which the parser has already settled before your code sees the token, so naming it panics at `New` and tokens carrying it are always rejected. - **`exp` and `nbf`** are validated by default on every request by `github.com/golang-jwt/jwt/v5` ([RFC 7519 Sections 4.1.4 and 4.1.5](https://www.rfc-editor.org/rfc/rfc7519#section-4.1.4)). Use `jwt.WithLeeway`, `jwt.WithExpirationRequired` or `jwt.WithNotBeforeRequired` in `ParserOptions` to tighten this - and note that `jwt.WithoutClaimsValidation()` in `ParserOptions` turns it off entirely, so an expired token is accepted. - **Base64url segments must be unpadded** by default ([RFC 7515 Section 2](https://www.rfc-editor.org/rfc/rfc7515#section-2)) - `jwt.WithPaddingAllowed()` in `ParserOptions` accepts padded segments. An `Authorization` header read by `FromAuthHeader` with a scheme - the default `FromAuthHeader("Bearer")` included - also has to be well-formed `token68` ([RFC 9110 Section 11.6.2](https://www.rfc-editor.org/rfc/rfc9110#section-11.6.2)); that check is the extractor's, so `FromAuthHeader("")`, `FromHeader("Authorization")` and an extractor of your own hand the header over unchecked, and a `TokenProcessorFunc` runs after it in any case. - **Rejections carry a challenge.** Every 400, 401 and 407 response the middleware produces gets a `WWW-Authenticate` (or `Proxy-Authenticate`) header, which [RFC 9110 Section 15.5.2](https://www.rfc-editor.org/rfc/rfc9110#section-15.5.2) requires of a 401 and [RFC 6750 Section 3](https://www.rfc-editor.org/rfc/rfc6750#section-3) requires of a resource server refusing a bearer token: ```text WWW-Authenticate: Bearer realm="Restricted", error="invalid_token", error_description="The access token expired" ``` The schemes are taken from the extractor: an `Authorization` header names its own, and every other source - a query parameter, a cookie, a form field, a route parameter, one of your own - carries what RFC 6750 calls a bearer token and so contributes `Bearer`. A chain offers each of them, in the order it tries them, so a client refused on the credential it did send is never told to retry with a scheme it never used. The realm comes from `Realm`, and the `error_description` from the reason the token failed. Error parameters are only added for the bearer scheme, as RFC 6750 defines them, and a request that presented no usable credential is answered with a bare `Bearer realm="..."` instead, which [RFC 6750 Section 3.1](https://www.rfc-editor.org/rfc/rfc6750#section-3.1) asks for. A challenge already on the response is never replaced. One your `ErrorHandler` wrote is the answer and is left exactly as it is; one inherited from an authentication middleware that ran earlier is kept and this middleware's added beside it, which [RFC 9110 Section 11.6.1](https://www.rfc-editor.org/rfc/rfc9110#section-11.6.1) allows and which leaves the client both a scheme it may be able to use and the reason its token was refused. A custom `ErrorHandler` has to leave the status somewhere the middleware can read it: on the response (`c.SendStatus(...)`, or `c.Status(...)` followed by a send), or in a returned `*fiber.Error`. Both work, including `fiber.ErrUnauthorized` on its own. What it cannot do is return some error of its own and rely on `fiber.Config.ErrorHandler` to turn that into a 401 later - the status does not exist yet when the challenge is applied, and guessing at it would put `WWW-Authenticate` on the 403s and 500s such a handler also produces. If you do route rejections through your own error type, set the challenge there: ```go ErrorHandler: func(c fiber.Ctx, err error) error { return fiber.ErrUnauthorized // or c.SendStatus(fiber.StatusUnauthorized) }, ``` - **Tokens in the URL are not stored in shared caches.** When the request URL carries a token - a query parameter, a form parameter (Fiber's `FormValue` reads the query before the body), or a route parameter - the successful response is marked `Cache-Control: private`, as [RFC 6750 Section 2.3](https://www.rfc-editor.org/rfc/rfc6750#section-2.3) asks, since the URL a shared cache keys on contains the token. A policy the handler set is kept, except that `public` is dropped and `private` added unless the policy already keeps the response out of shared caches; a policy too malformed to parse, such as one with an unterminated quoted string, is replaced rather than appended to. What decides this is whether the URL carries a credential, not which extractor supplied the one that authenticated: a chain that preferred a cookie still answered a request whose URL a cache would key on. A request whose URL holds nothing is untouched, including one to a chain that could have read the query but found it absent or empty - `?token=` names the parameter without carrying a credential, and the extractors read it as no credential too. The three sources above are the ones a middleware can recognise. An extractor of your own reads wherever you wrote it to, so if that is somewhere in the URL, mark the response yourself - there is nothing in its metadata to tell this middleware where it looked. **Register a cache inside this middleware**, so that every request is authenticated before it can be answered: ```go app.Use(jwtware.New(cfg)) app.Use(cache.New()) // runs only for authenticated requests ``` A cache registered *outside* it answers from its store before this middleware runs at all, so a hit is served with no authentication whatsoever. Fiber's cache keys on the method, path and query by default - not on cookies (`KeyCookies`) or on the `Authorization` header (`KeyHeaders`) - so with any extractor that reads a cookie or a header, a stored response is handed to requests that present no credential at all. Put a cache in front of this middleware only if its key covers every credential your extractor reads. Either way, a cache key that does not identify the user shares one user's response with the next: that is your key's business, not this middleware's. What this middleware guarantees is the directive, and for a URL that carries a token it goes on before your handler runs, so an inner cache sees it when it decides whether to store. The one case that leaves is a handler which replaces `Cache-Control` outright, since such a cache reads the replacement before this middleware regains control. #### What your application has to configure Some rules cannot be applied by a middleware, because only the application knows the values involved: - **`aud`** - [RFC 7519 Section 4.1.3](https://www.rfc-editor.org/rfc/rfc7519#section-4.1.3) requires a token whose audience is not this application to be rejected. The audience is only checked when you name it: ```go app.Use(jwtware.New(jwtware.Config{ SigningKey: jwtware.SigningKey{JWTAlg: jwtware.RS256, Key: publicKey}, ParserOptions: []jwt.ParserOption{ jwt.WithAudience("https://api.example.com"), jwt.WithIssuer("https://issuer.example.com"), jwt.WithExpirationRequired(), }, })) ``` - **`typ`** - if your deployment issues several kinds of JWT, check the media type as [RFC 8725 Section 3.11](https://www.rfc-editor.org/rfc/rfc8725#section-3.11) recommends. For example, an OAuth 2.0 access token per [RFC 9068](https://www.rfc-editor.org/rfc/rfc9068#section-2.1) carries `"typ": "at+jwt"`: ```go SuccessHandler: func(c fiber.Ctx) error { if typ, _ := jwtware.FromContext(c).Header["typ"].(string); !strings.EqualFold(typ, "at+jwt") { // A rejection from here does not pass through the middleware's own // rejection path, so it has to carry its own challenge. c.Set(fiber.HeaderWWWAuthenticate, `Bearer realm="Restricted", error="invalid_token", error_description="The access token is of an unexpected type"`) return c.Status(fiber.StatusUnauthorized).SendString("unexpected token type") } return c.Next() }, ``` - **`jti`** - replay detection needs state the middleware does not keep. #### Status code for a request without credentials A request that carries no credentials at all is answered with **400** and `missing or malformed JWT`, which is what this middleware has always done and what the extractor can tell us: it reports a missing and a malformed credential as the same error, which is also why its challenge names no error code. [RFC 6750 Section 3.1](https://www.rfc-editor.org/rfc/rfc6750#section-3.1) reserves 400 for a malformed request and answers a request that "lacks any authentication information" with 401 instead. If you need those OAuth 2.0 semantics exactly, say so in an `ErrorHandler`: ```go ErrorHandler: func(c fiber.Ctx, err error) error { if errors.Is(err, extractors.ErrNotFound) { if c.Get(fiber.HeaderAuthorization) == "" { // No credentials at all: challenge without naming an error. c.Set(fiber.HeaderWWWAuthenticate, `Bearer realm="Restricted"`) return c.SendStatus(fiber.StatusUnauthorized) } return c.Status(fiber.StatusBadRequest).SendString(jwtware.ErrMissingToken.Error()) } return c.Status(fiber.StatusUnauthorized).SendString("Invalid or expired JWT") }, ``` ### HS256 Example ```go package main import ( "time" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/extractors" jwtware "github.com/gofiber/contrib/v3/jwt" "github.com/golang-jwt/jwt/v5" ) func main() { app := fiber.New() // Login route app.Post("/login", login) // Unauthenticated route app.Get("/", accessible) // JWT Middleware app.Use(jwtware.New(jwtware.Config{ SigningKey: jwtware.SigningKey{Key: []byte("secret")}, Extractor: extractors.FromAuthHeader("Bearer"), })) // Restricted Routes app.Get("/restricted", restricted) app.Listen(":3000") } func login(c fiber.Ctx) error { user := c.FormValue("user") pass := c.FormValue("pass") // Throws Unauthorized error if user != "john" || pass != "doe" { return c.SendStatus(fiber.StatusUnauthorized) } // Create the Claims claims := jwt.MapClaims{ "name": "John Doe", "admin": true, "exp": time.Now().Add(time.Hour * 72).Unix(), } // Create token token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) // Generate encoded token and send it as response. t, err := token.SignedString([]byte("secret")) if err != nil { return c.SendStatus(fiber.StatusInternalServerError) } return c.JSON(fiber.Map{"token": t}) } func accessible(c fiber.Ctx) error { return c.SendString("Accessible") } func restricted(c fiber.Ctx) error { user := jwtware.FromContext(c) claims := user.Claims.(jwt.MapClaims) name := claims["name"].(string) return c.SendString("Welcome " + name) } ``` ### Cookie Extractor Example ```go package main import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/extractors" jwtware "github.com/gofiber/contrib/v3/jwt" ) func main() { app := fiber.New() // JWT Middleware with cookie extractor app.Use(jwtware.New(jwtware.Config{ SigningKey: jwtware.SigningKey{Key: []byte("secret")}, Extractor: extractors.FromCookie("token"), })) app.Get("/protected", func(c fiber.Ctx) error { return c.SendString("Protected route") }) app.Listen(":3000") } ``` ### HS256 Test _Login using username and password to retrieve a token._ ```bash curl --data "user=john&pass=doe" http://localhost:3000/login ``` _Response_ ```json { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0NjE5NTcxMzZ9.RB3arc4-OyzASAaUhC2W3ReWaXAt_z2Fd3BN4aWTgEY" } ``` _Request a restricted resource using the token in Authorization request header._ ```bash curl localhost:3000/restricted -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0NjE5NTcxMzZ9.RB3arc4-OyzASAaUhC2W3ReWaXAt_z2Fd3BN4aWTgEY" ``` _Response_ ```text Welcome John Doe ``` ### RS256 Example ```go package main import ( "crypto/rand" "crypto/rsa" "log" "time" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/extractors" "github.com/golang-jwt/jwt/v5" jwtware "github.com/gofiber/contrib/v3/jwt" ) var ( // Obviously, this is just a test example. Do not do this in production. // In production, you would have the private key and public key pair generated // in advance. NEVER add a private key to any GitHub repo. privateKey *rsa.PrivateKey ) func main() { app := fiber.New() // Just as a demo, generate a new private/public key pair on each run. See note above. rng := rand.Reader var err error privateKey, err = rsa.GenerateKey(rng, 2048) if err != nil { log.Fatalf("rsa.GenerateKey: %v", err) } // Login route app.Post("/login", login) // Unauthenticated route app.Get("/", accessible) // JWT Middleware app.Use(jwtware.New(jwtware.Config{ SigningKey: jwtware.SigningKey{ JWTAlg: jwtware.RS256, Key: privateKey.Public(), }, Extractor: extractors.FromAuthHeader("Bearer"), })) // Restricted Routes app.Get("/restricted", restricted) app.Listen(":3000") } func login(c fiber.Ctx) error { user := c.FormValue("user") pass := c.FormValue("pass") // Throws Unauthorized error if user != "john" || pass != "doe" { return c.SendStatus(fiber.StatusUnauthorized) } // Create the Claims claims := jwt.MapClaims{ "name": "John Doe", "admin": true, "exp": time.Now().Add(time.Hour * 72).Unix(), } // Create token token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) // Generate encoded token and send it as response. t, err := token.SignedString(privateKey) if err != nil { log.Printf("token.SignedString: %v", err) return c.SendStatus(fiber.StatusInternalServerError) } return c.JSON(fiber.Map{"token": t}) } func accessible(c fiber.Ctx) error { return c.SendString("Accessible") } func restricted(c fiber.Ctx) error { user := jwtware.FromContext(c) claims := user.Claims.(jwt.MapClaims) name := claims["name"].(string) return c.SendString("Welcome " + name) } ``` ### Retrieving the token with PassLocalsToContext When `fiber.Config{PassLocalsToContext: true}` is set, the JWT token stored by the middleware is also available in the underlying `context.Context`. Use `jwtware.FromContext` with any of the supported context types: ```go // From a fiber.Ctx (most common usage) token := jwtware.FromContext(c) // From the underlying context.Context (useful in service layers or when PassLocalsToContext is enabled) token := jwtware.FromContext(c.Context()) ``` ### RS256 Test The RS256 is actually identical to the HS256 test above. ### JWK Set Test The tests are identical to basic `JWT` tests above, with exception that `JWKSetURLs` to valid public keys collection in JSON Web Key (JWK) Set format should be supplied. See [RFC 7517](https://www.rfc-editor.org/rfc/rfc7517). ### Custom KeyFunc example KeyFunc defines a user-defined function that supplies the public key for a token validation. The function shall take care of verifying the signing algorithm and selecting the proper key. A user-defined KeyFunc can be useful if tokens are issued by an external party. When a user-defined KeyFunc is provided, SigningKey, SigningKeys, and SigningMethod are ignored. This is one of the three options to provide a token validation key. The order of precedence is a user-defined KeyFunc, SigningKeys and SigningKey. Required if neither SigningKeys nor SigningKey is provided. Default to an internal implementation verifying the signing algorithm and selecting the proper key. ```go package main import ( "fmt" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/extractors" jwtware "github.com/gofiber/contrib/v3/jwt" "github.com/golang-jwt/jwt/v5" ) func main() { app := fiber.New() app.Use(jwtware.New(jwtware.Config{ KeyFunc: customKeyFunc(), Extractor: extractors.FromAuthHeader("Bearer"), })) app.Get("/ok", func(c fiber.Ctx) error { return c.SendString("OK") }) } func customKeyFunc() jwt.Keyfunc { return func(t *jwt.Token) (interface{}, error) { // Always check the signing method if t.Method.Alg() != jwtware.HS256 { return nil, fmt.Errorf("Unexpected jwt signing method=%v", t.Header["alg"]) } // TODO custom implementation of loading signing key like from a database signingKey := "secret" return []byte(signingKey), nil } } ``` --- ## LoadShed ![Release](https://img.shields.io/github/v/tag/gofiber/contrib?filter=*loadshed*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://github.com/gofiber/contrib/workflows/Test%20Loadshed/badge.svg) The LoadShed middleware for [Fiber](https://github.com/gofiber/fiber) is designed to help manage server load by shedding requests based on certain load criteria. **Compatible with Fiber v3.** ### Go version support We only support the latest two versions of Go. Visit [https://go.dev/doc/devel/release](https://go.dev/doc/devel/release) for more information. ### Install ```sh go get -u github.com/gofiber/fiber/v3 go get -u github.com/gofiber/contrib/v3/loadshed ``` ### Signatures ```go loadshed.New(config ...loadshed.Config) fiber.Handler ``` ### Examples To use the LoadShed middleware in your Fiber application, import it and apply it to your Fiber app. Here's an example: #### Basic ```go package main import ( "time" "github.com/gofiber/fiber/v3" loadshed "github.com/gofiber/contrib/v3/loadshed" ) func main() { app := fiber.New() // Configure and use LoadShed middleware app.Use(loadshed.New(loadshed.Config{ Criteria: &loadshed.CPULoadCriteria{ LowerThreshold: 0.75, // Set your own lower threshold UpperThreshold: 0.90, // Set your own upper threshold Interval: 10 * time.Second, Getter: &loadshed.DefaultCPUPercentGetter{}, }, })) app.Get("/", func(c fiber.Ctx) error { return c.SendString("Welcome!") }) app.Listen(":3000") } ``` #### With a custom rejection handler ```go package main import ( "time" "github.com/gofiber/fiber/v3" loadshed "github.com/gofiber/contrib/v3/loadshed" ) func main() { app := fiber.New() // Configure and use LoadShed middleware app.Use(loadshed.New(loadshed.Config{ Criteria: &loadshed.CPULoadCriteria{ LowerThreshold: 0.75, // Set your own lower threshold UpperThreshold: 0.90, // Set your own upper threshold Interval: 10 * time.Second, Getter: &loadshed.DefaultCPUPercentGetter{}, }, OnShed: func(ctx fiber.Ctx) error { if ctx.Method() == fiber.MethodGet { return ctx. Status(fiber.StatusTooManyRequests). Send([]byte{}) } return ctx. Status(fiber.StatusTooManyRequests). JSON(fiber.Map{ "error": "Keep calm", }) }, })) app.Get("/", func(c fiber.Ctx) error { return c.SendString("Welcome!") }) app.Listen(":3000") } ``` ### Config The LoadShed middleware in Fiber offers various configuration options to tailor the load shedding behavior according to the needs of your application. | Property | Type | Description | Default | |:---------|:---------------------------|:--------------------------------------------------------|:------------------------| | Next | `func(fiber.Ctx) bool` | Function to skip this middleware when returned true. | `nil` | | Criteria | `LoadCriteria` | Interface for defining load shedding criteria. | `&CPULoadCriteria{...}` | | OnShed | `func(c fiber.Ctx) error` | Function to be executed if a request should be declined | `nil` | ### LoadCriteria LoadCriteria is an interface in the LoadShed middleware that defines the criteria for determining when to shed load in the system. Different implementations of this interface can use various metrics and algorithms to decide when and how to shed incoming requests to maintain system performance. #### CPULoadCriteria `CPULoadCriteria` is an implementation of the `LoadCriteria` interface, using CPU load as the metric for determining whether to shed requests. ##### Properties | Property | Type | Description | |:---------------|:-------------------|:--------------------------------------------------------------------------------------------------------------------------------------| | LowerThreshold | `float64` | The lower CPU usage threshold as a fraction (0.0 to 1.0). Requests are considered for shedding when CPU usage exceeds this threshold. | | UpperThreshold | `float64` | The upper CPU usage threshold as a fraction (0.0 to 1.0). All requests are shed when CPU usage exceeds this threshold. | | Interval | `time.Duration` | The time interval over which the CPU usage is averaged for decision making. | | Getter | `CPUPercentGetter` | Interface to retrieve CPU usage percentages. | ##### How It Works `CPULoadCriteria` determines the load on the system based on CPU usage and decides whether to shed incoming requests. It operates on the following principles: - **CPU Usage Measurement**: It measures the CPU usage over a specified interval. - **Thresholds**: Utilizes `LowerThreshold` and `UpperThreshold` values to decide when to start shedding requests. - **Proportional Rejection Probability**: - **Below `LowerThreshold`**: No requests are rejected, as the system is considered under acceptable load. - **Between `LowerThreshold` and `UpperThreshold`**: The probability of rejecting a request increases as the CPU usage approaches the `UpperThreshold`. This is calculated using the formula: ```plaintext rejectionProbability := (cpuUsage - LowerThreshold*100) / (UpperThreshold - LowerThreshold) ``` - **Above `UpperThreshold`**: All requests are rejected to prevent system overload. This mechanism ensures that the system can adaptively manage its load, maintaining stability and performance under varying traffic conditions. ### Default Config This is the default configuration for `LoadCriteria` in the LoadShed middleware. ```go var ConfigDefault = Config{ Next: nil, Criteria: &CPULoadCriteria{ LowerThreshold: 0.90, // 90% CPU usage as the start point for considering shedding UpperThreshold: 0.95, // 95% CPU usage as the point where all requests are shed Interval: 10 * time.Second, // CPU usage is averaged over 10 seconds Getter: &DefaultCPUPercentGetter{}, // Default method for getting CPU usage }, OnShed: nil, } ``` --- ## Monitor ![Release](https://img.shields.io/github/v/tag/gofiber/contrib?filter=*monitor*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://github.com/gofiber/contrib/workflows/Test%20Monitor/badge.svg) Monitor is a Fiber middleware that exposes real-time process, Go runtime, system, and HTTP service metrics through a built-in dashboard and JSON snapshot. The v3 monitor dashboard has been redesigned with an embedded, dependency-free UI and richer runtime and system metrics. Existing `monitor.New(config...)` usage remains source compatible. Monitor does not persist metrics, run a background collection loop, or replace Prometheus, OpenTelemetry, or an APM. **Compatible with Fiber v3.** ### Preview ![Monitor dashboard preview](https://raw.githubusercontent.com/gofurry/images/refs/heads/main/github/stats/dark.png) ### Install ```sh go get github.com/gofiber/contrib/v3/monitor ``` ### Signature ```go monitor.New(config ...monitor.Config) fiber.Handler ``` ### Usage The existing route-mounted form continues to serve HTML and JSON at whichever path you choose: ```go app.Get("/metrics", monitor.New()) ``` Request the same endpoint with `Accept: application/json` to receive the raw snapshot. A route-only mount does not see application traffic, so its HTTP request metrics remain empty. To collect HTTP metrics for the whole application, mount one monitor instance app-wide and use `Next` to pass non-monitor requests downstream: ```go package main import ( "github.com/gofiber/contrib/v3/monitor" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/log" "github.com/gofiber/fiber/v3/middleware/recover" ) func main() { app := fiber.New() app.Use(monitor.New(monitor.Config{ Next: func(c fiber.Ctx) bool { return c.Path() != "/metrics" }, })) app.Get("/", func(c fiber.Ctx) error { return c.SendString("Hello, World!") }) log.Fatal(app.Listen(":3000")) } ``` Open `http://localhost:3000/metrics`. Requests for the monitor endpoint itself are not included in the HTTP metrics. For production deployments, protect the endpoint with authentication, network restrictions, or a reverse proxy. Place endpoint-specific authentication before monitor: ```go app.Use("/metrics", authMiddleware) app.Use(monitor.New(monitor.Config{ Next: func(c fiber.Ctx) bool { return c.Path() != "/metrics" }, })) ``` When the application uses Fiber's recover middleware, place it after monitor so recovered panics are observed as `5xx` responses: ```go app.Use( monitor.New(monitor.Config{ Next: func(c fiber.Ctx) bool { return c.Path() != "/metrics" }, }), recover.New(), ) ``` For instrumented requests that return an error, monitor invokes the application's configured Fiber `ErrorHandler` before recording the response. This makes status metrics match the response sent to the client, including custom error statuses. The error is therefore consumed at the monitor layer and does not propagate to middleware mounted before it. ### Config ```go app.Use(monitor.New(monitor.Config{ Next: func(c fiber.Ctx) bool { return c.Path() != "/metrics" }, Title: "My Service", Description: "Live runtime and HTTP statistics.", Footer: "Internal operations dashboard.", FaviconURL: "/assets/favicon.svg", Refresh: 3 * time.Second, })) ``` | Field | Type | Default | Description | | ---------------------- | ---------------------- | ------------------- | ----------- | | `Next` | `func(fiber.Ctx) bool` | `nil` | Passes matching requests downstream and includes them in HTTP metrics. A false result serves monitor on the current path. | | `Title` | `string` | `Fiber Monitor` | Document title and dashboard heading. | | `Description` | `string` | See `ConfigDefault` | Text displayed below the heading. | | `Footer` | `string` | See `ConfigDefault` | Text displayed at the bottom of the page. | | `FaviconURL` | `string` | Built-in SVG | Root-relative path or absolute HTTP(S) URL. | | `Refresh` | `time.Duration` | `3s` | Browser polling interval and server snapshot cache TTL; values below `1s` are clamped. | | `APIOnly` | `bool` | `false` | Returns JSON regardless of the request `Accept` header. | | `EnableGCPauseMetrics` | `bool` | `false` | Enables exact last/window/total GC pause metrics through `runtime.ReadMemStats`, which briefly stops the Go runtime. | | `CustomHead` | `string` | Deprecated | Retained for source compatibility and ignored by the embedded dashboard. | | `FontURL` | `string` | Deprecated | Retained for source compatibility; no external font is loaded. | | `ChartJSURL` | `string` | Deprecated | Retained for source compatibility; charts use the built-in Canvas implementation. | Default runtime metrics use `runtime/metrics` and avoid the explicit stop-the-world snapshot performed by `runtime.ReadMemStats`. Applications that need exact GC pause totals and the GC Pause trend can opt in: ```go app.Use(monitor.New(monitor.Config{ Next: func(c fiber.Ctx) bool { return c.Path() != "/metrics" }, EnableGCPauseMetrics: true, })) ``` ### JSON Request the monitor endpoint with an explicit JSON accept header: ```sh curl -H "Accept: application/json" http://localhost:3000/metrics ``` The snapshot contains these groups: ```json { "collected_at": "2026-08-20T00:00:00Z", "collection": { "partial": false, "errors": [] }, "process": { "cpu_percent": 1.25, "rss_bytes": 67108864, "tcp_connections": 12, "uptime_seconds": 3600 }, "runtime": { "goroutines": 8 }, "system": { "cpu_percent": 25.5, "memory_used_bytes": 8589934592, "memory_total_bytes": 17179869184, "load1": 0.75, "tcp_connections": 42 }, "http": { "requests": 128 }, "pid": { "cpu": 1.25, "ram": 67108864, "conns": 12, "goroutines": 8, "requests": "128", "uptime": 3600 }, "os": { "cpu": 25.5, "ram": 8589934592, "total_ram": 17179869184, "load_avg": 0.75, "conns": 42 } } ``` Unsupported, failed, and not-yet-available window metrics are encoded as `null`, not as a synthetic zero. Collection failures expose stable identifiers such as `system.disk`; raw operating-system errors and paths are not returned. #### Migration notes Existing `monitor.New(config...)` usage remains source compatible. The `process` / `runtime` / `system` / `http` structure remains the primary JSON representation. The response also includes legacy `pid` / `os` compatibility views derived from those primary values: | Legacy field | Primary field | | ------------ | ------------- | | `pid.cpu` | `process.cpu_percent` | | `pid.ram` | `process.rss_bytes` | | `pid.conns` | `process.tcp_connections` | | `pid.goroutines` | `runtime.goroutines` | | `pid.requests` (string) | `http.requests` | | `pid.uptime` | `process.uptime_seconds` | | `os.cpu` | `system.cpu_percent` | | `os.ram` | `system.memory_used_bytes` | | `os.total_ram` | `system.memory_total_bytes` | | `os.load_avg` | `system.load1` | | `os.conns` | `system.tcp_connections` | Legacy numeric fields keep their original non-null JSON types and use zero when a nullable primary value is unavailable; the primary field remains `null` and `collection.errors` reports collection failures. Route-mounted monitor instances still serve the dashboard and JSON snapshot, but application HTTP metrics require the app-wide `Next` setup shown above. Monitor endpoint requests are no longer counted as application traffic. ### Metrics | Group | Metrics | | ---------- | ------- | | Process | CPU, RSS, threads, file descriptors/handles, TCP connections, runtime since monitor initialization | | Go runtime | Goroutines, heap allocation/system/in-use/idle/released memory, heap objects, Next GC, mallocs/frees, GOMAXPROCS, GC count, optional last/window/total GC pause, GC CPU fraction | | System | CPU, used/available/total memory, application-filesystem usage/type/free space, 1/5/15-minute load averages, aggregate network rates, TCP connections | | HTTP | Requests, in-flight requests, 1xx–5xx status classes, RPS, 4xx/5xx rates, P50/P95/P99 latency | CPU, network, request-rate, status-rate, and latency values need two collection windows. Their first snapshot is `null`. Exact GC pause metrics are `null` unless `EnableGCPauseMetrics` is true; when enabled, the first window pause is also `null` while its baseline is established. While they are disabled, the GC Pause trend explains how to enable them. Snapshots are collected only when JSON is requested and are shared within the configured refresh TTL. The dashboard keeps at most 90 trend samples in browser memory and displays 60 by default. The 30/60/90 selector changes only the visible browser history and remembers the choice in local storage. The dashboard stops polling while its browser tab is hidden and refreshes as soon as the tab is visible again, so a forgotten tab does not keep triggering collections. A snapshot request that takes longer than the refresh interval, or 10 seconds if that is longer, is aborted and the dashboard reports its data as stale. A `PARTIAL` status lists the metric groups that could not be collected in its tooltip. The eight trend panels cover CPU, memory, network, goroutines, requests per second, HTTP latency, HTTP error rates, and GC pauses. Heap, GC, application-filesystem, and HTTP status-code buttons open detailed views without triggering extra collection. Disk details never expose the filesystem path, mountpoint, or device name. ### Performance Run all benchmarks with: ```sh go test -run='^$' -bench=. -benchmem ``` The cold-collection benchmarks include platform-specific gopsutil calls and are expected to vary between operating systems and machines. Results are intended for regression tracking, not as production throughput guarantees. ### Security > This middleware exposes operational information and should be treated as a > protected administrative endpoint. Monitor does not implement authentication. Protect it with Fiber middleware, a reverse proxy, a private network, VPN, or firewall. Monitor records aggregate counters only. It does not collect request or response bodies, headers, cookies, authorization values, query strings, route parameters, client IPs, user identifiers, environment variables, command lines, filesystem paths, or device names. ### Notes - Each `New` call owns independent counters, histogram, cache, and sampling baselines. - Middleware instances are safe for concurrent use after construction. - The business-request hot path uses time reads and atomic operations; system and runtime collection is not performed there. - The embedded dashboard has no external JavaScript, CSS, font, or chart dependency. - It initially follows the browser color scheme and provides a persisted Light/Dark toggle. - Detail views are native modal dialogs, charts expose their latest values to assistive technologies, and on touch screens a tap or horizontal drag on a chart shows its values. - Linux, macOS, and Windows are supported with graceful degradation when a metric is unavailable. Load averages are intentionally unsupported on Windows because gopsutil's Windows implementation starts a background sampler. --- ## New Relic ![Release](https://img.shields.io/github/v/tag/gofiber/contrib?filter=*newrelic*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://github.com/gofiber/contrib/workflows/Test%20newrelic/badge.svg) [New Relic](https://github.com/newrelic/go-agent) support for Fiber. Only the headers the New Relic agent itself consumes are forwarded to New Relic transactions by default: the distributed tracing headers (`traceparent`, `tracestate`, `newrelic`), the synthetic monitor headers (`X-NewRelic-Synthetics`, `X-NewRelic-Synthetics-Info`) and the queue timing headers (`X-Request-Start`, `X-Queue-Start`). Any other header, including W3C `baggage`, has to be opted into with `RequestHeaderFilter`. The default is exported as `DefaultRequestHeaderFilter`, so a custom filter can build on top of it instead of restating the allowlist. **Compatible with Fiber v3.** ### Go version support We only support the latest two versions of Go. Visit [https://go.dev/doc/devel/release](https://go.dev/doc/devel/release) for more information. ### Install ```sh go get -u github.com/gofiber/fiber/v3 go get -u github.com/gofiber/contrib/v3/newrelic ``` ### Signature ```go middleware.New(config middleware.Config) fiber.Handler middleware.FromContext(ctx any) *nr.Transaction // nr "github.com/newrelic/go-agent/v3/newrelic" middleware.DefaultRequestHeaderFilter(key, value string) bool ``` `FromContext` accepts a `fiber.Ctx`, `fiber.CustomCtx`, `*fasthttp.RequestCtx`, or a standard `context.Context` (e.g. the value returned by `c.Context()` when `PassLocalsToContext` is enabled). It returns an `*nr.Transaction` (a New Relic transaction from `github.com/newrelic/go-agent/v3/newrelic`). ### Config | Property | Type | Description | Default | |:-----------------------|:-----------------|:------------------------------------------------------------|:--------------------------------| | License | `string` | Required - New Relic License Key | `""` | | AppName | `string` | New Relic Application Name | `fiber-api` | | Enabled | `bool` | Enable/Disable New Relic | `false` | | ~~TransportType~~ | ~~`string`~~ | ~~Can be HTTP or HTTPS~~ (Deprecated) | ~~`"HTTP"`~~ | | Application | `Application` | Existing New Relic App | `nil` | | ErrorStatusCodeHandler | `func(c fiber.Ctx, err error) int` | If you want to change newrelic status code, you can use it. | `DefaultErrorStatusCodeHandler` | | Next | `func(c fiber.Ctx) bool` | Next defines a function to skip this middleware when returned true. | `nil` | | RequestHeaderFilter | `func(key, value string) bool` | Return `true` to forward a request header to New Relic, `false` to skip it. | `DefaultRequestHeaderFilter` (distributed tracing, synthetics and queue timing headers only) | ### Usage ```go package main import ( "github.com/gofiber/fiber/v3" middleware "github.com/gofiber/contrib/v3/newrelic" ) func main() { app := fiber.New() app.Get("/", func(ctx fiber.Ctx) error { return ctx.SendStatus(200) }) cfg := middleware.Config{ License: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", AppName: "MyCustomApi", Enabled: true, } app.Use(middleware.New(cfg)) app.Listen(":8080") } ``` ### Usage with existing New Relic application ```go package main import ( "github.com/gofiber/fiber/v3" middleware "github.com/gofiber/contrib/v3/newrelic" nr "github.com/newrelic/go-agent/v3/newrelic" ) func main() { nrApp, err := nr.NewApplication( nr.ConfigAppName("MyCustomApi"), nr.ConfigLicense("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), nr.ConfigEnabled(true), ) app := fiber.New() app.Get("/", func(ctx fiber.Ctx) error { return ctx.SendStatus(200) }) app.Get("/foo", func(ctx fiber.Ctx) error { txn := middleware.FromContext(ctx) segment := txn.StartSegment("foo segment") defer segment.End() // do foo return nil }) cfg := middleware.Config{ Application: nrApp, } app.Use(middleware.New(cfg)) app.Listen(":8080") } ``` ### Retrieving the transaction with PassLocalsToContext When `fiber.Config{PassLocalsToContext: true}` is set, the New Relic transaction stored by the middleware is also available in the underlying `context.Context`. Use `FromContext` with any of the supported context types: ```go // From a fiber.Ctx (most common usage) txn := middleware.FromContext(c) // From the underlying context.Context (useful in service layers or when PassLocalsToContext is enabled) txn := middleware.FromContext(c.Context()) ``` --- ## OPA ![Release](https://img.shields.io/github/v/tag/gofiber/contrib?filter=*opa*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://github.com/gofiber/contrib/workflows/Test%20opa/badge.svg) [Open Policy Agent](https://github.com/open-policy-agent/opa) support for Fiber. **Compatible with Fiber v3.** ### Go version support We only support the latest two versions of Go. Visit [https://go.dev/doc/devel/release](https://go.dev/doc/devel/release) for more information. ### Install ```sh go get -u github.com/gofiber/fiber/v3 go get -u github.com/gofiber/contrib/v3/opa ``` ### Signature ```go opa.New(config opa.Config) fiber.Handler ``` ### Config | Property | Type | Description | Default | |:----------------------|:--------------------|:-------------------------------------------------------------|:--------------------------------------------------------------------| | RegoQuery | `string` | Required - Rego query | - | | RegoPolicy | `io.Reader` | Required - Rego policy | - | | IncludeQueryString | `bool` | Include query string as input to rego policy | `false` | | DeniedStatusCode | `int` | Http status code to return when policy denies request | `400` | | DeniedResponseMessage | `string` | Http response body text to return when policy denies request | `""` | | IncludeHeaders | `[]string` | Include headers as input to rego policy | - | | InputCreationMethod | `InputCreationFunc` | Use your own function to provide input for OPA | `func defaultInput(ctx fiber.Ctx) (map[string]interface{}, error)` | ### Types ```go type InputCreationFunc func(c fiber.Ctx) (map[string]interface{}, error) ``` ### Usage OPA Fiber middleware sends the following example data to the policy engine as input: ```json { "method": "GET", "path": "/somePath", "query": { "name": ["John Doe"] }, "headers": { "Accept": "application/json", "Content-Type": "application/json" } } ``` ```go package main import ( "bytes" "github.com/gofiber/fiber/v3" "github.com/gofiber/contrib/v3/opa" ) func main() { app := fiber.New() module := ` package example.authz default allow := false allow if { input.method == "GET" } ` cfg := opa.Config{ RegoQuery: "data.example.authz.allow", RegoPolicy: bytes.NewBufferString(module), IncludeQueryString: true, DeniedStatusCode: fiber.StatusForbidden, DeniedResponseMessage: "status forbidden", IncludeHeaders: []string{"Authorization"}, InputCreationMethod: func(ctx fiber.Ctx) (map[string]interface{}, error) { return map[string]interface{}{ "method": ctx.Method(), "path": ctx.Path(), }, nil }, } app.Use(opa.New(cfg)) app.Get("/", func(ctx fiber.Ctx) error { return ctx.SendStatus(200) }) app.Listen(":8080") } ``` --- ## OTel ![Release](https://img.shields.io/github/v/tag/gofiber/contrib?filter=*otel*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://github.com/gofiber/contrib/workflows/Test%20otel/badge.svg) [OpenTelemetry](https://opentelemetry.io/) support for Fiber. The middleware records a server span and the HTTP server metrics of the [semantic conventions](https://opentelemetry.io/docs/specs/semconv/http/) (v1.43.0) for every request, continues the trace its caller propagated, and hands the trace context to your handlers and back to the client. This package is listed on the [OpenTelemetry Registry](https://opentelemetry.io/registry/instrumentation-go-fiber/). **Compatible with Fiber v3.** ### Go version support We only support the latest two versions of Go. Visit [https://go.dev/doc/devel/release](https://go.dev/doc/devel/release) for more information. ### Install ```sh go get -u github.com/gofiber/contrib/v3/otel ``` ### Signature ```go otel.New(opts ...otel.Option) fiber.Handler ``` ### Usage ```go app := fiber.New() app.Use(recover.New()) // outside: answers panics, after they are recorded app.Use(fiberotel.New()) // before the routes and the middleware that use the span app.Get("/users/:id", func(c fiber.Ctx) error { // c.Context() carries the request's span: pass it on to anything you trace. user, err := store.Get(c.Context(), c.Params("id")) ... }) ``` The middleware reports to the global tracer provider, meter provider and propagator unless options name others, so set them up once at startup - see the [example](#example). **Set a propagator**: until `otel.SetTextMapPropagator` is called, the global one propagates nothing, and a service never joins the traces of its callers. Where you mount it matters: - **Recovery middleware goes before it**, so that a panic passes through this middleware - which records it - before being answered. - **Middleware that use the span go after it**: a logger writing trace IDs, or anything starting child spans from `c.Context()`. - **It answers errors itself.** To record the status the client receives, the middleware runs the application's error handler on an error returned by the handler chain - as Fiber's logger does - and returns `nil`. Middleware mounted before it therefore see no error; mount error-reporting middleware after it, or read the error from the span. ### Config You can configure the middleware using functional parameters. | Function | Argument Type | Description | Default | | :--- | :--- | :--- | :--- | | `WithNext` | `func(fiber.Ctx) bool` | Skips the middleware for a request when the function returns true. | `nil` | | `WithTracerProvider` | `oteltrace.TracerProvider` | The tracer provider spans are created with. With the [no-op provider](https://pkg.go.dev/go.opentelemetry.io/otel/trace/noop), no span is started at all, but the caller's trace context still reaches your handlers and the response. | the global provider | | `WithMeterProvider` | `otelmetric.MeterProvider` | The meter provider metrics are recorded with. | the global provider | | `WithoutMetrics` | `bool` | Disables metrics collection when set to true. | `false` | | `WithPropagators` | `propagation.TextMapPropagator` | The propagators that extract the caller's trace context from the request, and inject the request's into the response. | the global propagator | | `WithResponsePropagators` | `propagation.TextMapPropagator` | The propagators that inject the trace context into the response. `propagation.TraceContext{}` keeps baggage out of responses; `propagation.NewCompositeTextMapPropagator()` sends no trace context at all. | the `WithPropagators` ones | | `WithTraceResponseHeader` | `string` | A response header that carries the request's trace ID, such as `X-Trace-Id`. | none | | `WithPublicEndpoint` | - | Treats every request as coming from a client you do not control: its span starts a new trace and links to the caller's span context instead of continuing it. | off | | `WithPublicEndpointFn` | `func(fiber.Ctx) bool` | Decides per request, before the span starts, whether it comes from a public endpoint. | `nil` | | `WithSpanStartOptions` | `...oteltrace.SpanStartOption` | Options added to every server span when it starts, such as links or attributes. | none | | `WithSpanNameFormatter` | `func(fiber.Ctx) string` | Names the span once the handler chain has run, when the span is recording. | `{method} {route}`, or `{method}` when no route matched | | `WithPort` | `int` | The value of `server.port` on spans and metrics. | spans: the port the `Host` header names; metrics: none | | `WithClientIP` | `bool` | Whether to record `client.address`, `network.peer.address` and `network.peer.port`. | `true` | | `WithCapturedRequestHeaders` | `...string` | Request headers to record on the span as `http.request.header.`. | none | | `WithCapturedResponseHeaders` | `...string` | Response headers to record on the span as `http.response.header.`. | none | | `WithRedactedQueryParams` | `...string` | Query parameters whose values `url.query` redacts, in addition to the [sensitive ones](#query-redaction). | none | | `WithCustomAttributes` | `func(fiber.Ctx) []attribute.KeyValue` | Adds attributes to the span when it starts, where samplers see them. | `nil` | | `WithCustomResponseAttributes` | `func(fiber.Ctx) []attribute.KeyValue` | Adds span attributes after the handler chain has run, when the span is recording. | `nil` | | `WithCustomMetricAttributes` | `func(fiber.Ctx) []attribute.KeyValue` | Adds attributes to all metrics of a request, resolved when it starts. | `nil` | | `WithCustomResponseMetricAttributes` | `func(fiber.Ctx) []attribute.KeyValue` | Adds attributes to the duration and body size metrics after the handler chain has run. | `nil` | | (⚠️ **Deprecated**) `WithCollectClientIP` | `bool` | Deprecated alias for `WithClientIP`. | `true` | | (❌ **Removed**) `WithServerName` | `string` | This option was removed because the `http.server_name` attribute is deprecated in the OpenTelemetry semantic conventions. The recommended attribute is `server.address`, which this middleware already fills with the hostname reported by Fiber. | - | Values read from `fiber.Ctx` - `Params`, `Get`, `GetRespHeader` and the like - are only valid during the request, while spans are exported and metric attribute sets kept after it. Copy strings the callbacks return with `utils.CopyString`, unless `fiber.Config.Immutable` is enabled. Keep metric attributes low-cardinality: each distinct attribute set is a metric series. ### Traces Every request the middleware does not skip gets a span of kind `SERVER`. It continues the trace context the configured propagators extract from the request, unless the request comes from a [public endpoint](#public-endpoints), and is handed to your handlers through `c.Context()`. #### Span name and status The span is named `{method} {route}` after the [route that matched](#routes), for example `GET /users/:id`, or after the method alone when no route matched. The route is only known once Fiber's router has run, so the span starts named after the method; samplers see `url.path` and the other attributes below. The span status is left unset for 1xx to 4xx responses - a 4xx is the client's error, not the server's - and set to `Error` for 5xx responses and codes outside `[100, 600)`. An error returned by the handler chain is recorded on the span as an `exception` event. #### Attributes | Attribute | When it is set | | :--- | :--- | | `http.request.method`, `url.scheme`, `url.path`, `server.address`, `network.protocol.name`, `network.protocol.version` | always | | `network.transport` | `tcp`, or `unix` on a Unix socket | | `server.port` | from `WithPort`, or else when the `Host` header names a port | | `url.query` | when the request has a query string; see [query redaction](#query-redaction) | | `user_agent.original` | when the request has a `User-Agent` header | | `client.address` | under `WithClientIP` (default); the peer, or the client a trusted proxy reports | | `network.peer.address`, `network.peer.port` | under `WithClientIP`; the other end of the connection | | `http.request.body.size` | when the request body size is [known](#body-sizes) | | `enduser.id` | the username of a `Basic` `Authorization` header | | `http.request.header.` | for the headers named by `WithCapturedRequestHeaders` | | `http.response.status_code` | once the response is decided | | `http.route` | when a route matched | | `error.type` | the status code, when it makes the span an error; `panic` or `response_callback_panic`, see [panics](#panics) | | `http.response.body.size` | when the response body size is [known](#body-sizes) | | `http.response.header.` | for the headers named by `WithCapturedResponseHeaders` | Everything known before the handler runs is set when the span starts, so that samplers can see it, as the semantic conventions ask. `url.scheme`, `server.address` and `client.address` honor `X-Forwarded-Proto`, `X-Forwarded-Host` and the configured `ProxyHeader` only from a proxy Fiber trusts - see `fiber.Config.TrustProxy`. #### Query redaction `url.query` holds the query string as the client sent it, except for the values of the parameters the semantic conventions name as sensitive - the signatures and credentials of pre-signed S3 and GCS URLs, which grant access to whoever holds them - which are replaced by `REDACTED`: `AWSAccessKeyId`, `Signature`, `sig`, `X-Goog-Signature`, `X-Amz-Signature`, `X-Amz-Credential` and `X-Amz-Security-Token`. Add your own with `WithRedactedQueryParams`: ```go app.Use(fiberotel.New(fiberotel.WithRedactedQueryParams("token", "api_key"))) // GET /download?token=s3cr3t&page=2 → url.query="token=REDACTED&page=2" ``` Names are matched as Fiber decodes them, so `s%69g` counts as `sig`, and case-sensitively, as the semantic conventions specify. #### Captured headers No header is recorded unless you name it, since headers often carry credentials and personal data: ```go app.Use(fiberotel.New( fiberotel.WithCapturedRequestHeaders("X-Request-Id", "Accept-Language"), fiberotel.WithCapturedResponseHeaders("X-Cache"), )) // http.request.header.x-request-id=["4f2a…"], http.response.header.x-cache=["HIT"] ``` Names are matched case-insensitively and recorded lowercased, each with every value the header has. #### Public endpoints A service exposed to clients it does not control should not let them decide which trace its spans join - or, under a parent-based sampler, whether they are recorded at all. With `WithPublicEndpoint`, the span of every request starts a new trace and links to the span context the caller sent, if any. Use `WithPublicEndpointFn` to decide per request, for instance to trust callers on your internal network: ```go app.Use(fiberotel.New(fiberotel.WithPublicEndpointFn(func(c fiber.Ctx) bool { return !strings.HasPrefix(c.IP(), "10.") }))) ``` Under the no-op tracer provider there is no new trace to start, so the caller's span context is simply not handed on. ### Metrics | Metric | Instrument | Unit | Attributes | | :--- | :--- | :--- | :--- | | `http.server.request.duration` | Float64Histogram | `s` | `http.request.method`, `url.scheme`, `network.protocol.name`, `network.protocol.version`, `http.response.status_code`, `http.route`, `error.type` | | `http.server.request.body.size` | Int64Histogram | `By` | same as above | | `http.server.response.body.size` | Int64Histogram | `By` | same as above | | `http.server.active_requests` | Int64UpDownCounter | `{request}` | `http.request.method`, `url.scheme` | `server.port` is added to all four when `WithPort` configures it, and the `WithCustomMetricAttributes` and `WithCustomResponseMetricAttributes` attributes as described above. The duration histogram uses the bucket boundaries the semantic conventions advise. Measurements are recorded in the request's span context, so an SDK configured for exemplars links histogram samples to the traces of the requests they came from. `server.address` is on spans only: the semantic conventions make it opt-in on metrics because it comes from the `Host` header, so every value a client invented would open a new series. An application that knows its hosts can add it back: ```go fiberotel.WithCustomMetricAttributes(func(c fiber.Ctx) []attribute.KeyValue { return []attribute.KeyValue{semconv.ServerAddress(utils.CopyString(c.Hostname()))} }) ``` `http.server.request.duration`, like the span, runs from when the middleware is reached until the request's telemetry ends, so it covers the middleware and handlers mounted after this one. #### Routes `http.route` is the pattern of the route that matched the request, including group prefixes - `/api/users/:id` - and it is left out when no route matched: a 404, a 405, and a request answered by an `app.Use` handler, such as `static.New`. Fiber leaves such a request on the last `Use` middleware it passed, usually this one at `/`, and reporting that would file every unmatched request under the root route. One case is attributed to the wrong pattern: if the matched handler falls through with `c.Next()` into a trailing `app.Use` middleware, Fiber has already replaced the route with that middleware's mount path by the time this one records it. Avoid falling through into a trailing `app.Use` if you need exact routes. A request fasthttp rejects before routing - a body over `BodyLimit`, oversized headers, a read timeout - is answered by Fiber's server error handler, which runs the `Use` chain again and writes the real status only afterwards; this middleware records such a request with no route and a status of 200. #### Body sizes `http.server.request.body.size` and `http.server.response.body.size` record a payload only when its size is known, and are left out otherwise rather than recorded as zero bytes, which would drag the reported percentiles down. That is the case for a stream of unannounced length, such as `c.SendStreamWriter` or `c.SendStream` without a size, whose length is only known once fasthttp has written it. A pre-parsed multipart form is measured by its `Content-Length`, since reading the body back would re-marshal every uploaded file into memory. Under `fiber.Config{StreamRequestBody: true}`, request bodies reach the handler as streams and are left out of `http.server.request.body.size`: fasthttp pre-reads a bounded prefix and hands the rest to the handler, which may never read it, so the announced `Content-Length` is a client's claim rather than a measurement. A response that carries no body on the wire records zero, however much the handler wrote: a `HEAD`, a status that forbids a body (`1xx`, `204`, `304`), or a handler that sets `c.Response().SkipBody` itself. #### Disabling metrics `WithoutMetrics(true)` turns them off entirely. With a no-op meter provider, or SDK views that drop all four instruments, a request skips all metric work as well. Likewise, with the no-op tracer provider, the middleware starts no span and only propagates the caller's trace context. ### Context propagation The span's context is set as the request's `c.Context()`, so anything traced from a handler becomes a child of the server span, and outgoing calls carry the trace. When the request is done, that context is canceled - like a `net/http` request context - and the middleware around this one gets back the context it passed in. A streamed response is the exception, as long as the context the request arrived with can never be canceled, which is Fiber's default: fasthttp writes the body after the handler has returned - the writer of `c.SendStreamWriter`, for instance, or the readers behind `c.SendStream`, `c.SendFile` and the static middleware - so the context is left to the garbage collector instead of being canceled under them. Under a cancelable incoming context it is canceled as usual, since a child left uncanceled would stay registered with that context until it ends. The trace context is injected into the response headers too, by the `WithPropagators` propagators unless `WithResponsePropagators` names others. To expose the trace ID under a header of your own, for instance so a support request can quote it: ```go app.Use(fiberotel.New( fiberotel.WithTraceResponseHeader("X-Trace-Id"), // Optionally, keep the propagation headers - and baggage - out of responses. fiberotel.WithResponsePropagators(propagation.NewCompositeTextMapPropagator()), )) ``` ### Error handling The middleware runs the application's error handler on an error returned by the handler chain, so that the recorded status is the one the client receives, and returns `nil`. It picks the handler Fiber would: a mounted sub-app's own `ErrorHandler` for its routes, and a 500 when the error handler itself fails. #### Panics A panic in the handler chain is not recovered: it continues, unchanged, to the recovery middleware mounted outside this one. On its way out it still ends the request's telemetry - active requests drop back, the duration and request size are recorded, the context is restored and canceled - and the span records the panic as an `exception` event, with the stack that raised it, an `Error` status and `error.type=panic`. The response status and size are left out of the span and the metrics, because the recovery middleware decides them after this one has returned. The trace headers are still set on the response. #### Response attribute callbacks Response attribute callbacks and the span name formatter run after the handler chain. If one of them panics, the original panic propagates to the application's recovery middleware, and the span and request metrics record `error.type=response_callback_panic`, the span with error status; a span name formatter that panicked is not called again, and the span keeps the default name. The final HTTP status and response body size are omitted because outer recovery and error handling have not run yet. Active-request accounting and context cleanup still complete. ### Example ```go package main import ( "context" "errors" "log" fiberotel "github.com/gofiber/contrib/v3/otel" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/recover" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.43.0" oteltrace "go.opentelemetry.io/otel/trace" ) var tracer = otel.Tracer("fiber-server") func main() { res := resource.NewWithAttributes(semconv.SchemaURL, semconv.ServiceName("my-service")) traceExporter, err := stdouttrace.New(stdouttrace.WithPrettyPrint()) if err != nil { log.Fatal(err) } tp := sdktrace.NewTracerProvider(sdktrace.WithBatcher(traceExporter), sdktrace.WithResource(res)) metricExporter, err := stdoutmetric.New() if err != nil { log.Fatal(err) } mp := sdkmetric.NewMeterProvider( sdkmetric.WithReader(sdkmetric.NewPeriodicReader(metricExporter)), sdkmetric.WithResource(res), ) otel.SetTracerProvider(tp) otel.SetMeterProvider(mp) otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) defer func() { _ = tp.Shutdown(context.Background()) _ = mp.Shutdown(context.Background()) }() app := fiber.New() app.Use(recover.New()) app.Use(fiberotel.New(fiberotel.WithTraceResponseHeader("X-Trace-Id"))) app.Get("/error", func(fiber.Ctx) error { return errors.New("abc") }) app.Get("/users/:id", func(c fiber.Ctx) error { id := c.Params("id") name := getUser(c.Context(), id) return c.JSON(fiber.Map{"id": id, "name": name}) }) if err := app.Listen(":3000"); err != nil { log.Print(err) } } func getUser(ctx context.Context, id string) string { _, span := tracer.Start(ctx, "getUser", oteltrace.WithAttributes(attribute.String("id", id))) defer span.End() if id == "123" { return "otel tester" } return "unknown" } ``` --- ## Paseto ![Release](https://img.shields.io/github/v/tag/gofiber/contrib?filter=*paseto*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://github.com/gofiber/contrib/workflows/Test%20paseto/badge.svg) PASETO returns a Web Token (PASETO) auth middleware. - For valid token, it sets the payload data in Ctx.Locals (and in the underlying `context.Context` when `PassLocalsToContext` is enabled) and calls next handler. - For invalid token, it returns "401 - Unauthorized" error. - For missing token, it returns "400 - BadRequest" error. **Compatible with Fiber v3.** ### Go version support We only support the latest two versions of Go. Visit [https://go.dev/doc/devel/release](https://go.dev/doc/devel/release) for more information. ### Install ```sh go get -u github.com/gofiber/fiber/v3 go get -u github.com/gofiber/contrib/v3/paseto go get -u github.com/o1egl/paseto ``` ### Signature ```go pasetoware.New(config ...pasetoware.Config) func(fiber.Ctx) error pasetoware.FromContext(ctx any) interface{} ``` `FromContext` accepts a `fiber.Ctx`, `fiber.CustomCtx`, `*fasthttp.RequestCtx`, or a standard `context.Context` (e.g. the value returned by `c.Context()` when `PassLocalsToContext` is enabled). ### Config | Property | Type | Description | Default | |:---------------|:--------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------------------------------| | Next | `func(fiber.Ctx) bool` | Defines a function to skip this middleware when it returns true. | `nil` | | SuccessHandler | `func(fiber.Ctx) error` | SuccessHandler defines a function which is executed for a valid token. | `c.Next()` | | ErrorHandler | `func(fiber.Ctx, error) error` | ErrorHandler defines a function which is executed for an invalid token. | `401 Invalid or expired PASETO` | | Validate | `PayloadValidator` | Defines a function to validate if payload is valid. Optional. In case payload used is created using `CreateToken` function. If token is created using another function, this function must be provided. | `nil` | | SymmetricKey | `[]byte` | Secret key to encrypt token. If present the middleware will generate local tokens. | `nil` | | PrivateKey | `ed25519.PrivateKey` | Secret key to sign the tokens. If present (along with its `PublicKey`) the middleware will generate public tokens. | `nil` | | PublicKey | `crypto.PublicKey` | Public key to verify the tokens. If present (along with `PrivateKey`) the middleware will generate public tokens. | `nil` | | Extractor | `Extractor` | Extractor defines a function to extract the token from the request. | `FromAuthHeader("Bearer")` | ### Available Extractors PASETO middleware uses the shared Fiber extractors (github.com/gofiber/fiber/v3/extractors) and provides several helpers for different token sources: Import them like this: ```go import "github.com/gofiber/fiber/v3/extractors" ``` For an overview and additional examples, see the Fiber Extractors guide: - https://docs.gofiber.io/guide/extractors - `extractors.FromAuthHeader(prefix string)` - Extracts token from the Authorization header using the given scheme prefix (e.g., "Bearer"). **This is the recommended and most secure method.** - `extractors.FromHeader(header string)` - Extracts token from the specified HTTP header - `extractors.FromQuery(param string)` - Extracts token from URL query parameters - `extractors.FromParam(param string)` - Extracts token from URL path parameters - `extractors.FromCookie(key string)` - Extracts token from cookies - `extractors.FromForm(param string)` - Extracts token from form data - `extractors.Chain(extrs ...extractors.Extractor)` - Tries multiple extractors in order until one succeeds #### Security Considerations ⚠️ **Security Warning**: When choosing an extractor, consider the security implications: - **URL-based extractors** (`FromQuery`, `FromParam`): Tokens can leak through server logs, browser referrer headers, proxy logs, and browser history. Use only for development or when security is not a primary concern. - **Form-based extractors** (`FromForm`): Similar risks to URL extractors, especially if forms are submitted via GET requests. - **Header-based extractors** (`FromAuthHeader`, `FromHeader`): Most secure as headers are not typically logged or exposed in referrers. - **Cookie-based extractors** (`FromCookie`): Secure for web applications but requires proper cookie security settings (HttpOnly, Secure, SameSite). **Recommendation**: Use `FromAuthHeader("Bearer")` (the default) for production applications unless you have specific requirements that necessitate alternative extractors. ### Migration from TokenPrefix If you were previously using `TokenPrefix`, you can now use `extractors.FromAuthHeader` with the prefix: ```go // Old way pasetoware.New(pasetoware.Config{ SymmetricKey: []byte("secret"), TokenPrefix: "Bearer", }) // New way pasetoware.New(pasetoware.Config{ SymmetricKey: []byte("secret"), Extractor: extractors.FromAuthHeader("Bearer"), }) ``` ### Examples Below have a list of some examples that can help you start to use this middleware. In case of any additional example that doesn't show here, please take a look at the test file. #### SymmetricKey ```go package main import ( "time" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/extractors" pasetoware "github.com/gofiber/contrib/v3/paseto" ) const secretSymmetricKey = "symmetric-secret-key (size = 32)" func main() { app := fiber.New() // Login route app.Post("/login", login) // Unauthenticated route app.Get("/", accessible) // Paseto Middleware with local (encrypted) token apiGroup := app.Group("api", pasetoware.New(pasetoware.Config{ SymmetricKey: []byte(secretSymmetricKey), Extractor: extractors.FromAuthHeader("Bearer"), })) // Restricted Routes apiGroup.Get("/restricted", restricted) err := app.Listen(":8088") if err != nil { return } } func login(c fiber.Ctx) error { user := c.FormValue("user") pass := c.FormValue("pass") // Throws Unauthorized error if user != "john" || pass != "doe" { return c.SendStatus(fiber.StatusUnauthorized) } // Create token and encrypt it encryptedToken, err := pasetoware.CreateToken([]byte(secretSymmetricKey), user, 12*time.Hour, pasetoware.PurposeLocal) if err != nil { return c.SendStatus(fiber.StatusInternalServerError) } return c.JSON(fiber.Map{"token": encryptedToken}) } func accessible(c fiber.Ctx) error { return c.SendString("Accessible") } func restricted(c fiber.Ctx) error { payload := pasetoware.FromContext(c).(string) return c.SendString("Welcome " + payload) } ``` ##### Test it _Login using username and password to retrieve a token._ ```sh curl --data "user=john&pass=doe" http://localhost:8088/login ``` _Response_ ```json { "token": "" } ``` _Request a restricted resource using the token in Authorization request header._ ```sh curl localhost:8088/api/restricted -H "Authorization: Bearer " ``` _Response_ ```text Welcome john ``` #### SymmetricKey + Custom Validator callback ```go package main import ( "encoding/json" "time" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/extractors" "github.com/o1egl/paseto" pasetoware "github.com/gofiber/contrib/v3/paseto" ) const secretSymmetricKey = "symmetric-secret-key (size = 32)" type customPayloadStruct struct { Name string `json:"name"` ExpiresAt time.Time `json:"expiresAt"` } func main() { app := fiber.New() // Login route app.Post("/login", login) // Unauthenticated route app.Get("/", accessible) // Paseto Middleware with local (encrypted) token apiGroup := app.Group("api", pasetoware.New(pasetoware.Config{ SymmetricKey: []byte(secretSymmetricKey), Extractor: extractors.FromAuthHeader("Bearer"), Validate: func(decrypted []byte) (any, error) { var payload customPayloadStruct err := json.Unmarshal(decrypted, &payload) return payload, err }, })) // Restricted Routes apiGroup.Get("/restricted", restricted) err := app.Listen(":8088") if err != nil { return } } func login(c fiber.Ctx) error { user := c.FormValue("user") pass := c.FormValue("pass") // Throws Unauthorized error if user != "john" || pass != "doe" { return c.SendStatus(fiber.StatusUnauthorized) } // Create the payload payload := customPayloadStruct{ Name: "John Doe", ExpiresAt: time.Now().Add(12 * time.Hour), } // Create token and encrypt it encryptedToken, err := paseto.NewV2().Encrypt([]byte(secretSymmetricKey), payload, nil) if err != nil { return c.SendStatus(fiber.StatusInternalServerError) } return c.JSON(fiber.Map{"token": encryptedToken}) } func accessible(c fiber.Ctx) error { return c.SendString("Accessible") } func restricted(c fiber.Ctx) error { payload := pasetoware.FromContext(c).(customPayloadStruct) return c.SendString("Welcome " + payload.Name) } ``` #### Cookie Extractor Example ```go package main import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/extractors" pasetoware "github.com/gofiber/contrib/v3/paseto" ) const secretSymmetricKey = "symmetric-secret-key (size = 32)" func main() { app := fiber.New() // Paseto Middleware with cookie extractor app.Use(pasetoware.New(pasetoware.Config{ SymmetricKey: []byte(secretSymmetricKey), Extractor: extractors.FromCookie("token"), })) app.Get("/protected", func(c fiber.Ctx) error { return c.SendString("Protected route") }) app.Listen(":8080") } ``` #### Query Extractor Example ```go package main import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/extractors" pasetoware "github.com/gofiber/contrib/v3/paseto" ) const secretSymmetricKey = "symmetric-secret-key (size = 32)" func main() { app := fiber.New() // Paseto Middleware with query extractor app.Use(pasetoware.New(pasetoware.Config{ SymmetricKey: []byte(secretSymmetricKey), Extractor: extractors.FromQuery("token"), })) app.Get("/protected", func(c fiber.Ctx) error { return c.SendString("Protected route") }) app.Listen(":8080") } ``` #### PublicPrivate Key ```go package main import ( "crypto/ed25519" "encoding/hex" "time" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/extractors" pasetoware "github.com/gofiber/contrib/v3/paseto" ) const privateKeySeed = "e9c67fe2433aa4110caf029eba70df2c822cad226b6300ead3dcae443ac3810f" var seed, _ = hex.DecodeString(privateKeySeed) var privateKey = ed25519.NewKeyFromSeed(seed) type customPayloadStruct struct { Name string `json:"name"` ExpiresAt time.Time `json:"expiresAt"` } func main() { app := fiber.New() // Login route app.Post("/login", login) // Unauthenticated route app.Get("/", accessible) // Paseto Middleware with public (signed) token apiGroup := app.Group("api", pasetoware.New(pasetoware.Config{ Extractor: extractors.FromAuthHeader("Bearer"), PrivateKey: privateKey, PublicKey: privateKey.Public(), })) // Restricted Routes apiGroup.Get("/restricted", restricted) err := app.Listen(":8088") if err != nil { return } } func login(c fiber.Ctx) error { user := c.FormValue("user") pass := c.FormValue("pass") // Throws Unauthorized error if user != "john" || pass != "doe" { return c.SendStatus(fiber.StatusUnauthorized) } // Create token and sign it signedToken, err := pasetoware.CreateToken(privateKey, user, 12*time.Hour, pasetoware.PurposePublic) if err != nil { return c.SendStatus(fiber.StatusInternalServerError) } return c.JSON(fiber.Map{"token": signedToken}) } func accessible(c fiber.Ctx) error { return c.SendString("Accessible") } func restricted(c fiber.Ctx) error { payload := pasetoware.FromContext(c).(string) return c.SendString("Welcome " + payload) } ``` ##### Get the payload from the context ```go payloadFromCtx := pasetoware.FromContext(c) if payloadFromCtx == nil { // Handle case where token is not in context, e.g. by returning an error return } payload := payloadFromCtx.(string) ``` `FromContext` accepts a `fiber.Ctx`, `fiber.CustomCtx`, `*fasthttp.RequestCtx`, or a standard `context.Context` (e.g. the value returned by `c.Context()` when `PassLocalsToContext` is enabled): ```go // From a fiber.Ctx (most common usage) payload := pasetoware.FromContext(c) // From the underlying context.Context (useful in service layers or when PassLocalsToContext is enabled) payload := pasetoware.FromContext(c.Context()) ``` ##### Test it _Login using username and password to retrieve a token._ ```sh curl --data "user=john&pass=doe" http://localhost:8088/login ``` _Response_ ```json { "token": "" } ``` _Request a restricted resource using the token in Authorization request header._ ```sh curl localhost:8088/api/restricted -H "Authorization: Bearer " ``` _Response_ ```text Welcome John Doe ``` --- ## Prometheus ![Release](https://img.shields.io/github/v/tag/gofiber/contrib?filter=*prometheus*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://github.com/gofiber/contrib/workflows/Test%20Prometheus/badge.svg) Prometheus middleware for [Fiber](https://github.com/gofiber/fiber) that instruments incoming requests and serves the metrics endpoint, based on [ansrivas/fiberprometheus](https://github.com/ansrivas/fiberprometheus). It comes with [Grafana dashboards](#grafana-dashboards) for the metrics it exposes. **Compatible with Fiber v3.** ### Go version support We only support the latest two versions of Go. Visit [https://go.dev/doc/devel/release](https://go.dev/doc/devel/release) for more information. ### Install ```sh go get -u github.com/gofiber/fiber/v3 go get -u github.com/gofiber/contrib/v3/prometheus ``` ### Signature ```go prometheus.New(config ...prometheus.Config) fiber.Handler ``` ### Config | Property | Type | Description | Default | |:---------|:-----|:------------|:--------| | ServiceName | `string` | Added as the `service` const label on every metric. Omitted when empty. Trimmed; must be valid UTF-8. | `""` | | Namespace | `string` | Prefixes every metric name. Trimmed; must form a valid metric name. | `"http"` | | Subsystem | `string` | Prefixes every metric name after `Namespace`. Trimmed and validated the same way. | `""` | | MetricsPath | `string` | Path served with the Prometheus exposition format. Unless `Next` returns true, requests to it are answered by the middleware and are not instrumented. Compared case-sensitively against the full request path, ignoring trailing slashes. | `"/metrics"` | | Labels | `prometheus.Labels` | Extra const labels attached to every metric. A key that is empty, starts with `__`, or collides with a reserved label (`status_code`, `status_class`, `method`, `path`, `le`) panics. | `nil` | | Registerer | `prometheus.Registerer` | Registry used to register the metrics. Reusing one across two `New` calls panics on duplicate registration. | private registry | | Gatherer | `prometheus.Gatherer` | Source the metrics endpoint gathers from. | private registry | | DisableGoCollector | `bool` | Skips registration of the Go runtime metrics collector. A registry that refuses the collector is reported to `MetricsErrorLog`, not fatal. | `false` | | DisableProcessCollector | `bool` | Skips registration of the process metrics collector. A refusal is handled as for `DisableGoCollector`. | `false` | | RequestDurationBuckets | `[]float64` | Histogram buckets for request latency, in seconds. `nil` selects the defaults; an empty non-nil slice drops the classic buckets, but only alongside `NativeHistogramBucketFactor`. Bounds must be strictly increasing, `+Inf` last only, or `New` panics. | see [Default Config](#default-config) | | RequestSizeBuckets | `[]float64` | Histogram buckets for request payload size, in bytes. | see [Default Config](#default-config) | | ResponseSizeBuckets | `[]float64` | Histogram buckets for response payload size, in bytes. | see [Default Config](#default-config) | | NativeHistogramBucketFactor | `float64` | Enables native histograms when greater than 1, capping the growth factor between buckets. Any other non-zero value panics, including a NaN or an infinity. | `0` | | NativeHistogramMaxBucketNumber | `uint32` | Bounds the native histogram buckets kept per series. | `0` (unlimited) | | NativeHistogramMinResetDuration | `time.Duration` | Minimum time before a native histogram may be reset to control its bucket count. | `0` | | TrackUnmatchedRequests | `bool` | Records metrics for requests that do not resolve to a registered route. | `false` | | UnmatchedRouteLabel | `string` | Path label used for unmatched requests when `TrackUnmatchedRequests` is enabled. Must be valid UTF-8. | `"/__unmatched__"` | | EnableOpenMetrics | `bool` | Negotiates the experimental OpenMetrics encoding. Not required for exemplars — protobuf carries them too. | `false` | | EnableOpenMetricsTextCreatedSamples | `bool` | Adds synthetic `_created` samples to OpenMetrics responses. Requires `EnableOpenMetrics`. | `false` | | DisableExemplars | `bool` | Skips trace exemplar collection, and with it the request-context read every instrumented request otherwise pays. | `false` | | DisableCompression | `bool` | Serves metrics uncompressed even when the client requests gzip or zstd. | `false` | | MetricsMaxRequestsInFlight | `int` | Caps concurrent scrapes; the excess is answered with 503. A negative panics — `promhttp` would read it as unlimited. | `0` (unlimited) | | MetricsTimeout | `time.Duration` | Bounds a single scrape before it is answered with 503. A negative panics, as for `MetricsMaxRequestsInFlight`. | `0` (no timeout) | | MetricsErrorLog | `promhttp.Logger` | Receives errors raised while gathering or writing metrics, plus the faults the middleware absorbs (a panicking `DynamicLabels` or `Next`, a refused runtime collector). A typed nil panics. | `nil` | | MetricsErrorHandling | `promhttp.HandlerErrorHandling` | How gathering errors are reported to the scraper. An unknown value panics; avoid `PanicOnError` — it takes the process down. | `promhttp.HTTPErrorOnError` | | DisabledMetrics | `[]Metric` | Metric families to skip registering and recording. Entries are trimmed; an unknown name panics. | `nil` | | SkipURIs | `[]string` | Route patterns excluded from instrumentation, e.g. `/user/:id`. A trailing `*` matches by prefix; a leading `/` is added when missing. A skipped route's error propagates normally. | `nil` | | SkipStatusCodes | `[]int` | Response status codes excluded from metrics. Codes are three digits; anything else panics. | `nil` | | SkipStatusClasses | `[]string` | Status classes excluded from metrics, `"1xx"` through `"5xx"` or `"unknown"`. Anything else panics. | `nil` | | DynamicLabels | `map[string]func(fiber.Ctx) string` | Extra labels computed per request. Names follow the same rules as `Labels`. A panicking function drops the sample. | `nil` | | Next | `func(fiber.Ctx) bool` | Skips the middleware when it returns true, including for `MetricsPath`. A panicking function is read as true. | `nil` | ### Default Config ```go var ConfigDefault = Config{ Namespace: "http", MetricsPath: "/metrics", UnmatchedRouteLabel: "/__unmatched__", RequestDurationBuckets: []float64{0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 10, 15, 30, 60}, RequestSizeBuckets: []float64{256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 5242880}, ResponseSizeBuckets: []float64{256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 5242880}, } ``` ### Example ```go package main import ( fiberprometheus "github.com/gofiber/contrib/v3/prometheus" "github.com/gofiber/fiber/v3" ) func main() { app := fiber.New() // Mount the middleware globally so it observes every request. It answers // scrapes on Config.MetricsPath ("/metrics" by default) itself and passes // everything else through to your handlers. app.Use(fiberprometheus.New(fiberprometheus.Config{ ServiceName: "my-service-name", SkipURIs: []string{"/ping"}, SkipStatusCodes: []int{401, 403, 404}, })) app.Get("/", func(c fiber.Ctx) error { return c.SendString("Hello World") }) app.Get("/ping", func(c fiber.Ctx) error { return c.SendString("pong") }) app.Post("/some", func(c fiber.Ctx) error { return c.SendString("Welcome!") }) app.Listen(":3000") } ``` Register the middleware before the routes you want instrumented, and mount it only once — mounting the same handler twice double-counts every request that reaches both instances. Scrapes are the exception: the first invocation answers them without calling `ctx.Next()`, so they never reach the second. ### Metrics The following metrics are exposed, prefixed with `Namespace` (`http` by default) and `Subsystem`: ```text http_requests_total http_requests_status_class_total http_request_duration_seconds http_requests_in_progress http_request_size_bytes http_response_size_bytes ``` Every metric except `http_requests_in_progress` is labeled with the *registered route pattern* (for example `/user/:id`), not the request path, so label cardinality stays bounded no matter what clients request. Trailing slashes are trimmed from the pattern, which is what lets a `SkipURIs` entry match however it was spelled — so under `fiber.Config{StrictRouting: true}`, `/foo` and `/foo/` are two endpoints sharing one series. `http_requests_in_progress` is labeled by HTTP method only. It has to be incremented before the router picks a handler, at which point the route pattern is not known yet. `http_request_duration_seconds` runs from the timestamp fasthttp takes before calling the handler until the handler chain and, if it ran, the error handler have returned. Routing and middleware mounted before this one are part of it; `DynamicLabels` functions, which run after the chain, are not. Requests recorded under `UnmatchedRouteLabel` are the exception, timed from when this middleware runs — see below. `http_request_size_bytes` and `http_response_size_bytes` record a payload only when its size is known — either `Content-Length` is set, or the body is buffered and can be measured. A stream of unannounced length, such as `c.SendStream` without a size or an SSE response, is left out of those histograms rather than recorded as zero bytes, which would drag the reported percentiles towards nothing. On the request side what actually arrived is measured, so a client cannot bill the histogram for a body it never sent. The exception is a pre-parsed multipart form: fasthttp keeps the parsed parts — the large ones spilled to temp files — so reading the body back would re-marshal every uploaded file into memory just to size it, and the announced `Content-Length` is used instead — unless it exceeds `BodyLimit`, which means the body was never received in full and no honest size exists to record. Under `fiber.Config{StreamRequestBody: true}` request payloads are left out of `http_request_size_bytes` entirely. fasthttp prefetches a bounded prefix and hands the rest to the handler, and a handler that returns without reading it — an auth rejection, a 404, any route that ignores its body — leaves the remainder undrained. The announced `Content-Length` is then a client's claim rather than a measurement, and draining the stream to check would defeat the point of streaming. A response that carries no body on the wire records zero however much the handler wrote — a `HEAD`, any status RFC 9110 forbids a body on (`1xx`, `204`, `304`), or a handler that sets `c.Response().SkipBody` itself — because fasthttp drops the body and no payload bytes reach the client. `http_requests_status_class_total` is convenience, not new information: `status_class` is a function of `status_code`, so `rate(http_requests_status_class_total{status_class="5xx"}[5m])` is `rate(http_requests_total{status_code=~"5.."}[5m])`. It costs a second series per route, method and class — drop it through `DisabledMetrics` if that trade is not worth it to you. Requests that miss every registered route are not recorded unless `TrackUnmatchedRequests` is enabled, in which case they are labeled with `UnmatchedRouteLabel`. One caveat comes with that flag: a request fasthttp rejects before routing — a body over `BodyLimit`, oversized headers, a read timeout — is counted as a `200`. Fiber answers those through its server error handler, which replays the `Use` chain with non-`Use` routes skipped and writes the real status only afterwards, and Fiber v3.5.0 offers no way to tell that replay apart from an ordinary request answered by `Use` handlers. fasthttp does not stamp such a request either. On a keep-alive connection it still carries the previous request's timestamp, and timing it from there would add however long the connection sat idle — a duration the client chooses. So with the flag on, unmatched requests are timed from when this middleware runs, and middleware mounted before it is not part of their duration. Matched requests keep fasthttp's timestamp, which is always fresh for them; the flag still costs them a clock read, because whether a request matched is only known once the chain has returned. A request answered entirely by `app.Use` handlers counts as unmatched too: `static.New`, or a `Use`-mounted guard returning 401, never matches a non-`Use` route, so nothing is recorded for it by default. One case is attributed to the wrong pattern. If the matched handler delegates onwards with `c.Next()` and a trailing `app.Use` middleware runs last, Fiber has already replaced the route on the context with that middleware's mount path by the time this middleware regains control, so the request is recorded under that mount path — usually `/`. The endpoint pattern is unrecoverable at that point, and Fiber exposes no way to tell a `use` route from any other, so the case cannot be detected either. Avoid falling through from a route handler into a trailing `app.Use` if you need exact route labels. #### Dropping metrics you do not need Every family costs cardinality. `DisabledMetrics` skips registering and recording the ones you will not query — most often the two size histograms: ```go app.Use(fiberprometheus.New(fiberprometheus.Config{ DisabledMetrics: []fiberprometheus.Metric{ fiberprometheus.MetricRequestSize, fiberprometheus.MetricResponseSize, }, })) ``` #### Extra labels per request `DynamicLabels` adds labels whose values are computed once per recorded request, after the handler chain has returned. Every distinct value creates a new series, so a value taken straight from the request is a denial-of-service vector: a client that varies a header freely grows the registry without bound. Map untrusted input onto a fixed set of values before returning it: ```go var knownTenants = map[string]bool{"acme": true, "globex": true} app.Use(fiberprometheus.New(fiberprometheus.Config{ DynamicLabels: map[string]func(fiber.Ctx) string{ "tenant": func(c fiber.Ctx) string { if tenant := c.Get("X-Tenant"); knownTenants[tenant] { return tenant } return "other" }, }, })) ``` They apply to every family except `http_requests_in_progress`, which is incremented before routing and so cannot see them. Names must not collide with the reserved `status_code`, `status_class`, `method`, `path` and `le` labels or with `Labels`; the middleware panics at startup if they do. The middleware copies each returned value the first time it sees a label set, so it is safe to return one of Fiber's zero-copy strings such as `c.Get(...)` or `c.Params(...)` directly; a label set seen before allocates nothing. A function that panics costs its request every metric, not the request itself: the sample is dropped, the response is unaffected, and the drop is reported to `MetricsErrorLog` — once, since a bad type assertion panics on every request, and a line per request would funnel every connection through the log sink. The middleware cannot do better than drop it, because these run after the handler chain has unwound, past any `recover` the application mounted. `Config.Next` is guarded the same way, but treated as having returned true: the middleware stands aside, leaving the request — `MetricsPath` included — to the rest of the chain, rather than serving an endpoint the panicking filter may have meant to withhold. Guard your type assertions rather than relying on either. #### Filtering An entry may also name `UnmatchedRouteLabel` to exclude the traffic `TrackUnmatchedRequests` records. That is a separate namespace — this list holds route patterns, the label is a value — so a real route spelled the same as the label still obeys the pattern rules, and a rule written for real routes never takes 404 monitoring with it. The separator distinguishes the two shapes: with the label set to `/api`, `/api`, `/api*` and `/api*/` name the label, while `/api/*` is a prefix rule for the routes below it. `SkipURIs` matches the registered route pattern — note that fiberzap's option of the same name matches the request path instead. A trailing `*` matches by prefix and stops at a path segment boundary, so `/admin/*` excludes `/admin` and `/admin/users` but not `/administration`. Trailing stars are stripped as a group, so the glob spelling `/admin/**` means the same thing. `/*` excludes everything, and then no metric family is registered at all — not even the in-flight gauge, which is incremented before routing and so beyond the reach of any per-route filter. Trailing slashes are ignored, and a leading `/` is added when missing — route patterns always carry one, so `admin` without it would otherwise match nothing. The match is case-sensitive against the pattern as registered, while Fiber routes case-insensitively by default, so spell the entry the way the route was registered: `/Admin`, not `/admin`. Because Fiber route patterns can themselves end in `*`, such an entry also matches the pattern named exactly that: `/static*` excludes both the route registered as `/static*` and anything under `/static`. Blank entries are ignored in `SkipURIs` and `SkipStatusClasses` alike, so splitting an unset environment variable on `,` neither excludes the root route nor stops the process from booting; ask for `/` explicitly to skip the root. Every other unmatchable entry — an unknown metric name, a status code that is not three digits, a status class outside `"1xx"`–`"5xx"` and `"unknown"` — panics at startup rather than filtering nothing in silence. `SkipStatusCodes` takes exact codes; `SkipStatusClasses` takes whole classes so you do not have to enumerate them: ```go app.Use(fiberprometheus.New(fiberprometheus.Config{ SkipURIs: []string{"/health", "/internal/*"}, SkipStatusClasses: []string{"4xx"}, })) ``` ### Grafana dashboards The [`grafana`](https://github.com/gofiber/contrib/tree/main/v3/prometheus/grafana) folder holds four dashboards for the metrics this middleware exposes. They link to one another and share their variables, so a selection carries over as you move between them. | Dashboard | Shows | |:----------|:------| | [Fiber / HTTP Overview](https://github.com/gofiber/contrib/blob/main/v3/prometheus/grafana/fiber-http-overview.json) | Request rate, error ratios and latency of a service, a table of its routes, in-flight requests and payload throughput. | | [Fiber / HTTP Route](https://github.com/gofiber/contrib/blob/main/v3/prometheus/grafana/fiber-http-route.json) | One route in depth: status codes, latency percentiles and distribution, payload sizes, and how its instances compare. | | [Fiber / Go Runtime](https://github.com/gofiber/contrib/blob/main/v3/prometheus/grafana/fiber-go-runtime.json) | The Go and process collectors: CPU, memory, the garbage collector, goroutines, threads and file descriptors. | | [Fiber / HTTP Wallboard](https://github.com/gofiber/contrib/blob/main/v3/prometheus/grafana/fiber-http-wallboard.json) | The essentials in large type for a screen across the room: request rate, server errors, p99 latency, in-flight requests and whether each instance is up. | ![Fiber / HTTP Overview dashboard](https://raw.githubusercontent.com/gofiber/contrib/main/v3/prometheus/grafana/screenshots/fiber-http-overview.png) Selecting a route in the overview's table opens the route dashboard: ![Fiber / HTTP Route dashboard](https://raw.githubusercontent.com/gofiber/contrib/main/v3/prometheus/grafana/screenshots/fiber-http-route.png) The runtime dashboard follows the same job, service and instance selection: ![Fiber / Go Runtime dashboard](https://raw.githubusercontent.com/gofiber/contrib/main/v3/prometheus/grafana/screenshots/fiber-go-runtime.png) The wallboard trades detail for distance: five tiles meant to be read from across the room. Those that report a state take its color — server errors turn orange at 1% and red at 5%, p99 latency at 1 s and 2.5 s, and an instance that stops answering its scrapes turns red. The latency thresholds are starting points; set them to your own objectives. Open the wallboard in kiosk mode with the controls hidden, so the tiles fill the screen: ```text https://grafana.example.com/d/fiber-http-wallboard/fiber-http-wallboard?kiosk&_dash.hideTimePicker&_dash.hideVariables&_dash.hideLinks&var-job=my-service ``` ![Fiber / HTTP Wallboard dashboard](https://raw.githubusercontent.com/gofiber/contrib/main/v3/prometheus/grafana/screenshots/fiber-http-wallboard.png) #### Importing In Grafana, open **Dashboards → New → Import**, upload one of the JSON files and save. Nothing is asked at import time: each dashboard picks its Prometheus data source through its **Data source** variable. To provision them instead, copy the files into a directory and point a file provider at it: ```yaml apiVersion: 1 providers: - name: fiber folder: Fiber type: file options: path: /var/lib/grafana/dashboards/fiber ``` Set the data source's **Scrape interval** to the interval Prometheus scrapes your application at. The panels compute rates over `$__rate_interval`, which Grafana derives from that setting, and a window that holds fewer than two scrapes leaves them empty. #### Variables - **Metric prefix** is `Namespace` and `Subsystem` joined by an underscore. It is detected from the `*_requests_total` series, so a custom `Namespace` needs no edits — even one such as `my-app` that only the UTF-8 name scheme allows; `http` is preselected when present. - **Job**, **Service** and **Instance** narrow the selection. **Service** lists the values of the `service` label that `ServiceName` sets; without that option the list stays empty and the panels still work. - **Filters** applies ad hoc label filters to every query: the labels you add through `Labels` or `DynamicLabels`, or those your scrape configuration attaches, such as `cluster` or `namespace`. A filter on a label some series lack empties the panels built on them: the in-flight gauge carries no `path` or dynamic labels, and the wallboard's instance blocks come from `up`. - **Route** and **Method**, on the route dashboard, pick the route to show. #### Requirements The dashboards need Grafana 11.3 or later and were tested against 11.3, 11.6, 12.4 and 13.2; Grafana 11.0 loads them too, but draws every route, method and instance in the same color. Prometheus has to be 2.40 or later, for the native histogram functions the queries fall back to. The HTTP panels are built on `requests_total` and `request_duration_seconds`; the in-flight, payload and runtime panels need the families and collectors they show. The runtime dashboard and the wallboard find their targets through `requests_total` or `requests_in_progress`, so an instance shows up there once it has answered any request, even one to a skipped route. `requests_status_class_total` is not used — status classes are derived from `status_code` — so dropping it through `DisabledMetrics` costs no panel. Every histogram query works whether Prometheus ingests classic buckets, native histograms, or classic histograms it converts with `convert_classic_histograms_to_nhcb`. Where a histogram is stored both ways, the classic buckets are used. The p99 latency line on the route dashboard shows trace exemplars once Prometheus stores them (`--enable-feature=exemplar-storage`) and receives them, which takes an encoding that carries exemplars — see [Exemplars](#exemplars). Configure the data source's exemplar settings for the `traceID` label to link them to your tracing backend. ### Error handling Because Fiber runs the application error handler only after the whole handler chain has unwound, the middleware invokes it itself when a downstream handler returns an error. This is what Fiber's own logger middleware does, and it is what allows the recorded status code and response size to match what the client actually received. The error is therefore consumed by this middleware and does not propagate to handlers mounted *before* it. That applies only to requests it records. When the middleware stands aside — `Next` returns true, every metric family is disabled, or the route matched a `SkipURIs` pattern — there is no status code to be gained, so the error is returned unchanged and surfaces where it would without the middleware mounted. A request answered by Fiber's `timeout` middleware is recorded with the status the client receives (`503` by default since Fiber v3.6.0, or whatever `OnTimeout` returns). That middleware answers through fasthttp's `RequestCtx.TimeoutErrorWithCode`, which parks the response and installs it only after the whole chain has unwound, so the live response still reads `200` at the point metrics are taken; the middleware reads the parked one instead. Mount `recover.New()` **after** this middleware, not before it: ```go app.Use(prometheus.New(prometheus.Config{})) app.Use(recover.New()) ``` A panic unwinds straight past the recording step, so with `recover` mounted first the middleware never sees the request complete and the resulting 500 appears in no metric family at all — only the in-flight gauge, which is deferred, stays balanced. Recovering downstream turns the panic into an ordinary error return, which this middleware records as the 500 the client received. ### Registry and collectors By default the middleware creates a private `Registerer`/`Gatherer` pair and uses it for both registration and scraping, and registers the Go runtime and process collectors into it. When customizing the registry, ensure that `Registerer` and `Gatherer` refer to the same metrics source (for example, a `*prometheus.Registry`). Supplying only one that does not implement the other interface panics during initialization so metrics are not silently dropped. Supplying both also supports pairing a wrapper such as `prometheus.WrapRegistererWithPrefix` with the registry it wraps. Such a wrapper is not itself a `Gatherer`, so it is unwrapped — through its nested client_golang wrappers — down to the `Registerer` it hands registrations to, and that is what the `Gatherer` is compared against. A wrapper the walk resolves to an unrelated registry panics during initialization rather than silently omitting the middleware's metrics from scrapes. The comparison is deliberately one of provable inequality, so a pair the walk cannot resolve is accepted, and three shapes are undecidable. A `Registerer` that is neither a `Gatherer` nor a client_golang wrapper keeps its destination to itself — reading any type that merely holds another `Registerer` as a delegation chain would reject valid pairings. A `Gatherer` that hides its source behind a wrapper, `prometheus.GathererFunc(other.Gather)` for instance, is opaque in the same way even when the `Registerer` is a plain registry. And the walk itself is depth-bounded, so a chain of wrappers longer than the bound ends undecided rather than resolved — a bound no real configuration reaches, since wrappers are nested by hand. None of the three is checked, so pairing one with an unrelated source still scrapes nothing. ```go registry := prometheus.NewRegistry() app.Use(fiberprometheus.New(fiberprometheus.Config{ ServiceName: "my-service-name", Registerer: registry, Gatherer: registry, })) ``` #### Where the endpoint is served `MetricsPath` is compared case-sensitively against the full request path, with trailing slashes ignored on both sides, which has two consequences worth knowing before you mount the middleware anywhere but the root. Mounting on a group leaves the default endpoint unreachable — `/api/metrics` never equals `/metrics`, and `/metrics` never reaches the group, so both 404. Set the full path instead: ```go api := app.Group("/api") api.Use(fiberprometheus.New(fiberprometheus.Config{MetricsPath: "/api/metrics"})) ``` And because Fiber routes case-insensitively by default while this comparison does not, `GET /METRICS` is instrumented as an ordinary request rather than answered with the exposition page. #### Exposure of the metrics endpoint The endpoint is unauthenticated. Because the middleware answers `MetricsPath` itself before routing, a route registered at the same path with auth in front of it is never reached — so restrict access at the network layer, bind the metrics listener to an internal interface, or use `Config.Next` to reject scrapes that do not come from your monitoring system. A scrape discloses more than request counts: the default collectors report the exact Go version, process memory, open file descriptors and start time, and the `path` label enumerates every registered route pattern in the application. Treat it as internal. #### Protecting the scrape endpoint A slow or stuck gather can otherwise pile up. `MetricsMaxRequestsInFlight` caps concurrent scrapes and `MetricsTimeout` bounds each one; both answer the excess with 503. Gather errors are silent by default — pass a `MetricsErrorLog` to see them: ```go app.Use(fiberprometheus.New(fiberprometheus.Config{ MetricsMaxRequestsInFlight: 4, MetricsTimeout: 10 * time.Second, MetricsErrorLog: log.New(os.Stderr, "prometheus: ", log.LstdFlags), })) ``` `MetricsTimeout` bounds the *answer*, not the gather: `promhttp` replies 503 and returns while the gathering goroutine runs on to completion, still holding its `MetricsMaxRequestsInFlight` slot. A gatherer that regularly outruns the timeout will therefore still exhaust the cap — fix the slow collector rather than raising the limit. ### Native histograms Setting `NativeHistogramBucketFactor` above 1 enables native histograms on the duration and size histograms. They resolve latency without hand-tuned buckets: the factor caps the growth between consecutive buckets, so `1.1` gives roughly 10% resolution. This requires a Prometheus server with native histograms enabled, and the values are only carried by the protobuf exposition format. Classic and native buckets are emitted together by default. To go native-only, pass an empty non-nil bucket slice — unlike `nil`, which selects the defaults. This only takes effect alongside `NativeHistogramBucketFactor`: client_golang substitutes its own defaults rather than leave a histogram with no buckets, so an empty slice on its own would give the size histograms latency-shaped buckets (`le="0.005"` … `le="10"`) measured in bytes. ```go app.Use(fiberprometheus.New(fiberprometheus.Config{ NativeHistogramBucketFactor: 1.1, NativeHistogramMaxBucketNumber: 160, NativeHistogramMinResetDuration: time.Hour, RequestDurationBuckets: []float64{}, RequestSizeBuckets: []float64{}, ResponseSizeBuckets: []float64{}, })) ``` ### Exemplars When the request context carries a **sampled** OpenTelemetry span, the duration and size histograms record the trace ID as an exemplar under the `traceID` label. Unsampled spans are skipped: Prometheus keeps one exemplar per bucket and overwrites it on each observation, so recording traces that were never exported would evict the links that lead somewhere. Reaching a scraper takes an encoding that carries exemplars. Protobuf does, and promhttp negotiates it without any configuration — which is what a Prometheus server asks for once native histograms are enabled. OpenMetrics text does too, but only when you set `EnableOpenMetrics: true`; the plain text exposition carries no exemplars in either case. Collecting one costs a request-context read on every instrumented request: Fiber installs a background context when the application never set one, which the request then has to clear again on release. Set `DisableExemplars: true` when nothing in your stack starts spans, and that work goes away. ### 📊 Result - Hit the default url at [http://localhost:3000](http://localhost:3000) - Navigate to [http://localhost:3000/metrics](http://localhost:3000/metrics) --- ## Sentry ![Release](https://img.shields.io/github/v/tag/gofiber/contrib?filter=*sentry*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://github.com/gofiber/contrib/workflows/Test%20sentry/badge.svg) [Sentry](https://sentry.io/) support for Fiber. **Compatible with Fiber v3.** ### Go version support We only support the latest two versions of Go. Visit [https://go.dev/doc/devel/release](https://go.dev/doc/devel/release) for more information. ### Install ```sh go get -u github.com/gofiber/fiber/v3 go get -u github.com/gofiber/contrib/v3/sentry go get -u github.com/getsentry/sentry-go ``` ### Signature ```go fiberSentry.New(config ...fiberSentry.Config) fiber.Handler fiberSentry.GetHubFromContext(ctx any) *sdk.Hub // sdk "github.com/getsentry/sentry-go" fiberSentry.MustGetHubFromContext(ctx any) *sdk.Hub // sdk "github.com/getsentry/sentry-go" ``` `GetHubFromContext` and `MustGetHubFromContext` each accept a `fiber.Ctx`, `fiber.CustomCtx`, `*fasthttp.RequestCtx`, or a standard `context.Context` (e.g. the value returned by `c.Context()` when `PassLocalsToContext` is enabled). The `Must*` variant panics if the hub is not found. `*sdk.Hub` is `*sentry.Hub` from `github.com/getsentry/sentry-go`. ### Config | Property | Type | Description | Default | | :-------------- | :-------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------- | | Repanic | `bool` | Repanic configures whether Sentry should repanic after recovery. Set to true, if [Recover](https://github.com/gofiber/fiber/tree/master/middleware/recover) middleware is used. | `false` | | WaitForDelivery | `bool` | WaitForDelivery configures whether you want to block the request before moving forward with the response. If [Recover](https://github.com/gofiber/fiber/tree/master/middleware/recover) middleware is used, it's safe to either skip this option or set it to false. | `false` | | Timeout | `time.Duration` | Timeout for the event delivery requests. | `time.Second * 2` | ### Usage `sentry` attaches an instance of `*sentry.Hub` (https://godoc.org/github.com/getsentry/sentry-go#Hub) to the request's context, which makes it available throughout the rest of the request's lifetime. You can access it by using the `sentry.GetHubFromContext()` or `sentry.MustGetHubFromContext()` method on the context itself in any of your proceeding middleware and routes. Keep in mind that `*sentry.Hub` should be used instead of the global `sentry.CaptureMessage`, `sentry.CaptureException`, or any other calls, as it keeps the separation of data between the requests. - **Keep in mind that `*sentry.Hub` won't be available in middleware attached before `sentry`. In this case, `GetHubFromContext()` returns nil, and `MustGetHubFromContext()` will panic.** ```go package main import ( "fmt" "log" sdk "github.com/getsentry/sentry-go" fiberSentry "github.com/gofiber/contrib/v3/sentry" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/utils" ) func main() { _ = sdk.Init(sdk.ClientOptions{ Dsn: "", BeforeSend: func(event *sdk.Event, hint *sdk.EventHint) *sdk.Event { if hint.Context != nil { if c, ok := hint.Context.Value(sdk.RequestContextKey).(fiber.Ctx); ok { // You have access to the original Context if it panicked fmt.Println(utils.ImmutableString(c.Hostname())) } } fmt.Println(event) return event }, Debug: true, AttachStacktrace: true, }) app := fiber.New() app.Use(fiberSentry.New(fiberSentry.Config{ Repanic: true, WaitForDelivery: true, })) enhanceSentryEvent := func(c fiber.Ctx) error { if hub := fiberSentry.GetHubFromContext(c); hub != nil { hub.Scope().SetTag("someRandomTag", "maybeYouNeedIt") } return c.Next() } app.All("/foo", enhanceSentryEvent, func(c fiber.Ctx) error { panic("y tho") }) app.All("/", func(c fiber.Ctx) error { if hub := fiberSentry.GetHubFromContext(c); hub != nil { hub.WithScope(func(scope *sdk.Scope) { scope.SetExtra("unwantedQuery", "someQueryDataMaybe") hub.CaptureMessage("User provided unwanted query string, but we recovered just fine") }) } return c.SendStatus(fiber.StatusOK) }) log.Fatal(app.Listen(":3000")) } ``` ### Accessing Context in `BeforeSend` callback ```go import ( "fmt" "github.com/gofiber/fiber/v3" sdk "github.com/getsentry/sentry-go" ) sdk.Init(sdk.ClientOptions{ Dsn: "your-public-dsn", BeforeSend: func(event *sdk.Event, hint *sdk.EventHint) *sdk.Event { if hint.Context != nil { if c, ok := hint.Context.Value(sdk.RequestContextKey).(fiber.Ctx); ok { // You have access to the original Context if it panicked fmt.Println(c.Hostname()) } } return event }, }) ``` ### Retrieving the hub with PassLocalsToContext When `fiber.Config{PassLocalsToContext: true}` is set, the Sentry hub stored by the middleware is also available in the underlying `context.Context`. Use `GetHubFromContext` or `MustGetHubFromContext` with any of the supported context types: ```go // From a fiber.Ctx (most common usage) hub := fiberSentry.GetHubFromContext(c) // From the underlying context.Context (useful in service layers or when PassLocalsToContext is enabled) hub := fiberSentry.GetHubFromContext(c.Context()) ``` `MustGetHubFromContext` panics if the hub is not found (e.g. in middleware that runs before `sentry`): ```go hub := fiberSentry.MustGetHubFromContext(c) ``` --- ## Socket.io ![Release](https://img.shields.io/github/v/tag/gofiber/contrib?filter=*socketio*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://github.com/gofiber/contrib/workflows/Test%20Socket.io/badge.svg) WebSocket wrapper for [Fiber](https://github.com/gofiber/fiber) that implements the [Engine.IO v4](https://github.com/socketio/engine.io-protocol) / [Socket.IO v5](https://github.com/socketio/socket.io-protocol) wire protocol, making it fully compatible with the official [`socket.io-client`](https://socket.io/docs/v4/client-api/) library. For applications that used older `socketio` releases as a plain WebSocket event bus, migrate to `github.com/gofiber/contrib/v3/websocket/event`. A deprecated compatibility shim is available at `github.com/gofiber/contrib/v3/socketio/legacy`. **Compatible with Fiber v3.** ### Features This middleware implements the full Engine.IO v4 / Socket.IO v5 wire protocol. Highlights: - **Synchronous handshake.** The Engine.IO OPEN / Socket.IO CONNECT exchange completes before the user `New()` callback returns, so emits issued inside the callback are ordered after the handshake reply. - **HTTP long-polling fallback (opt-in).** Set `socketio.EnablePolling = true` and mount the same handler for `GET` and `POST` to accept `transport=polling` clients. Polling sessions speak the same Engine.IO v4 / Socket.IO v5 wire protocol over HTTP and route through the same listener API (Emit, Ack, Close, Broadcast). Polling-to-WebSocket transport upgrade is not yet implemented; sessions that connect via polling stay on polling. - **Namespaces and handshake auth.** The negotiated namespace is honoured for inbound and outbound packets; the client's connect-time `auth` payload is exposed via `Websocket.HandshakeAuth()` and `EventPayload.HandshakeAuth`. - **Inbound acks.** Client-initiated callbacks surface as `EventPayload.HasAck` / `AckID`; reply once with `payload.Ack(args...)`. - **Outbound acks.** Server-initiated `EmitWithAck`, `EmitWithAckTimeout`, and `EmitWithAckArgs` round-trip a callback id and invoke the supplied callback when the client acks (or on timeout/disconnect). - **Multi-arg events.** Inbound events expose every argument tuple as `EventPayload.Args [][]byte`; outbound `EmitArgs` / `EmitWithAckArgs` send pre-encoded JSON tuples. - **Deterministic heartbeat.** Server PINGs every `PingInterval`; the connection is torn down if no PONG arrives within `PingTimeout`. The heartbeat is a runtime timer, not a goroutine, and an idle read deadline of `PingInterval + PingTimeout` backs it up. - **Closing handshake.** `Close` queues the SIO DISCONNECT packet and a Close frame behind the emits already pending, keeps reading until the peer's Close frame arrives (bounded by `CloseTimeout`), then closes the socket, so `socket.io-client` reports `io server disconnect` and does not reconnect. A client SIO DISCONNECT is answered the same way. Every tear-down releases the socket, including when a write is stalled on a peer that stopped reading. - **Two goroutines per connection.** The upgrade handler's goroutine runs the read loop and one goroutine serialises writes; polling sessions own no goroutine at all. - **Zero-copy inbound, SWAR outbound.** Inbound events are validated once and split into sub-slices of the frame they arrived in; outbound frames are built in a single pre-sized allocation with the SWAR JSON string encoder from `gofiber/utils`, and a broadcast builds each namespace's frame once for all its recipients. - **Coalesced writes.** On top of the websocket middleware's write coalescing, replies to a burst of pipelined client frames leave in one write. - **EIO 0x1E batched frames.** Multi-packet WebSocket frames separated by ASCII RS (`0x1E`) are parsed correctly, with a hard cap (`MaxBatchPackets`) to prevent slice-header amplification. - **Reserved-event-name guard.** User code cannot register or emit names reserved by the protocol (e.g. `connect`, `disconnect`). - **EIO version validation.** Handshakes that advertise an unsupported `EIO` version are rejected. - **Auth payload validation.** The auth blob must be a JSON object and is bounded by `MaxAuthPayload`; oversize or malformed payloads are answered with CONNECT_ERROR. - **DoS hardening.** `MaxPayload`, `MaxBatchPackets`, `MaxEventNameLength`, `MaxEventArgs`, and `MaxAuthPayload` bound every attacker-controlled length and count. - **Lock-free listener registry** plus `atomic.Bool isAlive`, removing the per-event mutex from the hot path. - **Optional drop-frames-on-overflow.** When `DropFramesOnOverflow` is true, a saturated send queue drops the offending frame and fires `EventError` instead of tearing down the connection. - **Graceful drain.** The package-level `Shutdown(ctx)` closes every active socket and waits for each worker to exit (or until `ctx` is cancelled). ### Known limitations - **One namespace per Engine.IO connection.** Each WebSocket binds the namespace negotiated during the SIO CONNECT packet; multiplexing several namespaces over one EIO connection is not supported. A CONNECT for another namespace on an established connection is answered with CONNECT_ERROR (`Invalid namespace`) instead of an acknowledgement whose events would never be delivered. - **No BINARY_EVENT (5) / BINARY_ACK (6).** Binary Socket.IO frames are passed through as raw `EventMessage` data; attachment reassembly is not implemented. - **No connection-state recovery.** Resume-on-reconnect (Socket.IO's `connectionStateRecovery` feature) is not implemented; reconnects always start a fresh session. - **No polling-to-WebSocket transport upgrade.** When polling is enabled, sessions that open with `transport=polling` advertise an empty `upgrades` array and stay on polling for the session lifetime. Clients that need WebSocket from the start should configure `transports: ['websocket']`. - **No JSONP polling fallback.** JSONP requests (`?j=N`) are rejected with engine.io error code 3. Modern browsers use XHR2/fetch; JSONP support is not planned. - **CORS is not handled by the middleware.** Mount `github.com/gofiber/fiber/v3/middleware/cors` (or your preferred CORS middleware) upstream of the polling route to control the policy. Long-poll holds connections open for up to ~25s by default, so reverse-proxy timeouts must accommodate (e.g. nginx `proxy_read_timeout >= 60s` and `proxy_buffering off`). ##### Production hardening notes - **Rate limiting**. Each polling open allocates a `*Websocket` and arms a heartbeat timer (no goroutine). With `EnablePolling = true` an unauthenticated client can create sessions until `HandshakeTimeout` reaps idle ones (10s default). Mount `github.com/gofiber/fiber/v3/middleware/limiter` upstream of the route to bound concurrent session creation. - **Write timeout**. A long-poll GET response that the client never reads pins a fasthttp worker on TCP backpressure. Configure `fiber.Config{WriteTimeout: ...}` (a few seconds is typically appropriate) so abandoned reads do not strand workers. - **Burst sizing**. `PollQueueMaxFrames` (default `1024`) bounds the per-session outbound buffer. With the default `DropFramesOnOverflow = false` a synchronous burst of more than 1024 emits inside a single listener call disconnects the session with `ErrSendQueueClosed`. Either pace large bursts across drains, raise `PollQueueMaxFrames`, or set `DropFramesOnOverflow = true` to tolerate overflow at the cost of dropped frames + `EventError`. - **Listener panics**. Both transports recover panics inside the `New()` callback and inside event listeners; the panic value is logged via the package `Logger` hook. Avoid `panic(string(attackerControlledBytes))` to prevent log injection in downstream consumers. ### Performance The hot paths avoid `encoding/json`'s reflection and copy nothing they do not have to. Inbound frames are handed over by the websocket middleware's pooled `ReadMessage`, validated once, and split into sub-slices; outbound frames are assembled in one pre-sized buffer with `gofiber/utils`' SWAR JSON string encoder; a broadcast builds each namespace's frame once and shares it between recipients. Each WebSocket connection costs two goroutines (the upgrade handler running the read loop, plus the writer) and one runtime timer instead of four goroutines; a polling session costs none. `benchstat` over `go test -run '^$' -bench . -benchmem -count=6` on a 4 vCPU runner, before and after (in-memory listener, root namespace): | Benchmark | Before | After | Change | |:--------------------------------------------------|:---------------------|:---------------------|:--------------| | Parse inbound event (name + 3 args) | 2.71 µs, 16 allocs | 0.75 µs, 2 allocs | -72% time | | Build outbound event, JSON argument | 716 ns, 5 allocs | 421 ns, 1 alloc | -41% time | | Build outbound event, raw-text argument | 1.13 µs, 12 allocs | 198 ns, 1 alloc | -82% time | | Round trip: emit, listener, reply (1 connection) | 15.9 µs, 24 allocs | 8.5 µs, 10 allocs | -47% time | | Broadcast to 1024 subscribers | 1.58 ms, 7192 allocs | 0.54 ms, 2009 allocs | -66% time | | Connect: handshake and close | 119 µs, 143 allocs | 140 µs, 155 allocs | +17% time | The connect path is the one that got slower; almost all of it is the websocket middleware's upgrade now running through `net/http`'s upgrader on a fasthttp hijack, which is what makes write coalescing possible, and the same shift appears when the previous socketio code is built against it. ### Configuration All tunables are package-level variables; override before the first connection is accepted. | Variable | Default | Meaning | |:-----------------------|:-------------------|:------------------------------------------------------------------------------| | `PingInterval` | `25s` | How often the server emits Engine.IO PING. | | `PingTimeout` | `20s` | Grace window for the client PONG before the connection is killed. | | `HandshakeTimeout` | `10s` | Hard deadline for completing EIO OPEN + SIO CONNECT. | | `MaxPayload` | `1_000_000` (1 MB) | Max bytes per inbound WebSocket frame; advertised to the client. | | `MaxAuthPayload` | `8 KiB` | Max bytes for the SIO CONNECT auth JSON. | | `MaxBatchPackets` | `256` | Max EIO packets in a single `0x1E`-batched frame. | | `MaxEventNameLength` | `256` | Max length of an inbound SIO event name. | | `MaxEventArgs` | `256` | Max elements (event name included) in an inbound SIO EVENT or ACK array. | | `CloseTimeout` | `5s` | One budget for the whole tear-down after `Close`: waiting for a slot in a saturated send queue, reading for the peer's Close frame and letting a stalled write finish all share it. Zero closes the socket as soon as the frames are queued. | | `WriteTimeout` | `0` (disabled) | Deadline for a single WebSocket frame write by the send goroutine; a peer that stops reading is otherwise only detected by the heartbeat. | | `OutboundAckTimeout` | `30s` | Default ack deadline for `EmitWithAck`. | | `SendQueueSize` | `100` | Capacity of the per-connection outbound queue. | | `DropFramesOnOverflow` | `false` | If true, drop the offending frame on overflow (fires `EventError`). | | `RetrySendTimeout` | `20ms` | Deprecated: no effect. The websocket middleware allocates a connection per upgrade, so there is no released connection to retry against. | | `MaxSendRetry` | `5` | Deprecated: no effect; see `RetrySendTimeout`. | | `ReadTimeout` | `10ms` | Deprecated: no longer consulted by the read loop; kept for backward compatibility. | | `EnablePolling` | `false` | If true, the handler returned from `New` also serves Engine.IO HTTP long-polling on `GET`/`POST`. | | `PollingMaxBufferSize` | `1_000_000` | Cap on a single polling HTTP body (request POST or response GET drain). The drain that ends a session always carries the SIO DISCONNECT and EIO CLOSE packets; farewell frames that do not fit beside them are dropped. | | `MaxPollWait` | `30s` | Maximum time a long-poll GET blocks waiting for outbound frames. | | `PollQueueMaxFrames` | `1024` | Cap on buffered outbound frames per polling session; overflow honors `DropFramesOnOverflow`. The SIO DISCONNECT and EIO CLOSE packets queued by `Close` are exempt, so a queue an `EventClose` listener filled still ends the session cleanly. | Use `socketio.Shutdown(ctx)` from `fiber.App.ShutdownWithContext` for a deterministic drain. ### Go version support We only support the latest two versions of Go. Visit [https://go.dev/doc/devel/release](https://go.dev/doc/devel/release) for more information. ### Install ```sh go get -u github.com/gofiber/fiber/v3 go get -u github.com/gofiber/contrib/v3/socketio ``` ### Protocol compatibility The middleware automatically handles the Engine.IO / Socket.IO handshake so you do **not** need any special server-side code; just point your `socket.io-client` at the WebSocket endpoint. #### Required client configuration The default `socket.io-client` transport order is `['polling', 'websocket']`. The middleware supports both, but polling is **opt-in**: **WebSocket only (default):** ```js import { io } from "socket.io-client"; const socket = io("http://localhost:3000", { path: "/ws", // match the Fiber route transports: ["websocket"], // skip polling }); ``` **Polling (or polling + websocket fallback):** enable `EnablePolling` server-side and mount the handler for both `GET` and `POST`: ```go socketio.EnablePolling = true h := socketio.New(func(kws *socketio.Websocket) { /* ... */ }) app.Get("/ws", h) app.Post("/ws", h) // Optionally allow CORS preflight: // app.Options("/ws", h) ``` ```js import { io } from "socket.io-client"; // Default transport order: polling first, then upgrade attempt. Since this // implementation does not yet upgrade polling sessions to WebSocket, the // session stays on polling. For a forced WebSocket connect use // transports: ["websocket"]; for polling-only use transports: ["polling"]. const socket = io("http://localhost:3000", { path: "/ws", }); ``` > CORS is not handled by the middleware. If your client connects from a different origin, mount your preferred CORS middleware (e.g. `github.com/gofiber/fiber/v3/middleware/cors`) upstream of the route. Long-polling holds a request open for up to ~25s by default, so reverse-proxy timeouts must accommodate (`proxy_read_timeout` >= 60s on nginx, `proxy_buffering off`). ##### Polling pitfalls - **Forgot to mount POST.** Polling clients send packets via POST; without `app.Post(path, h)` (or `app.All(...)`) the server returns 404 and the client loops with `transport error`. Always mount both `GET` and `POST` for polling routes. - **`kws.Conn` is nil on polling.** Use `kws.IsPolling()` to branch, or stick to the transport-agnostic `Emit`, `EmitEvent`, `EmitArgs`, `EmitWithAck*`, `Broadcast`, `Ack`, and `Close` methods. They all work identically on both transports. - **Snapshot vs live request state.** `kws.Locals`, `kws.Params`, `kws.Query`, `kws.Cookies` are captured at session-open time on polling sessions (because fasthttp recycles the request context after the OPEN handler returns). Store mutable per-connection data via `kws.SetAttribute` instead. - **Burst bigger than `PollQueueMaxFrames`.** With the default `DropFramesOnOverflow = false`, emitting more than `PollQueueMaxFrames` (1024) frames before any GET drains them tears the session down with `ErrSendQueueClosed`. Either pace bursts, raise `PollQueueMaxFrames`, or set `DropFramesOnOverflow = true` to drop the offending frames + fire `EventError(ErrSendQueueOverflow)` instead. - **Body limit collision.** If your Fiber app sets `BodyLimit` lower than `PollingMaxBufferSize`, fasthttp rejects the POST before our handler runs. Keep `BodyLimit` >= `PollingMaxBufferSize`. #### Tunable globals These package-level variables can be overridden before the first connection is accepted (typically in `init()` or early in `main`). They control timing and limits for the Engine.IO / Socket.IO transport. | Variable | Default | Description | |:--------------------|:-------------------|:-----------------------------------------------------------------------------------------------------| | `PingInterval` | `25 * time.Second` | Interval between Engine.IO PING frames sent by the server to keep the connection alive. | | `PingTimeout` | `20 * time.Second` | How long the server waits for the client's PONG before considering the connection dead. | | `HandshakeTimeout` | `10 * time.Second` | Maximum time allowed for the Engine.IO / Socket.IO handshake (including namespace CONNECT) to complete. | | `CloseTimeout` | `5 * time.Second` | Budget for the whole tear-down after `Close` or a client SIO DISCONNECT; the socket is closed once the peer's Close frame arrives or the budget is spent, whatever it was spent on. | | `WriteTimeout` | `0` (disabled) | Deadline for a single WebSocket frame write; zero relies on the heartbeat to detect a peer that stopped reading. | | `MaxPayload` | `1 << 20` (1 MiB) | Maximum size in bytes for a single inbound WebSocket frame; oversize messages close the socket. | | `MaxAuthPayload` | `8 << 10` (8 KiB) | Maximum size in bytes for the Socket.IO CONNECT auth JSON. | | `MaxBatchPackets` | `256` | Maximum number of Engine.IO packets accepted in a single `0x1E`-batched frame. | | `MaxEventNameLength`| `256` | Maximum length of an inbound Socket.IO event name. | | `MaxEventArgs` | `256` | Maximum number of elements, event name included, in an inbound Socket.IO EVENT or ACK array. | | `OutboundAckTimeout`| `30 * time.Second` | Default timeout used by `EmitWithAck` when no per-call timeout is supplied. | | `DropFramesOnOverflow` | `false` | If true, saturated outbound queues drop the offending frame and fire `EventError`. | | `RetrySendTimeout` | `20 * time.Millisecond` | Deprecated: no effect; the send goroutine writes each frame exactly once. | | `MaxSendRetry` | `5` | Deprecated: no effect; see `RetrySendTimeout`. | | `EnablePolling` | `false` | If true, the handler also accepts Engine.IO HTTP long-polling on `GET`/`POST` (opt-in fallback). | | `PollingMaxBufferSize` | `1_000_000` | Cap on a single polling HTTP body (POST request body or GET drain response body), in bytes; the packets `Close` queues always fit in the drain that ends the session. | | `MaxPollWait` | `30 * time.Second` | Maximum time a long-poll GET blocks waiting for outbound frames before returning an empty 200. | | `PollQueueMaxFrames`| `1024` | Maximum buffered outbound frames per polling session before overflow handling applies; the packets `Close` queues are exempt. | ```go func init() { socketio.PingInterval = 15 * time.Second socketio.PingTimeout = 10 * time.Second socketio.MaxPayload = 4 << 20 // 4 MiB } ``` #### Message format All messages are exchanged as Socket.IO events. | Side | API call | Wire format | |:----------------|:--------------------------------------|:-----------------------------------| | Server → Client | `kws.Emit([]byte("hello"))` | `42["message","hello"]` | | Server → Client | `kws.EmitEvent("greet", data)` | `42["greet",]` | | Client → Server | `socket.emit("message", obj)` | fires `EventMessage` with `obj` | | Client → Server | `socket.emit("custom", obj)` | fires the `"custom"` event | > **Note:** `Emit`, `EmitEvent`, `EmitArgs`, and ack-emitting variants pass valid JSON through unchanged. Raw text bytes are encoded as JSON strings for compatibility with older examples. #### Acks, namespaces, handshake auth The middleware implements the full Socket.IO v5 ack flow and forwards the client's connect-time auth payload to your handlers. ##### Multi-argument emits `EmitArgs` and `EmitWithAckArgs` accept a variadic list of values, so you can send richer event tuples without manually concatenating arrays. Valid JSON is passed through unchanged; raw text is encoded as a JSON string. ```go // 42["greet","hi",{"id":1}] kws.EmitArgs("greet", []byte(`"hi"`), []byte(`{"id":1}`)) ``` ##### Server-initiated acks `EmitWithAck` (and `EmitWithAckTimeout`) emit an event with an ack id and invoke the supplied callback once the client acks, or with an error when the timeout expires. `EmitWithAck` uses `OutboundAckTimeout`; `EmitWithAckTimeout` takes a per-call duration plus a structured `AckCallback` that distinguishes timeout from disconnect. ```go kws.EmitWithAckTimeout("ping", []byte(`"hello"`), 3*time.Second, func(ack []byte, err error) { if err != nil { log.Printf("ack failed: %v", err) return } // ack is the raw JSON the client passed to its callback (single value // or a JSON-array literal for multi-arg acks). }) ``` ##### Client-initiated acks When the client emits with a callback, the inbound event payload carries an ack id. Use `HasAck` and `AckID` to detect it, then send a single ack reply via `EventPayload.Ack`: ```go socketio.On("greet", func(ep *socketio.EventPayload) { if ep.HasAck { // ep.Args holds the raw JSON arguments the client sent. _ = ep.Ack([]byte(`"ok"`)) } }) ``` ##### Namespaces The middleware honours the namespace negotiated during the Socket.IO CONNECT packet. Events emitted from the server are routed back on the same namespace the client joined; no extra configuration is required on the Go side. ##### Handshake auth The client's `auth` payload must be a JSON object. It is parsed during the Socket.IO handshake and exposed to handlers as `EventPayload.HandshakeAuth` (raw JSON bytes). It is most commonly inspected on `EventConnect`: ```js // client const socket = io("http://localhost:3000", { path: "/ws", transports: ["websocket"], auth: { token: "secret" }, }); ``` ```go socketio.On(socketio.EventConnect, func(ep *socketio.EventPayload) { // ep.HandshakeAuth == []byte(`{"token":"secret"}`) var auth struct{ Token string `json:"token"` } _ = json.Unmarshal(ep.HandshakeAuth, &auth) }) ``` ### Signatures ```go // Initialize new socketio in the callback this will // execute a callback that expects kws *Websocket Object // and optional config websocket.Config func New(callback func(kws *Websocket), config ...websocket.Config) func(fiber.Ctx) error ``` ```go // Add listener callback for an event into the listeners list func On(event string, callback func(payload *EventPayload)) ``` ```go // Emit the message to a specific socket uuids list // Ignores all errors func EmitToList(uuids []string, message []byte, mType ...int) ``` ```go // Emit to a specific socket connection func EmitTo(uuid string, message []byte, mType ...int) error ``` ```go // Broadcast to all the active connections func Broadcast(message []byte, mType ...int) ``` ```go // Fire custom event on all connections func Fire(event string, data []byte) ``` ```go // Emit a named event with multiple arguments // (e.g. EmitArgs("greet", []byte(`"hi"`), []byte(`{"id":1}`))) func (kws *Websocket) EmitArgs(event string, args ...[]byte) ``` ```go // Emit a named event and invoke cb when the client acks (or on timeout / // disconnect). The default deadline is OutboundAckTimeout. The callback // receives the raw JSON ack value (or nil on timeout/disconnect). func (kws *Websocket) EmitWithAck(event string, data []byte, cb func(ack []byte)) ``` ```go // Like EmitWithAck but with a per-call timeout and a structured AckCallback // that distinguishes ErrAckTimeout from ErrAckDisconnected. Pass timeout = 0 // to disable the timeout. func (kws *Websocket) EmitWithAckTimeout(event string, data []byte, timeout time.Duration, cb AckCallback) ``` ```go // Multi-argument variant of EmitWithAck. The callback receives the slice of // raw ack arguments the client supplied (or an error on timeout / // disconnect). Uses OutboundAckTimeout. func (kws *Websocket) EmitWithAckArgs(event string, args [][]byte, cb func([][]byte, error)) ``` ```go // HandshakeAuth returns the raw JSON auth payload sent by the client at // connect time (nil if the client did not provide one). func (kws *Websocket) HandshakeAuth() json.RawMessage ``` ```go // Ack sends a Socket.IO ACK frame back to the client for the inbound event // represented by this payload. Idempotent: only the first invocation // produces a wire frame; later calls return ErrAckAlreadySent. func (ep *EventPayload) Ack(args ...[]byte) error ``` ### Example #### Go server ```go package main import ( "encoding/json" "fmt" "log" "github.com/gofiber/contrib/v3/socketio" "github.com/gofiber/contrib/v3/websocket" "github.com/gofiber/fiber/v3" ) // MessageObject Basic chat message object type MessageObject struct { Data string `json:"data"` From string `json:"from"` Event string `json:"event"` To string `json:"to"` } func main() { // The key for the map is message.to clients := make(map[string]string) // Start a new Fiber application app := fiber.New() // Setup the middleware to retrieve the data sent in first GET request app.Use(func(c fiber.Ctx) error { // IsWebSocketUpgrade returns true if the client // requested upgrade to the WebSocket protocol. if websocket.IsWebSocketUpgrade(c) { c.Locals("allowed", true) return c.Next() } return fiber.ErrUpgradeRequired }) // Multiple event handling supported socketio.On(socketio.EventConnect, func(ep *socketio.EventPayload) { fmt.Printf("Connection event 1 - User: %s", ep.Kws.GetStringAttribute("user_id")) }) // Custom event handling supported socketio.On("CUSTOM_EVENT", func(ep *socketio.EventPayload) { fmt.Printf("Custom event - User: %s", ep.Kws.GetStringAttribute("user_id")) // ---> // DO YOUR BUSINESS HERE // ---> }) // On message event socketio.On(socketio.EventMessage, func(ep *socketio.EventPayload) { fmt.Printf("Message event - User: %s - Message: %s", ep.Kws.GetStringAttribute("user_id"), string(ep.Data)) message := MessageObject{} // Unmarshal the json message // { // "from": "", // "to": "", // "event": "CUSTOM_EVENT", // "data": "hello" //} err := json.Unmarshal(ep.Data, &message) if err != nil { fmt.Println(err) return } // Fire custom event based on some // business logic if message.Event != "" { ep.Kws.Fire(message.Event, []byte(message.Data)) } // Emit the message directly to specified user err = ep.Kws.EmitTo(clients[message.To], ep.Data, socketio.TextMessage) if err != nil { fmt.Println(err) } }) // On disconnect event socketio.On(socketio.EventDisconnect, func(ep *socketio.EventPayload) { // Remove the user from the local clients delete(clients, ep.Kws.GetStringAttribute("user_id")) fmt.Printf("Disconnection event - User: %s", ep.Kws.GetStringAttribute("user_id")) }) // On close event // This event is called when the server disconnects the user actively with .Close() method socketio.On(socketio.EventClose, func(ep *socketio.EventPayload) { // Remove the user from the local clients delete(clients, ep.Kws.GetStringAttribute("user_id")) fmt.Printf("Close event - User: %s", ep.Kws.GetStringAttribute("user_id")) }) // On error event socketio.On(socketio.EventError, func(ep *socketio.EventPayload) { fmt.Printf("Error event - User: %s", ep.Kws.GetStringAttribute("user_id")) }) app.Get("/ws/:id", socketio.New(func(kws *socketio.Websocket) { // Retrieve the user id from endpoint userId := kws.Params("id") // Add the connection to the list of the connected clients // The UUID is generated randomly and is the key that allow // socketio to manage Emit/EmitTo/Broadcast clients[userId] = kws.UUID // Every websocket connection has an optional session key => value storage kws.SetAttribute("user_id", userId) // Broadcast to all the connected users the newcomer newUserMsg, _ := json.Marshal(fmt.Sprintf("New user connected: %s and UUID: %s", userId, kws.UUID)) kws.Broadcast(newUserMsg, true, socketio.TextMessage) // Write welcome message. Raw text is encoded as a JSON string. welcomeMsg, _ := json.Marshal(fmt.Sprintf("Hello user: %s with UUID: %s", userId, kws.UUID)) kws.Emit(welcomeMsg, socketio.TextMessage) })) log.Fatal(app.Listen(":3000")) } ``` #### TypeScript / JavaScript client ```ts import { io } from "socket.io-client"; const socket = io("http://localhost:3000", { path: "/ws", transports: ["websocket"], }); socket.on("connect", () => { console.log("connected, sid =", socket.id); // Send a message to the server socket.emit("message", { from: "user1", to: "user2", event: "", data: "hello", }); }); socket.on("message", (data: unknown) => { console.log("received message:", data); }); socket.on("disconnect", (reason) => { console.log("disconnected:", reason); }); ``` --- ### Supported events | Const | Event | Description | |:----------------|:-------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------| | EventMessage | `message` | Fired when a `socket.emit("message", …)` event is received from the client | | EventPing | `ping` | Fired when a WebSocket PING control frame is received (RFC 6455); `Data` carries its payload. Engine.IO PING is server-originated and not surfaced via this event. | | EventPong | `pong` | Fired when an Engine.IO PONG (`"3"`) replies to the server's heartbeat or when a WebSocket PONG control frame is received. | | EventDisconnect | `disconnect` | Fired exactly once on disconnection. `Error` is nil for a clean close (`Close`, a client SIO DISCONNECT, or a peer Close frame with code 1000, 1001 or none, RFC 6455 section 11.7) and carries the cause otherwise. | | EventConnect | `connect` | Fired after the Engine.IO / Socket.IO handshake completes; `ep.HandshakeAuth` is populated with the client's `auth` payload (raw JSON, nil if not provided) | | EventClose | `close` | Fired exactly once when the connection is actively closed from the server via `Close`, before the closing frames are queued: frames a listener emits still reach the client. Different from client disconnection | | EventError | `error` | Fired when some error appears useful also for debugging websockets | Custom events map directly to the event name used in `socket.emit("myEvent", …)` on the client and `kws.EmitEvent("myEvent", data)` on the server. ### Event Payload object | Variable | Type | Description | |:-----------------|:--------------------|:--------------------------------------------------------------------------------------------------| | Kws | `*Websocket` | The connection object | | Name | `string` | The name of the event | | SocketUUID | `string` | Unique connection UUID | | SocketAttributes | `map[string]any` | Snapshot of the connection's attributes at dispatch time; nil when none were set | | Error | `error` | (optional) Fired from disconnection or error events | | Data | `[]byte` | Raw JSON of the event payload (first argument of `socket.emit`); a sub-slice of the frame the event arrived in, safe to retain | | Args | `[][]byte` | All raw JSON arguments after the event name; useful when the client emits multiple values | | AckID | `uint64` | Ack id assigned by the client when it emitted with a callback (0 if `HasAck` is false) | | HasAck | `bool` | True when the inbound event expects an ack reply; respond via `EventPayload.Ack(args...)` | | HandshakeAuth | `json.RawMessage` | Raw JSON auth payload from the Socket.IO handshake; populated on `EventConnect` listeners (use `Kws.HandshakeAuth()` elsewhere) | ### Socket instance functions | Name | Type | Description | |:--------------------|:-------------------|:---------------------------------------------------------------------------------------------| | SetAttribute | `void` | Set a specific attribute for the specific socket connection | | GetUUID | `string` | Get socket connection UUID | | SetUUID | `error` | Set socket connection UUID | | GetAttribute | `string` | Get a specific attribute from the socket attributes | | EmitToList | `void` | Emit the message to a specific socket uuids list | | EmitTo | `error` | Emit to a specific socket connection | | Broadcast | `void` | Broadcast to all the active connections except broadcasting the message to itself | | Fire | `void` | Fire custom event | | Emit | `void` | Send data as a `"message"` socket.io event; valid JSON is passed through, raw text is JSON-encoded | | EmitEvent | `void` | Send a named socket.io event; valid JSON is passed through, raw text is JSON-encoded | | EmitArgs | `void` | Emit a named event with multiple arguments; valid JSON is passed through, raw text is JSON-encoded | | EmitWithAck | `void` | Emit an event and invoke `cb(ack)` when the client acks (uses `OutboundAckTimeout`) | | EmitWithAckTimeout | `void` | Like `EmitWithAck` but with a per-call timeout and a structured `AckCallback` | | EmitWithAckArgs | `void` | Multi-arg variant; `cb([][]byte, error)` receives the ack tuple (uses `OutboundAckTimeout`) | | HandshakeAuth | `json.RawMessage` | Raw JSON auth payload sent by the client at connect time (nil if absent) | | IsAlive | `bool` | Reports whether the underlying connection is still open and the heartbeat loop is running | | IsPolling | `bool` | Reports whether the session is bound to HTTP long-polling rather than WebSocket; when true, `Conn` is nil | | Close | `void` | Actively close the connection from the server | **Note: the FastHTTP connection can be accessed directly from the instance** ```go kws.Conn ``` `kws.Conn` is `nil` for HTTP long-polling sessions. Code that touches the underlying WebSocket directly should guard with `if kws.Conn != nil` or check the transport via the absence of `kws.Conn`. Listener APIs (`Emit`, `Ack`, `Close`, `Broadcast`, `EmitWithAck`, etc.) work transparently on both transports. --- ## SocketIO Legacy Event Shim Compatibility shim for applications that used older `socketio` releases as a plain WebSocket event bus. Deprecated: import `github.com/gofiber/contrib/v3/websocket/event` directly for new code. The root `github.com/gofiber/contrib/v3/socketio` package is reserved for the Engine.IO / Socket.IO protocol and clients such as `socket.io-client`. ### Migration Preferred import: ```go import "github.com/gofiber/contrib/v3/websocket/event" ``` Temporary compatibility import: ```go import "github.com/gofiber/contrib/v3/socketio/legacy" ``` The shim re-exports the old event-bus surface: ```go legacy.On(legacy.EventMessage, func(ep *legacy.EventPayload) { ep.Kws.Emit([]byte("pong"), legacy.TextMessage) }) app.Get("/ws", legacy.New(func(kws *legacy.Websocket) {})) ``` Set tuning globals such as `PongTimeout`, `SendQueueSize`, or `MaxSendRetry` on `github.com/gofiber/contrib/v3/websocket/event` directly before accepting connections. --- ## SPNEGO Kerberos Authentication Middleware for Fiber ![Release](https://img.shields.io/github/v/tag/gofiber/contrib?filter=*spnego*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://github.com/gofiber/contrib/workflows/Test%20spnego/badge.svg) This middleware provides SPNEGO (Simple and Protected GSSAPI Negotiation Mechanism) authentication for [Fiber](https://github.com/gofiber/fiber) applications, enabling Kerberos authentication for HTTP requests and inspired by [gokrb5](https://github.com/jcmturner/gokrb5) ### Features - Kerberos authentication via SPNEGO mechanism - Flexible keytab lookup system - Support for dynamic keytab retrieval from various sources - Integration with Fiber context for authenticated identity storage - Group-based authorization from Active Directory's PAC, with no extra parsing - Optional sessions, so an authenticated client is not revalidated per request - Keytab principal, clock skew and host-address controls - Success and failure hooks for metrics and audit - Configurable logging ### Version Compatibility This middleware is compatible with: - **Fiber v3** ### Installation ```bash # For Fiber v3 go get github.com/gofiber/contrib/v3/spnego ``` ### Usage ```go package main import ( "fmt" "os" "path/filepath" "time" "github.com/gofiber/contrib/v3/spnego" "github.com/gofiber/contrib/v3/spnego/utils" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/log" ) func main() { app := fiber.New() // Create a configuration with a keytab lookup function // For testing, you can create a mock keytab file using utils.NewMockKeytab // In production, use a real keytab file // A temporary directory keeps key material out of the working directory. tempDir, err := os.MkdirTemp("", "spnego-example") if err != nil { panic(fmt.Errorf("create temp dir failed: %w", err)) } // panic rather than log.Fatalf below: Fatalf calls os.Exit, which would // skip these deferred cleanups and leave the keytab on disk. defer func() { _ = os.RemoveAll(tempDir) }() keytabPath := filepath.Join(tempDir, "temp-sso.keytab") _, clean, err := utils.NewMockKeytab( utils.WithPrincipal("HTTP/sso1.example.com"), utils.WithRealm("EXAMPLE.LOCAL"), utils.WithFilename(keytabPath), utils.WithPairs(utils.EncryptTypePair{ Version: 2, EncryptType: 18, CreateTime: time.Now(), }), ) if err != nil { panic(fmt.Errorf("create mock keytab failed: %w", err)) } defer clean() keytabLookup, err := spnego.NewKeytabFileLookupFunc(keytabPath) if err != nil { panic(fmt.Errorf("create keytab lookup failed: %w", err)) } // Create the middleware authMiddleware, err := spnego.New(spnego.Config{ KeytabLookup: keytabLookup, }) if err != nil { panic(fmt.Errorf("create middleware failed: %w", err)) } // Apply the middleware to protected routes app.Use("/protected", authMiddleware) // Access authenticated identity app.Get("/protected/resource", func(c fiber.Ctx) error { identity, ok := spnego.GetAuthenticatedIdentityFromContext(c) if !ok { return c.Status(fiber.StatusUnauthorized).SendString("Unauthorized") } return c.SendString(fmt.Sprintf("Hello, %s!", identity.UserName())) }) log.Info("Starting server on :3000") if err := app.Listen(":3000"); err != nil { panic(fmt.Errorf("start server failed: %w", err)) } } ``` ### Dynamic Keytab Lookup The middleware is designed with extensibility in mind, allowing keytab retrieval from various sources beyond static files: > **`KeytabLookup` is called on every request the middleware handles, authenticated or not.** The lookup runs before the ticket is examined, so a client that presents no credentials at all still drives one call per request — size any rate limit or timeout for traffic you do not control. It also sits in the request hot path, so a lookup that contacts an external system adds its latency, and its failure modes, to every request. If you fetch from a database, a secrets manager, or a remote service, cache the result and refresh it out of band (on a TTL or a rotation event), and apply a bounded timeout. > > `NewKeytabFileLookupFunc` already does this: it caches the merged keytab and re-reads the files only when one of them changes size, modification time or identity, so rotating a keytab on disk still takes effect without paying for a parse per request. What it does still pay is one `stat` per keytab file per request, which is what makes a rotation — or a revocation — take effect on the next request rather than after some interval. If that cost matters more to you than immediacy, supply your own `KeytabLookup` that re-reads on a timer. > > Change detection compares each file's size, modification time and identity. On Unix that means replacing a keytab by rename is picked up even when the staging tool preserved the timestamp of a same-sized file, because identity there is the inode. On Windows it is the creation time, which is a hint rather than a dependable identity — NTFS file tunneling can restore a replaced file's creation time, and some filesystems report none — and on other platforms there is no identity at all; both fall back to size and modification time. > > Detection is not exact even on Unix: an in-place rewrite that keeps the same size and lands within one filesystem timestamp tick looks unchanged, since the file's identity does not change either. Rotating by rename avoids that case on Unix. Elsewhere, where identity cannot be relied on, make sure the replacement differs in size or modification time. > > A keytab that reads but does not parse is treated as a rotation caught mid-write: the last keytab that parsed cleanly keeps being served, and a retry runs at most once a second while the fault lasts. That cover expires after 30 seconds — long enough to absorb a half-written file, short enough that a rotation to a permanently corrupt keytab surfaces as an error instead of silently keeping superseded keys alive. Entering the degraded state logs a warning, expiry logs an error, and recovery logs an all-clear at warning level too, so a deployment filtering below warnings still sees each alert close. Each is emitted once per episode rather than per request, and an episode can only begin when the files on disk change, so the rate follows rotations rather than request volume. Note that the lines are not ordered against each other across concurrent requests: an all-clear can occasionally print above the warning it clears, so match them on content rather than on arrival order. The lines are written after the reload lock is released, so a blocking log sink cannot queue every other authenticated request behind it — though the request that triggered the transition does wait for the write, as it would for any log call. A keytab that cannot be read at all — deleted, unmounted, replaced by something unreadable — is reported as an error instead of being masked by the cache. Note that revoking a keytab with `chmod` alone is not enough: changing permissions moves only the file's ctime, which the cache does not track, so an already-loaded keytab keeps being served until something else changes. Remove or replace the file to revoke it. A `KeytabLookupFunc` is any function returning a `*keytab.Keytab`, so the source is yours to choose: ```go // Example: Retrieve keytab from a database func dbKeytabLookup() (*keytab.Keytab, error) { // Your database lookup logic here // ... return keytabFromDatabase, nil } // Example: Retrieve keytab from a remote service func remoteKeytabLookup() (*keytab.Keytab, error) { // Your remote service call logic here // ... return keytabFromRemote, nil } ``` > **Whatever error your lookup returns is logged and passed to `OnError`, so it must not carry anything secret.** That is easy to get wrong with a keytab: gokrb5's length checks interpolate the whole buffer they were handed, which for a single-principal keytab is the entire key. So `return keytab.Load(path)` writes that key to your logs whenever a file ends earlier than its own headers say — which is exactly what a rotation caught mid-write looks like. > > Only the parse cause has to go. Reading the file is where the diagnostics worth keeping are — a missing file, a permission change — and none of that carries the contents, so separate the two rather than flattening both into one message: > > ```go > raw, err := os.ReadFile(path) > if err != nil { > return nil, fmt.Errorf("reading %s: %w", path, err) > } > kt := keytab.New() > if err := kt.Unmarshal(raw); err != nil { > return nil, fmt.Errorf("%s did not parse (%d bytes read)", path, len(raw)) > } > ``` > > `NewKeytabFileLookupFunc` already does this, which is the other reason to prefer it over a hand-rolled file loader. #### Errors A failed keytab lookup is answered with a bare `500`. The detail from `NewKeytabFileLookupFunc` names the keytab's path, and for a file it could not read the underlying OS error too — a file that read but did not parse reports only its length, for the reason above. A custom `KeytabLookupFunc` may report whatever it likes. None of it should reach an unauthenticated caller, so the response body carries only `Internal Server Error`. The detail is logged at error level instead, throttled to one line per 30 seconds, so a persistent fault cannot be turned into a log flood by unauthenticated callers. The status is always `500`: a `*fiber.Error` returned by your own `KeytabLookupFunc` deliberately does not set the response status, since an infrastructure fault reported as, say, `401` would make clients retry credentials against a server that cannot check them. There are two sentinels, and both reach the `ErrorHandler` and `OnError` the same way, with the same sanitised body: | Sentinel | Raised when | | --- | --- | | `ErrLookupKeytabFailed` | `KeytabLookup` returned an error, or no keytab | | `ErrSPNEGOHandlerFailed` | gokrb5 stopped short of an authentication outcome — in v8.4.4, a `SessionManager` that could not persist a session | The returned error still matches them, so an application `ErrorHandler` can tell either from any other 500. Match on both: matching only the first misses every broken-session-store failure. ```go app := fiber.New(fiber.Config{ ErrorHandler: func(c fiber.Ctx, err error) error { if errors.Is(err, spnego.ErrLookupKeytabFailed) || errors.Is(err, spnego.ErrSPNEGOHandlerFailed) { alertOnCall() } return fiber.DefaultErrorHandler(c, err) }, }) ``` ### API Reference #### `New(cfg Config) (fiber.Handler, error)` Creates a new SPNEGO authentication middleware. #### `GetAuthenticatedIdentityFromContext(ctx FiberContext) (goidentity.Identity, bool)` Retrieves the authenticated identity from the Fiber context. `FiberContext` is any type exposing `Locals(key any, value ...any) any`, which `fiber.Ctx` satisfies, so handlers call this as `GetAuthenticatedIdentityFromContext(c)`. #### `NewKeytabFileLookupFunc(keytabFiles ...string) (KeytabLookupFunc, error)` Creates a new KeytabLookupFunc that loads keytab files, caching the merged result until a file changes on disk. At least one path is required and none may be empty — an unset environment variable is refused here rather than at every request. #### `NewSystemKeytabLookupFunc() (KeytabLookupFunc, error)` Creates a KeytabLookupFunc for the host's system keytab: `$KRB5_KTNAME` when set, otherwise `/etc/krb5.keytab`. MIT Kerberos writes `KRB5_KTNAME` as an optional `TYPE:residual` pair. The file-backed types (`FILE:`, `WRFILE:`) are accepted and stripped, and the known types that cannot be read as a file (`KEYRING:`, `MEMORY:`, `DIR:`, `ANY:`, `SRVTAB:`) are rejected at construction with `ErrUnsupportedKeytabResidualType` rather than being treated as a literal path that fails on every request. A prefix matching no known type is taken as part of the filename, since a relative path may legitimately contain a colon. A typo such as `FIEL:/etc/krb5.keytab` is therefore resolved as a path and fails when the file cannot be found — at construction if `FallbackToSystemKeytab` is set, otherwise on the first request. Note that this resolves a *keytab*, not a credential cache. Accepting a SPNEGO ticket requires the service's long-term keys, so a client credential cache (`KRB5CCNAME`) cannot be used to authenticate incoming requests. ### Configuration The `Config` struct supports the following fields: - `Next`: A `func(fiber.Ctx) bool` that, when it returns true, skips authentication for that request — useful for health probes and CORS preflights (optional) - `KeytabLookup`: A function that retrieves the keytab (required, unless `FallbackToSystemKeytab` is set) - `Log`: A `*log.Logger` receiving gokrb5's diagnostics (optional, defaults to no logging) - `UseFiberLogger`: Send gokrb5's diagnostics to Fiber's default logger when `Log` is nil (optional, defaults to `false`) - `Unauthorized`: A `func(fiber.Ctx) error` invoked when a presented Kerberos ticket is rejected, in place of SPNEGO's own rejection response (optional) - `FallbackToSystemKeytab`: Load the system keytab when `KeytabLookup` is nil, instead of rejecting the configuration (optional, defaults to `false`). The resolved keytab is loaded once during `New`, so a misconfigured host fails at startup rather than on every request. - `KeytabPrincipal`: Pin which principal's key decrypts an incoming ticket, out of a keytab holding several (optional, defaults to `""` — gokrb5 infers it from the ticket). See [Restricting the accepted principal](#restricting-the-accepted-principal). - `MaxClockSkew`: How far a ticket's issue time may sit from this host's clock (optional, defaults to `0`, which leaves gokrb5's own 5 minutes). A negative value is rejected by `New`. - `DisablePACDecoding`: Turn off decoding of Active Directory's PAC (optional, defaults to `false` — the PAC *is* decoded). Decoding is what populates the group SIDs behind `Identity.Authorized`, so only disable it on a service that never inspects groups. - `RequireHostAddress`: Reject a ticket that carries no host addresses (optional, defaults to `false`) - `SessionManager`: A `service.SessionMgr` letting gokrb5 serve later requests from a session instead of revalidating a ticket each time (optional, defaults to `nil`). See [Sessions](#sessions). - `OnSuccess`: A `func(fiber.Ctx, goidentity.Identity)` called once per authenticated request, before the rest of the chain (optional, defaults to `nil`) - `OnError`: A `func(fiber.Ctx, error)` called when the middleware itself fails — a keytab lookup failure, or a failure inside the SPNEGO handler such as a session store that cannot be reached — just before the response (optional, defaults to `nil`) `ConfigDefault` records these defaults if you would rather start from it than from a zero `Config` — the two are equal. `New` applies them by treating a zero field as unset rather than by reading the variable, so copy it; mutating it changes nothing. #### Group-based authorization Against Active Directory the middleware gives you more than a username. AD embeds a PAC in the ticket, gokrb5 decodes it by default, and the caller's group SIDs land on the identity — so authorization needs no extra parsing: ```go const admins = "S-1-5-21-1004336348-1177238915-682003330-512" app.Get("/admin", authMiddleware, func(c fiber.Ctx) error { identity, ok := spnego.GetAuthenticatedIdentityFromContext(c) if !ok || !identity.Authorized(admins) { return fiber.ErrForbidden } return c.SendString("Hello, " + identity.UserName()) }) ``` `identity.AuthzAttributes()` returns every SID if you would rather match against a set. For the full AD payload — effective name, logon domain, primary group — type-assert to gokrb5's concrete type: ```go if creds, ok := identity.(*credentials.Credentials); ok { ad := creds.GetADCredentials() log.Printf("%s logged on from %s", ad.EffectiveName, ad.LogonDomainName) } ``` MIT Kerberos does not normally issue a PAC, so on a non-AD realm expect `AuthzAttributes()` to be empty and authorize on the principal name instead. #### Restricting the accepted principal Left unset, gokrb5 infers the decrypting key from the ticket, so a ticket naming **any** principal in the keytab is accepted. That is the right behaviour for one service with one SPN, and the wrong one as soon as a keytab is merged from several: a client entitled to one of them gets into all of them. `KeytabPrincipal` pins the key, so a ticket minted for a different principal fails to decrypt and is refused. Set it whenever the keytab covers more than the service sitting behind it. ```go authMiddleware, err := spnego.New(spnego.Config{ KeytabLookup: keytabLookup, KeytabPrincipal: "HTTP/sso.example.com", }) ``` Give the principal without a realm. gokrb5 parses this with `types.ParseSPNString`, which drops anything after an `@` without reporting it, so `New` rejects a value containing one rather than letting it look effective. If you went looking for gokrb5's `service.SName` and wondered why it is not exposed: it has no effect here. In v8.4.4 the SPNEGO accept path runs `service.VerifyAPREQ`, which never reads it — the only reader is `KRB5BasicAuthenticator`, a different flow. Wiring it up would promise a restriction that does nothing. #### Sessions `SessionManager` is the largest saving available on an authenticated hot path: gokrb5 establishes a session after the first successful authentication and serves later requests from it, skipping ticket validation entirely. It also changes the trust model. The session becomes a credential in its own right, so whatever your implementation stores must be unguessable, bound to the client, and expired deliberately — a predictable session identifier is a full authentication bypass. The middleware forwards the request's cookies to gokrb5 only when this is set. What the manager writes is replayed selectively. **Headers** — `Set-Cookie` included — reach the Fiber response on any request that gets to an authentication outcome, meaning one that authenticates as well as one that gets a challenge or a refusal. The **body and status do not**, ever: they belong to the handler the request is being authenticated for, and are overwritten by it. Report through headers, or through your own logger. On a request that instead fails inside the handler, all three are dropped, so a cookie written just before the failure never advertises a session that was not stored. The request handed to your manager is built by this middleware rather than by `net/http`, so it carries only what gokrb5 reads plus what a session store needs to make its decisions: method, host, remote address, cookies, **the `Authorization` header**, protocol, `URL.Scheme`, `TLS`, and the context. `Body` is `http.NoBody` — non-nil, so the usual `defer r.Body.Close()` is safe, but empty. `RequestURI` and the URL's path and query are deliberately absent, so do not key a session on them. `Authorization` is credential material — the client's base64 Kerberos ticket, which is what gokrb5 reads it for. Do not log or serialise `r.Header` wholesale from a manager. Only `Flush` is provided beyond the `http.ResponseWriter` methods themselves, and it buffers rather than sending — the middleware has to see the whole response before it can tell an authentication outcome from a failure, so nothing streams through here. Probe for anything else with comma-ok or `http.ResponseController`; a bare `w.(http.Hijacker)` panics, and there is no connection behind this writer to hijack. `r.Context()` is Fiber's, so a deadline or a tracing span installed by middleware upstream reaches your store through the usual `QueryContext`. Do not expect more of it than the application put there: Fiber's context is `context.Background()` unless something calls `SetContext`, and Fiber does not cancel it when a client disconnects. Its strings are yours to keep. Fiber hands out header and URI values as views into fasthttp's request buffer, and fasthttp pools those buffers across connections — so retaining one normally means reading back whichever client came next, not just more of the same one. This middleware copies them before building the request, so a manager that stores `r.Host` alongside a session records the host it actually saw. The copies are made only when `SessionManager` is set, since nothing else outlives the buffer. The context is the one field that promise cannot cover, because its contents are the application's. `*fasthttp.RequestCtx` satisfies `context.Context`, so an app that does `c.SetContext(context.WithValue(c.RequestCtx(), k, v))` hands your manager a value chain reaching into the very buffer Fiber recycles. If you retain anything you pulled out of the context, copy it. `Secure: r.TLS != nil` — the usual idiom — is reliable only where Fiber terminates TLS itself. Behind a TLS-terminating proxy the connection into Fiber is plaintext, so `TLS` is nil. `URL.Scheme` is not a drop-in replacement: Fiber returns `http` regardless of `X-Forwarded-Proto` unless `Config.TrustProxy` is on **and** the peer matches `TrustProxyConfig`. So behind a proxy, both fields can say "not secure" for a connection the client made over HTTPS. Either configure `TrustProxy` for your proxy's address and then trust the scheme, or set `Secure` unconditionally if the service is only ever reached over HTTPS. The two failure paths are not symmetric, which matters for monitoring. A manager whose `New` cannot persist makes gokrb5 abandon the request from inside its handler; the middleware treats that as an internal failure rather than an ordinary response — logged on its own throttle, passed to `OnError` as `ErrSPNEGOHandlerFailed`, and answered with the same sanitised `Internal Server Error` body a keytab failure gets, so a manager that reports a DSN or a driver error cannot leak it to an unauthenticated caller. A manager whose `Get` fails is different: gokrb5 discards that error and falls through to full ticket validation, so a broken read path degrades performance silently and is worth watching on the store's own side. The two are told apart by the error your `New` returns, and by `WWW-Authenticate` — never by the status. The status is only ever the first one written, and a manager holding the raw `ResponseWriter` can write its own before it fails, which would otherwise mask the failure as an ordinary `200` or `4xx`. One caveat for whoever is on call. gokrb5 sends your manager's actual error to *its own* logger and writes only `Internal Server Error` to the response, so that boilerplate is what `ErrSPNEGOHandlerFailed` carries unless your manager wrote something itself. To see why the store refused, set `Log` or `UseFiberLogger` — or log it inside your manager, which is the more direct route. What it does carry is quoted and capped at 512 bytes, with a `(+N bytes)` suffix when there was more. Every string in this package's logs that the package did not write is treated that way — a manager's body, a `KeytabLookupFunc`'s error, a keytab path — because a newline in any of them would otherwise start a line of its own under the `spnego:` prefix. Quoting is not redaction: it keeps text from forging log lines and bounds how much of it lands, but a secret inside the cap still reaches the log, which is why what you return from a `KeytabLookupFunc` is yours to keep clean. gokrb5's own keytab parse errors are not quoted but dropped. They interpolate the whole file they were handed, and that file is key material: for a single-principal keytab the entire key fits inside the 512-byte cap, so quoting would have bounded it and logged it anyway. What is reported instead is the file's name, that it did not parse, and how many bytes were read — a rotation caught mid-write is short, a corrupt file usually is not. #### Observability `OnSuccess` and `OnError` are for metrics and audit; neither can change the outcome. ```go authMiddleware, err := spnego.New(spnego.Config{ KeytabLookup: keytabLookup, OnSuccess: func(c fiber.Ctx, identity goidentity.Identity) { authSuccesses.Inc() }, OnError: func(c fiber.Ctx, err error) { authInternalErrors.Inc() }, }) ``` The two do not cover every outcome, deliberately. `OnSuccess` fires only for a request that authenticated — a challenge, a continuation and a rejection are all *not* authenticated, and counting them as successes would overstate logins. `OnError` fires only when the middleware itself fails; a refused ticket is `Unauthorized`'s business, not an internal error. The error `OnError` receives carries the underlying cause, which names keytab paths and OS errors, so treat it as internal diagnostics rather than something to echo back. #### Behind a reverse proxy gokrb5 binds ticket validation to the connection's peer address, which behind a proxy is the proxy rather than the client. A client presenting an address-restricted ticket will therefore be refused. The middleware deliberately does **not** read `X-Forwarded-For` here: doing so would let a client choose the address its own ticket is validated against, which defeats the restriction entirely. If your clients use address-restricted tickets, terminate Kerberos at the edge or issue tickets without address restrictions. #### Logging gokrb5 writes a line for every request carrying a `Negotiate` token, with no level of its own: one on success, one on a refusal, and one on a token it cannot parse. On a busy service that is a line per request, so enabling this is not cheap on authenticated traffic. It is silent only for requests it declines to negotiate at all — no `Authorization` header, or one for a different scheme. One case escapes that rule: gokrb5 parses the client address before it looks at the token and logs when it cannot, so a listener whose remote addresses are not `host:port` — a Unix socket, for instance — produces a line on every request, including the opening challenge. Leaving both `Log` and `UseFiberLogger` unset keeps all of that off the log. Setting either one opts in, and note that neither honours Fiber's log level. #### Customizing the unauthorized response ```go authMiddleware, err := spnego.New(spnego.Config{ KeytabLookup: keytabLookup, Unauthorized: func(c fiber.Ctx) error { return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ "error": "kerberos authentication required", }) }, }) ``` **This handler runs only when Kerberos authentication is actually refused** — when a client presented a ticket the service rejected. gokrb5 answers `401` in three different situations, and only one of them is a failure: | WWW-Authenticate | Meaning | Reaches your handler | |---|---|---| | `Negotiate` (bare) | Opening challenge that starts the handshake | No | | `Negotiate oRQwEqADCgEB…` | Continuation token: retry with KRB5 | No | | `Negotiate oQcwBaADCgEC` | Ticket rejected | **Yes** | (Success is signalled by `Negotiate oRQwEqADCgEA…`, which differs from the continuation value only at the twelfth base64 character — `CgEA` versus `CgEB`.) The first two are steps in a negotiation that can still succeed. `curl --negotiate` and every major browser begin SPNEGO only when an untouched `401` arrives, so rewriting either one — changing the status to `403`, say — would stop authentication from ever completing. They are always passed straight through, and your handler never sees them. One limitation worth knowing: gokrb5 sends the continuation value not only for a genuine continue, but also for a `Negotiate` header that fails base64 decoding or does not unmarshal as SPNEGO or raw KRB5. An NTLM-only client is therefore indistinguishable from a client mid-handshake, and those failures never reach your handler. Classifying them as rejections instead would break real continuations, which is the worse failure. The rejection response's headers are already set when your handler runs, so setting a status and body leaves them in place. Removing the `WWW-Authenticate` header will break negotiation. ### Requirements - Fiber v3 - Kerberos infrastructure ### Notes - Ensure your Kerberos infrastructure is properly configured - The middleware handles the SPNEGO negotiation process - Retrieve authenticated identities with `GetAuthenticatedIdentityFromContext` --- ## Swagger UI > ⚠️ This module was renamed from `gofiber/contrib/swagger` to `swaggerui` to clearly distinguish it from the ported `swaggo` middleware. Update your imports accordingly. ![Release](https://img.shields.io/github/v/tag/gofiber/contrib?filter=*swaggerui*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://github.com/gofiber/contrib/workflows/Test%20swaggerui/badge.svg) Swagger UI middleware for [Fiber](https://github.com/gofiber/fiber). This handler serves pre-generated Swagger/OpenAPI specs via the swagger-ui package. **Compatible with Fiber v3.** ### Go version support We only support the latest two versions of Go. Visit [https://go.dev/doc/devel/release](https://go.dev/doc/devel/release) for more information. #### Table of Contents - [Signatures](#signatures) - [Installation](#installation) - [Examples](#examples) - [Config](#config) - [Default Config](#default-config) #### Signatures ```go func New(config ...swaggerui.Config) fiber.Handler ``` #### Installation Swagger is tested on the latests [Go versions](https://golang.org/dl/) with support for modules. So make sure to initialize one first if you didn't do that yet: ```bash go mod init github.com// ``` And then install the Swagger UI middleware: ```bash go get github.com/gofiber/contrib/v3/swaggerui ``` #### Examples Import the middleware package ```go import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/contrib/v3/swaggerui" ) ``` Using the default config: ```go app.Use(swaggerui.New()) ``` Using a custom config: ```go cfg := swaggerui.Config{ BasePath: "/", FilePath: "./docs/swagger.json", Path: "swagger", Title: "Swagger API Docs", } app.Use(swaggerui.New(cfg)) ``` Use program data for Swagger content: ```go cfg := swaggerui.Config{ BasePath: "/", FilePath: "./docs/swagger.json", FileContent: mySwaggerByteSlice, Path: "swagger", Title: "Swagger API Docs", } app.Use(swaggerui.New(cfg)) ``` Using multiple instances of Swagger: ```go // Create Swagger middleware for v1 // // Swagger will be available at: /api/v1/docs app.Use(swaggerui.New(swaggerui.Config{ BasePath: "/api/v1/", FilePath: "./docs/v1/swagger.json", Path: "docs", })) // Create Swagger middleware for a second API version // // Swagger will be available at: /api/v2/docs app.Use(swaggerui.New(swaggerui.Config{ BasePath: "/api/v2/", FilePath: "./docs/v2/swagger.json", Path: "docs", })) ``` #### Config ```go type Config struct { // Next defines a function to skip this middleware when returned true. // // Optional. Default: nil Next func(c fiber.Ctx) bool // BasePath for the UI path // // Optional. Default: / BasePath string // FilePath for the swagger.json or swagger.yaml file // // Optional. Default: ./swagger.json FilePath string // FileContent for the content of the swagger.json or swagger.yaml file. // If provided, FilePath will not be read. // // Optional. Default: nil FileContent []byte // Path combines with BasePath for the full UI path // // Optional. Default: docs Path string // Title for the documentation site // // Optional. Default: Fiber API documentation Title string // CacheAge defines the max-age for the Cache-Control header in seconds. // // Optional. Default: 3600 (1 hour) CacheAge int } ``` #### Default Config ```go var ConfigDefault = Config{ Next: nil, BasePath: "/", FilePath: "./swagger.json", Path: "docs", Title: "Fiber API documentation", CacheAge: 3600, // Default to 1 hour } ``` --- ## Swaggo > ⚠️ This module was renamed from `gofiber/swagger` to `swaggo` to clearly distinguish it from the ported `swaggerui` middleware. Update your imports accordingly. ![Release](https://img.shields.io/github/v/tag/gofiber/contrib?filter=*swaggo*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://github.com/gofiber/contrib/actions/workflows/test-swaggo.yml/badge.svg) Swaggo replaces the archived [github.com/gofiber/swagger](https://github.com/gofiber/swagger) module with an actively maintained drop-in generator for [Fiber](https://github.com/gofiber/fiber) v3. It mounts the official Swagger UI, serves the assets required by [swaggo/swag](https://github.com/swaggo/swag) generated documentation, and exposes helper utilities to wire the docs into any Fiber application. **Compatible with Fiber v3.** ### Go version support We only support the latest two versions of Go. Visit [https://go.dev/doc/devel/release](https://go.dev/doc/devel/release) for more information. #### Table of Contents - [Signatures](#signatures) - [Installation](#installation) - [Usage](#usage) - [Config](#config) - [Default Config](#default-config) #### Signatures ```go var HandlerDefault = New() func New(config ...Config) fiber.Handler ``` #### Installation Swagger Doc Generator is tested on the latest [Go versions](https://go.dev/dl/) with support for modules. Make sure to initialize one first if you have not done that yet: ```bash go mod init github.com// ``` Then install the middleware: ```bash go get github.com/gofiber/contrib/v3/swaggo ``` #### Usage First, document your API using swaggo/swag comments and generate the documentation files (usually inside a `docs` package) by running `swag init`. ```go package main import ( "github.com/gofiber/fiber/v3" swaggo "github.com/gofiber/contrib/v3/swaggo" // docs are generated by Swag CLI, you have to import them. // Replace with your own docs folder, usually "github.com/username/reponame/docs". _ "github.com/username/reponame/docs" ) func main() { app := fiber.New() // Mount the UI with the default configuration under /swagger app.Get("/swagger/*", swaggo.HandlerDefault) // Customize the UI by passing a Config app.Get("/docs/*", swaggo.New(swaggo.Config{ URL: "http://example.com/doc.json", DeepLinking: false, DocExpansion: "none", OAuth2RedirectUrl: "http://localhost:8080/swagger/oauth2-redirect.html", })) app.Listen(":8080") } ``` #### Config ```go type Config struct { InstanceName string Title string ConfigURL string URL string QueryConfigEnabled bool Layout string Plugins []template.JS Presets []template.JS DeepLinking bool DisplayOperationId bool DefaultModelsExpandDepth int DefaultModelExpandDepth int DefaultModelRendering string DisplayRequestDuration bool DocExpansion string Filter FilterConfig MaxDisplayedTags int ShowExtensions bool ShowCommonExtensions bool TagsSorter template.JS OnComplete template.JS SyntaxHighlight *SyntaxHighlightConfig TryItOutEnabled bool RequestSnippetsEnabled bool OAuth2RedirectUrl string RequestInterceptor template.JS RequestCurlOptions []string ResponseInterceptor template.JS ShowMutatedRequest bool SupportedSubmitMethods []string ValidatorUrl string WithCredentials bool ModelPropertyMacro template.JS ParameterMacro template.JS PersistAuthorization bool OAuth *OAuthConfig PreauthorizeBasic template.JS PreauthorizeApiKey template.JS CustomStyle template.CSS CustomScript template.JS } ``` #### Default Config ```go var ConfigDefault = Config{ Title: "Swagger UI", Layout: "StandaloneLayout", URL: "doc.json", DeepLinking: true, ShowMutatedRequest: true, Plugins: []template.JS{ template.JS("SwaggerUIBundle.plugins.DownloadUrl"), }, Presets: []template.JS{ template.JS("SwaggerUIBundle.presets.apis"), template.JS("SwaggerUIStandalonePreset"), }, SyntaxHighlight: &SyntaxHighlightConfig{Activate: true, Theme: "agate"}, } ``` > Refer to `config.go` for a complete list of options and documentation strings. --- ## Testcontainers ![Release](https://img.shields.io/github/v/tag/gofiber/contrib?filter=*testcontainers*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://github.com/gofiber/contrib/workflows/Test%20Testcontainers%20Services/badge.svg) A [Testcontainers](https://golang.testcontainers.org/) Service Implementation for Fiber. :::note Requires Go **1.26** and above ::: **Compatible with Fiber v3.** ### Go version support We only support the latest two versions of Go. Visit [https://go.dev/doc/devel/release](https://go.dev/doc/devel/release) for more information. > Test requirement: integration tests for this package require a reachable Docker daemon. ### Common Use Cases - Local development - Integration testing - Isolated service testing - End-to-end testing ### Install :::caution This Service Implementation only supports Fiber **v3**. ::: ```shell go get -u github.com/gofiber/fiber/v3 go get -u github.com/gofiber/contrib/v3/testcontainers ``` ### Signature #### NewModuleConfig ```go // NewModuleConfig creates a new container service config for a module. // // - The serviceKey is the key used to identify the service in the Fiber app's state. // - The img is the image name to use for the container. // - The run is the function to use to run the container. It's usually the Run function from the module, like [redis.Run] or [postgres.Run]. // - The opts are the functional options to pass to the run function. This argument is optional. func NewModuleConfig[T testcontainers.Container]( serviceKey string, img string, run func(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (T, error), opts ...testcontainers.ContainerCustomizer, ) Config[T] { ``` #### NewContainerConfig ```go // NewContainerConfig creates a new container service config for a generic container type, // not created by a Testcontainers module. So this function best used in combination with // the [AddService] function to add a custom container to the Fiber app's state. // // - The serviceKey is the key used to identify the service in the Fiber app's state. // - The img is the image name to use for the container. // - The opts are the functional options to pass to the [testcontainers.Run] function. This argument is optional. // // This function uses the [testcontainers.Run] function as the run function. func NewContainerConfig[T *testcontainers.DockerContainer](serviceKey string, img string, opts ...testcontainers.ContainerCustomizer) Config[*testcontainers.DockerContainer] ``` #### AddService ```go // AddService adds a Testcontainers container as a [fiber.Service] for the Fiber app. // It returns a pointer to a [ContainerService[T]] object, which contains the key used to identify // the service in the Fiber app's state, and an error if the config is nil. // The container should be a function like redis.Run or postgres.Run that returns a container type // which embeds [testcontainers.Container]. // - The cfg is the Fiber app's configuration, needed to add the service to the Fiber app's state. // - The containerConfig is the configuration for the container, where: // - The containerConfig.ServiceKey is the key used to identify the service in the Fiber app's state. // - The containerConfig.Run is the function to use to run the container. It's usually the Run function from the module, like redis.Run or postgres.Run. // - The containerConfig.Image is the image to use for the container. // - The containerConfig.Options are the functional options to pass to the [testcontainers.Run] function. This argument is optional. // // Use [NewModuleConfig] or [NewContainerConfig] helper functions to create valid containerConfig objects. func AddService[T testcontainers.Container](cfg *fiber.Config, containerConfig Config[T]) (*ContainerService[T], error) { ``` ### Types #### Config The `Config` type is a generic type that is used to configure the container. | Property | Type | Description | Default | |-------------|------|-------------|---------| | ServiceKey | string | The key used to identify the service in the Fiber app's state. | - | | Image | string | The image name to use for the container. | - | | Run | func(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (T, error) | The function to use to run the container. It's usually the Run function from the testcontainers-go module, like redis.Run or postgres.Run | - | | Options | []testcontainers.ContainerCustomizer | The functional options to pass to the [testcontainers.Run] function. This argument is optional. | - | ```go // Config contains the configuration for a container service. type Config[T testcontainers.Container] struct { // ServiceKey is the key used to identify the service in the Fiber app's state. ServiceKey string // Image is the image name to use for the container. Image string // Run is the function to use to run the container. // It's usually the Run function from the testcontainers-go module, like redis.Run or postgres.Run, // although it could be the generic [testcontainers.Run] function from the testcontainers-go package. Run func(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (T, error) // Options are the functional options to pass to the [testcontainers.Run] function. This argument is optional. // You can find the available options in the [testcontainers website]. // // [testcontainers website]: https://golang.testcontainers.org/features/creating_container/#customizing-the-container Options []testcontainers.ContainerCustomizer } ``` #### ContainerService The `ContainerService` type is a generic type that embeds a [testcontainers.Container](https://pkg.go.dev/github.com/testcontainers/testcontainers-go#Container) interface, and implements the [fiber.Service] interface, thanks to the Start, String, State and Terminate methods. It manages the lifecycle of a `testcontainers.Container` instance, and it can be retrieved from the Fiber app's state calling the `fiber.MustGetService` function with the key returned by the `ContainerService.Key` method. The type parameter `T` must implement the [testcontainers.Container](https://pkg.go.dev/github.com/testcontainers/testcontainers-go#Container) interface, as in the Testcontainers Go modules (e.g. [redis.RedisContainer](https://pkg.go.dev/github.com/testcontainers/testcontainers-go/modules/redis#RedisContainer), [postgres.PostgresContainer](https://pkg.go.dev/github.com/testcontainers/testcontainers-go/modules/postgres#PostgresContainer), etc.), or in the generic [testcontainers.DockerContainer](https://pkg.go.dev/github.com/testcontainers/testcontainers-go#GenericContainer) type, used for custom containers. :::note Since `ContainerService` implements the `fiber.Service` interface, container cleanup is handled automatically by the Fiber framework when the application shuts down. There's no need for manual cleanup code. ::: ```go type ContainerService[T testcontainers.Container] struct ``` ##### Signature ######  Key ```go // Key returns the key used to identify the service in the Fiber app's state. // Consumers should use string constants for service keys to ensure consistency // when retrieving services from the Fiber app's state. func (c *ContainerService[T]) Key() string ``` ###### Container ```go // Container returns the Testcontainers container instance, giving full access to the T type methods. // It's useful to access the container's methods, like [testcontainers.Container.MappedPort] // or [testcontainers.Container.Inspect]. func (c *ContainerService[T]) Container() T ``` ###### Start ```go // Start creates and starts the container, calling the [run] function with the [img] and [opts] arguments. // It implements the [fiber.Service] interface. func (c *ContainerService[T]) Start(ctx context.Context) error ``` ###### String ```go // String returns the service key, which uniquely identifies the container service. // It implements the [fiber.Service] interface. func (c *ContainerService[T]) String() string ``` ###### State ```go // State returns the status of the container. // It implements the [fiber.Service] interface. func (c *ContainerService[T]) State(ctx context.Context) (string, error) ``` ###### Terminate ```go // Terminate stops and removes the container. It implements the [fiber.Service] interface. func (c *ContainerService[T]) Terminate(ctx context.Context) error ``` #### Common Errors | Error | Description | Resolution | |-------|-------------|------------| | ErrNilConfig | Returned when the config is nil | Ensure config is properly initialized | | ErrContainerNotRunning | Returned when the container is not running | Check container state before operations | | ErrEmptyServiceKey | Returned when the service key is empty | Provide a non-empty service key | | ErrImageEmpty | Returned when the image is empty | Provide a valid image name | | ErrRunNil | Returned when the run is nil | Provide a valid run function | ### Examples You can find more examples in the [testable examples](https://github.com/gofiber/contrib/blob/main/v3/testcontainers/examples_test.go). #### Adding a module container using the Testcontainers Go's Redis module ```go package main import ( "fmt" "log" "github.com/gofiber/fiber/v3" "github.com/gofiber/contrib/v3/testcontainers" tc "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/redis" ) func main() { cfg := &fiber.Config{} // Define the base key for the module service. // The service returned by the [testcontainers.AddService] function, // using the [ContainerService.Key] method, // concatenates the base key with the "using testcontainers-go" suffix. const ( redisKey = "redis-module" ) // Adding containers coming from the testcontainers-go modules, // in this case, a Redis and a Postgres container. redisModuleConfig := testcontainers.NewModuleConfig(redisKey, "redis:latest", redis.Run) redisSrv, err := testcontainers.AddService(cfg, redisModuleConfig) if err != nil { log.Println("error adding redis module:", err) return } // Create a new Fiber app, using the provided configuration. app := fiber.New(*cfg) // Retrieve all services from the app's state. // This returns a slice of all the services registered in the app's state. srvs := app.State().Services() // Retrieve the Redis container from the app's state using the key returned by the [ContainerService.Key] method. redisCtr := fiber.MustGetService[*testcontainers.ContainerService[*redis.RedisContainer]](app.State(), redisSrv.Key()) // Start the Fiber app. app.Listen(":3000") } ``` #### Adding a custom container using the Testcontainers Go package ```go package main import ( "fmt" "log" "github.com/gofiber/fiber/v3" "github.com/gofiber/contrib/v3/testcontainers" tc "github.com/testcontainers/testcontainers-go" ) func main() { cfg := &fiber.Config{} // Define the base key for the generic service. // The service returned by the [testcontainers.AddService] function, // using the [ContainerService.Key] method, // concatenates the base key with the "using testcontainers-go" suffix. const ( nginxKey = "nginx-generic" ) // Adding a generic container, directly from the testcontainers-go package. containerConfig := testcontainers.NewContainerConfig(nginxKey, "nginx:latest", tc.WithExposedPorts("80/tcp")) nginxSrv, err := testcontainers.AddService(cfg, containerConfig) if err != nil { log.Println("error adding nginx generic:", err) return } app := fiber.New(*cfg) nginxCtr := fiber.MustGetService[*testcontainers.ContainerService[*tc.DockerContainer]](app.State(), nginxSrv.Key()) // Start the Fiber app. app.Listen(":3000") } ``` --- ## Uptime ![Release](https://img.shields.io/github/v/tag/gofiber/contrib?filter=*uptime*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://github.com/gofiber/contrib/workflows/Test%20Uptime/badge.svg) Uptime middleware for [Fiber](https://github.com/gofiber/fiber) that records in-process heartbeat history and serves a lightweight status page. **Compatible with Fiber v3.** ### Preview ![Uptime dashboard preview](https://raw.githubusercontent.com/gofurry/images/refs/heads/main/github/uptime/preview.png) ### Install ```sh go get -u github.com/gofiber/fiber/v3 go get -u github.com/gofiber/contrib/v3/uptime go get -u github.com/gofiber/storage/redis/v3 ``` ### Signature ```go uptime.New(config ...uptime.Config) fiber.Handler uptime.NewRedisStore(ctx context.Context, cfg uptime.RedisStoreConfig) (storage.Store, error) uptime.RemoveService(ctx context.Context, store *fiberredis.Storage, keyPrefix, serviceID string) error ``` ### Basic usage ```go package main import ( "github.com/gofiber/contrib/v3/uptime" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/log" fiberredis "github.com/gofiber/storage/redis/v3" ) func main() { app := fiber.New() store := fiberredis.New() app.Hooks().OnPostShutdown(func(_ error) error { return store.Close() }) app.Use(uptime.New(uptime.Config{ App: app, Store: store, ServiceID: "api", ServiceName: "API", })) app.Get("/", func(c fiber.Ctx) error { return c.SendString("ok") }) log.Fatal(app.Listen(":3000")) } ``` Open: - `http://localhost:3000/uptime` - `http://localhost:3000/uptime/api/status` ### Endpoint probes By default, uptime records a heartbeat for the current process identified by `ServiceID`. You can also configure HTTP endpoints. Each endpoint is shown as a separate service on the dashboard and in the JSON API. ```go app.Use(uptime.New(uptime.Config{ App: app, Store: store, Endpoints: []uptime.EndpointConfig{ { ID: "api-health", Name: "API Health", URL: "https://api.example.com/health", Method: "GET", Interval: 10 * time.Second, Timeout: 3 * time.Second, ExpectedStatusCodes: []int{200, 204}, }, }, })) ``` When `ExpectedStatusCodes` is empty, any `2xx` or `3xx` response is considered up. Failed probes do not write heartbeat slots, so the endpoint naturally moves to yellow, red, or down as slots are missed. Every instance runs its own probes, so N replicas sharing a `StorageKeyPrefix` send N times the probe traffic to each target. A slot counts as up when any replica saw the endpoint up. The first probe runs when `New` is called, before `app.Listen` has opened the port. Endpoints pointing at the app's own routes therefore miss their first slot or two on startup. ### Redis Redis state is stored through `github.com/gofiber/storage/redis/v3`. Create the Fiber Redis storage in your app and pass it to the uptime config. By default, that storage package connects to `127.0.0.1:6379` and database `0`. ```go store := fiberredis.New(fiberredis.Config{ Addrs: []string{"127.0.0.1:6379"}, Password: "", Database: 0, }) app.Hooks().OnPostShutdown(func(_ error) error { return store.Close() }) app.Use(uptime.New(uptime.Config{ App: app, Store: store, ServiceID: "api", StorageKeyPrefix: "fiber:uptime", })) ``` The middleware uses `Conn()` from the Fiber Redis storage to run the uptime queries it needs. Use a dedicated Redis database or a distinct `StorageKeyPrefix` when multiple environments share the same Redis server. The caller owns the Redis storage lifecycle. Close the Redis storage after the Fiber app has shut down, for example from `OnPostShutdown`, so uptime can stop its background recorders first. When multiple uptime instances share the same Redis database and `StorageKeyPrefix`, use the same `Timezone` and heartbeat or probe interval for the same `ServiceID` or endpoint `ID`. If those settings need to differ, use distinct service IDs, endpoint IDs, or storage prefixes. #### Using the Redis store without middleware `NewRedisStore` returns the public `github.com/gofiber/contrib/v3/uptime/storage.Store`, initialized and ready for concurrent queries and writes without middleware. Invalid configuration or initialization failure returns a nil store and an error. ```go package main import ( "context" "fmt" "log" "time" "github.com/gofiber/contrib/v3/uptime" fiberredis "github.com/gofiber/storage/redis/v3" "github.com/redis/go-redis/v9" ) func main() { client := redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", ContextTimeoutEnabled: true, }) defer client.Close() redisStorage := fiberredis.NewFromConnection(client) defer redisStorage.Close() ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() store, err := uptime.NewRedisStore(ctx, uptime.RedisStoreConfig{ Store: redisStorage, StorageKeyPrefix: "fiber:uptime", RetentionDays: 90, }) if err != nil { log.Print(err) return } services, err := store.ListServices(ctx) if err != nil { log.Print(err) return } fmt.Printf("%d services\n", len(services)) } ``` Initialization passes the context to Redis; network timeouts still depend on the client configuration. `ContextTimeoutEnabled`, used above with `fiberredis.NewFromConnection`, enables go-redis to apply context deadlines to I/O. Cancellation does not guarantee an immediate return. On initialization failure, `errors.Is` can inspect both the underlying error and any `ctx.Err()`. An empty `StorageKeyPrefix` defaults to `fiber:uptime`. Prefix normalization matches the built-in backend, trimming leading/trailing colons. Zero `RetentionDays` defaults to 90; negative values or an overflowing TTL are rejected. State expiry follows [Key expiry](#key-expiry); instance metadata expires after 24 hours. The factory starts no workers, HTTP service, or automatic maintenance. The store can also be passed to `Config.Storage`; match its retention setting to the factory's. :::caution `UpsertService`, `UpsertInstance`, `WriteHeartbeat`, `RollupDaily`, and `Cleanup` modify monitoring data. Coordinate changes to records used by an active monitor, and use its Redis database, key prefix, and retention settings. Multiple queries do not form a transactionally consistent snapshot. ::: The caller owns the Fiber Redis storage and any separately borrowed client. The factory never closes them, even on failure. Close them after all users have stopped. #### Key expiry Service, sample and daily keys carry a TTL of `RetentionDays + 1` days that is re-armed on every write. Background maintenance normally removes them long before that, so the TTL only matters once no process is left to run the cleanup: a decommissioned service, a changed key prefix, an app that never comes back. Without it those keys would stay in Redis forever. The `services` registry set is shared by all instances under a prefix and is never expired. Service hashes receive a TTL when registered, even before their first heartbeat. Use `RemoveService` to clear a service and its registry entry. #### Removing a service When using the built-in Redis `Store` path, the dashboard lists every service found under `StorageKeyPrefix`, not just the ones in the current config. Services are never dropped automatically while their retention window keeps being refreshed, so renaming `ServiceID` or an endpoint `ID`, or removing an endpoint, leaves the old identifier behind as a row that reports down forever. Delete it explicitly: ```go err := uptime.RemoveService(context.Background(), store, "fiber:uptime", "old-endpoint-id") ``` Pass the same `StorageKeyPrefix` the middleware or `NewRedisStore` uses, or `""` for the default. This removes the service, its history and its raw samples. Remove the service from your config and restart before calling it. Calling it while the service is still being recorded takes it off the dashboard for good, but in-flight heartbeats will recreate partial keys; those carry the TTL above and expire on their own. `RemoveService` applies to the built-in Redis path and data written through `NewRedisStore`. Other custom storage backends may expose their own removal operation. ### Custom storage For most users, the built-in Redis `Store` path remains the simplest integration. `Storage` is the advanced extension point for applications that need a custom persistence backend. Provide an implementation of `github.com/gofiber/contrib/v3/uptime/storage.Store` through `Storage`: ```go app.Use(uptime.New(uptime.Config{ App: app, Storage: myStore, ServiceID: "api", })) ``` Custom stores must be ready before calling `uptime.New` and safe for concurrent use. The caller owns their lifecycle and should close underlying resources after the Fiber application has shut down; uptime does not close custom stores. `Store` and `Storage` are mutually exclusive, and `StorageKeyPrefix` applies only to the built-in Redis `Store` path. Redis remains the built-in storage integration. ### Snapshots and custom UI The dashboard and JSON API share an in-memory snapshot, limiting backing-store work on public status routes. It is held for one `SampleInterval`, or for the shortest interval any service in it is tracked at when an endpoint is probed faster than that, so a snapshot is never served past the point where its own `current_status` would have changed. If a refresh fails, the last snapshot is served with `storage.status` set to `degraded` and the same fixed `last_error` label the live status uses; the backing store's own message goes to the log rather than the public payload. A failed refresh restarts the interval like a successful one, so an outage cannot turn every request into another attempt against the unavailable store. The same snapshot payload is available at `UI.Path + "/api/status"` for custom dashboards. The built-in dashboard polls that endpoint every `SampleInterval`, but no more often than every 10 seconds. It stops polling while its browser tab is hidden and refreshes as soon as the tab is visible again, so a forgotten tab does not keep reading the backing store. A request that fails, or takes longer than the polling interval and is aborted, marks the dashboard `STALE` while the last successful snapshot stays on screen. `ERROR` means the API reported a storage problem. Screen readers are told when the `LIVE`, `STALE`, or `ERROR` state changes, but not on every refresh. ### Service insights Each service card has an **Insights** button, collapsed by default. Expanding it shows four metrics and a daily availability trend for the same `DaysToShow` window as the existing uptime bars: - **Availability**: total up slots divided by total expected slots across valid days, rather than an average of daily percentages. - **Downtime**: the sum of the daily estimated downtime. - **Affected days**: valid days with at least one missed expected slot. - **Stable streak**: consecutive perfect days backward from the newest valid day, including today when every completed expected slot so far is up. Newest no-data days are skipped; an internal no-data gap or imperfect day stops the streak. `N+ days` means it reaches the window boundary and the service existed before that window, so the streak may be longer. Days without data or expected slots do not contribute to the metrics and are not treated as downtime. With no valid days, all four metrics display `—`, and the trend displays `No data`. Missing days create gaps in the trend. Expanded cards stay expanded across automatic refreshes; reloading the page collapses them again. Charts are rendered only while expanded. The trend uses native SVG and Vanilla JavaScript, with zero additional dependencies, storage queries, or persistent data. Existing daily bars remain keyboard accessible. The trend's gridlines use round percentages, it exposes its latest and lowest values to assistive technologies, and on touch screens a tap or horizontal drag on it shows a day's values. Each service in the status JSON also includes a `summary` object with `has_data`, `availability_rate`, `estimated_downtime_seconds`, `affected_days`, `stable_streak_days`, and `stable_streak_capped`. When `has_data` is false, the numeric fields are zero and `stable_streak_capped` is false; clients should display no data rather than 0% availability. These metrics add no latency, incident, or alerting semantics and require no new configuration. ### Dashboard favicon The built-in dashboard includes an embedded favicon by default. Set `UI.FaviconURL` to override it with either a root-relative path served by the same application or an absolute HTTP(S) URL: ```go app.Use(uptime.New(uptime.Config{ App: app, Store: store, ServiceID: "api", UI: uptime.UIConfig{ FaviconURL: "/assets/favicon.svg", }, })) ``` Filesystem paths such as `./favicon.ico` are not supported directly. Expose a local file through a Fiber route or static handler, then configure its URL. Remote favicon URLs cause each dashboard visitor's browser to contact that remote host, so a same-origin URL is preferred for private deployments. ### Config | Property | Type | Description | Default | |:--|:--|:--|:--| | App | `*fiber.App` | Fiber app used to register the shutdown hook that closes the uptime runtime. | Required | | Next | `func(fiber.Ctx) bool` | Skip the uptime handler when true. | `nil` | | ServiceID | `string` | Stable service identifier for the current process. Required only when `Endpoints` is empty. | `""` | | ServiceName | `string` | Display name. | `ServiceID` | | ServiceDescription | `string` | Display description. | `""` | | Endpoints | `[]uptime.EndpointConfig` | Optional HTTP endpoints to probe as tracked services. | `nil` | | SampleInterval | `time.Duration` | Heartbeat interval. | `3 * time.Second` | | RetentionDays | `int` | Days to retain daily history; zero uses the default, negative values are invalid. Redis requires at most `106750` days to fit its expiry backstop in `time.Duration`; custom `Storage` has no Redis-specific limit. | `90` | | DaysToShow | `int` | Number of days shown in snapshots and dashboard. | `30` | | Timezone | `*time.Location` | Timezone for day and slot boundaries. | `time.Local` | | NodeID | `int64` | Optional node value used for generated instance IDs. | `0` | | InstanceID | `int64` | Explicit process instance ID. | Generated | | IDGenerator | `uptime.IDGenerator` | Custom instance ID generator. | `nil` | | Store | `*fiberredis.Storage` | Fiber Redis storage instance from `github.com/gofiber/storage/redis/v3`. | Required unless `Storage` is set. | | Storage | `storage.Store` | Custom uptime storage backend. | Required unless `Store` is set. | | StorageKeyPrefix | `string` | Prefix for uptime Redis keys. Applies only when `Store` is used. | `"fiber:uptime"` | | UI | `uptime.UIConfig` | Dashboard copy, favicon, and thresholds. `FaviconURL` accepts a root-relative path or absolute HTTP(S) URL. Threshold values are configurable in `(0, 1]`; zero uses the defaults. | Embedded favicon, light English UI, green at `99.9%`, yellow at `99%` | #### EndpointConfig | Property | Type | Description | Default | |:--|:--|:--|:--| | ID | `string` | Stable endpoint identifier. | Required | | Name | `string` | Display name. | `ID` | | Description | `string` | Display description. | `""` | | URL | `string` | Absolute `http` or `https` URL to probe. | Required | | Method | `string` | HTTP method used for the probe. | `GET` | | Headers | `map[string]string` | Optional request headers sent with each probe. | `nil` | | ExpectedStatusCodes | `[]int` | Status codes that mark the endpoint up. Empty means any `2xx` or `3xx`. | `nil` | | Interval | `time.Duration` | Endpoint heartbeat interval. | `Config.SampleInterval` | | Timeout | `time.Duration` | Maximum duration for one probe. | `5 * time.Second` | ### Handler behavior The Fiber handler serves: - `/uptime` - `/uptime/` - `/uptime/api/status` `GET` and `HEAD` are supported. Other methods return `405 Method Not Allowed`. Requests outside `UI.Path`, including unknown uptime subpaths, are passed to the next handler. The handler matches request paths against `UI.Path` (default `/uptime`). When mounted under a Fiber group or `Use` prefix, the match is relative to that mount point. The middleware does not read request bodies, capture response bodies, or wrap business handlers. Process heartbeats and endpoint probes are run by background tickers owned by the uptime runtime. ### Performance notes Store writes and endpoint probes happen on background tickers, not on every business request. Status requests read the backing store to build the response. Use Fiber's cache middleware if the dashboard or JSON API should be cached. ### Concurrency safety Uptime middleware instances are safe for concurrent use after construction. The snapshot payload is built from fresh store reads. Redis commands are issued through the Fiber Redis storage connection and are safe for concurrent use by the background recorder and Fiber handlers. `Config.App` is required so `New` can register a Fiber shutdown hook that stops the uptime runtime. The caller owns the Redis storage lifecycle and should close it after the app has shut down, for example from `OnPostShutdown` or a signal handler that closes Redis after `app.Shutdown` returns. ### Security notes Mount the dashboard on an internal or protected route when uptime history should not be public. The middleware does not log request bodies, response bodies, authorization headers, cookies, or query strings. Endpoint probe response bodies are closed without being read. Avoid putting secrets in endpoint URLs because URLs may still appear in upstream infrastructure logs outside this middleware. --- ## Websocket ![Release](https://img.shields.io/github/v/tag/gofiber/contrib?filter=*websocket*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://github.com/gofiber/contrib/workflows/Test%20websocket/badge.svg) Based on [Fasthttp WebSocket](https://github.com/fasthttp/websocket) for [Fiber](https://github.com/gofiber/fiber) with available `fiber.Ctx` methods like [Locals](https://docs.gofiber.io/api/ctx#locals), [Params](https://docs.gofiber.io/api/ctx#params), [Query](https://docs.gofiber.io/api/ctx#query) and [Cookies](https://docs.gofiber.io/api/ctx#cookies). For a plain WebSocket event-bus helper, use the [`event`](./event/README.md) subpackage. It keeps ordinary WebSocket wire compatibility and is separate from the Socket.IO protocol implementation in `github.com/gofiber/contrib/v3/socketio`. **Compatible with Fiber v3.** ### Go version support We only support the latest two versions of Go. Visit [https://go.dev/doc/devel/release](https://go.dev/doc/devel/release) for more information. ### Install ```sh go get -u github.com/gofiber/fiber/v3 go get -u github.com/gofiber/contrib/v3/websocket ``` ### Signatures ```go func New(handler func(*websocket.Conn), config ...websocket.Config) fiber.Handler { ``` ### Config | Property | Type | Description | Default | |:--------------------|:-----------------------------|:------------------------------------------------------------------------------------------------------------------------------|:-----------------------| | Next | `func(fiber.Ctx) bool` | Defines a function to skip this middleware when it returns true. | `nil` | | HandshakeTimeout | `time.Duration` | Bounds sending the 101 response, which fasthttp writes once the handler chain returns. The server's `WriteTimeout`, when set, applies instead. | `0` (No timeout) | | Subprotocols | `[]string` | Subprotocols this server supports, in order of preference. The first entry the client also offers is negotiated. | `nil` | | Origins | `[]string` | Allowed Origins based on the Origin header, compared case-insensitively. If empty, everything is allowed. | `nil` | | AllowEmptyOrigin | `bool` | Allows connections without an Origin header when Origins is configured. Useful for non-browser clients. | `false` | | ReadBufferSize | `int` | ReadBufferSize specifies the I/O buffer size in bytes for incoming messages. | `0` (Use default size) | | WriteBufferSize | `int` | WriteBufferSize specifies the I/O buffer size in bytes for outgoing messages. | `0` (Use default size) | | WriteBufferPool | `websocket.BufferPool` | WriteBufferPool is a pool of buffers for write operations. | `nil` | | EnableCompression | `bool` | EnableCompression specifies if the client should attempt to negotiate per message compression (RFC 7692). | `false` | | RecoverHandler | `func(*websocket.Conn)` | RecoverHandler is a panic handler function that recovers from panics. | `defaultRecover` | ### Example ```go package main import ( "log" "github.com/gofiber/fiber/v3" "github.com/gofiber/contrib/v3/websocket" ) func main() { app := fiber.New() app.Use("/ws", func(c fiber.Ctx) error { // IsWebSocketUpgrade returns true if the client // requested upgrade to the WebSocket protocol. if websocket.IsWebSocketUpgrade(c) { c.Locals("allowed", true) return c.Next() } return fiber.ErrUpgradeRequired }) app.Get("/ws/:id", websocket.New(func(c *websocket.Conn) { // c.Locals is added to the *websocket.Conn log.Println(c.Locals("allowed")) // true log.Println(c.Params("id")) // 123 log.Println(c.Query("v")) // 1.0 log.Println(c.Cookies("session")) // "" // websocket.Conn bindings https://pkg.go.dev/github.com/fasthttp/websocket?tab=doc#pkg-index var ( mt int msg []byte err error ) for { if mt, msg, err = c.ReadMessage(); err != nil { log.Println("read:", err) break } log.Printf("recv: %s", msg) if err = c.WriteMessage(mt, msg); err != nil { log.Println("write:", err) break } } })) log.Fatal(app.Listen(":3000")) // Access the websocket server: ws://localhost:3000/ws/123?v=1.0 // https://www.websocket.org/echo.html } ``` ### Handshake rejections A request that carries neither an `Upgrade` header nor an `upgrade` token in `Connection` never asked to switch protocols and is answered with `426 Upgrade Required`. A request that *does* ask to upgrade but whose handshake is rejected is returned as a `*fiber.Error` carrying the status RFC 6455 defines for that failure, so it reaches the app's `ErrorHandler` like any other error and takes its body and format from there: | Reason | Status | |:-----------------------------------------------------------|:--------------------------| | Origin not in `Origins` (RFC 6455 section 4.2.2) | `403 Forbidden` | | Missing/blank `Sec-WebSocket-Key`, unsupported version, or only one of the `Upgrade`/`Connection` signals | `400 Bad Request` | | Request method is not `GET` | `405 Method Not Allowed` | Rejected handshakes also carry `Sec-WebSocket-Version: 13` so a client that asked for another version learns which one the server speaks (RFC 6455 section 4.4). Headers set by earlier middleware are left in place. ### Reads and writes `c.ReadMessage()` reads messages up to 64 KiB into a pooled buffer and returns an exact-size copy; a larger message comes back in its own buffer, as from the library's `ReadMessage`. Bound message size with `SetReadLimit`; for zero-copy reads use `NextReader` with your own buffer. Replies to a burst of pipelined frames leave in one write instead of one per frame: writes made while the handler still has unread frames wait until it asks for the next one, at most 64 KiB or 1 ms. Writes made while nothing is waiting to be read go out immediately, so push-only handlers are unaffected, and frames are never reordered. With 5-byte frames and 16 in flight per connection, server CPU per frame goes from 5.7 µs to 1.0 µs. To own the connection the upgrade runs through the library's `net/http` `Upgrader` on a fasthttp-backed hijack. fasthttp still sends the 101, so middleware running after `c.Next()` sees the status and headers and can add its own, and `Sec-WebSocket-Key` is validated as RFC 6455 requires. `c.NetConn()` returns the middleware's connection; its `UnsafeConn()` is the socket underneath. `Close` sends what is pending, waiting at most 100 ms for a peer that is not reading, and interrupts a write stalled on that peer. ### Note with cache middleware If you get the error `websocket: bad handshake` when using the [cache middleware](https://github.com/gofiber/fiber/tree/master/middleware/cache), please use `config.Next` to skip websocket path. ```go app := fiber.New() app.Use(cache.New(cache.Config{ Next: func(c fiber.Ctx) bool { return strings.Contains(c.Route().Path, "/ws") }, })) app.Get("/ws/:id", websocket.New(func(c *websocket.Conn) {})) ``` ### Note with recover middleware For internal implementation reasons, currently recover middleware does not work with websocket middleware, please use `config.RecoverHandler` to add recover handler to websocket endpoints. By default, config `RecoverHandler` recovers from panic, writes the value and stack trace to stderr, and sends the peer a fixed `{"error":"internal error"}`. Nothing derived from the panic reaches the client: the operator already has the full detail on stderr, and a panic value can carry internal paths, connection strings or schema names. If you want the peer told more than that, supply your own `RecoverHandler` — it receives the `*websocket.Conn` and calls `recover()` itself, so it can write whatever the application considers safe. Once the recover handler returns, the connection is closed: a handler that panicked cannot be assumed to still own it, and nothing else closes a hijacked connection. A handler that returns normally leaves the socket open, so it may hand the connection to another goroutine before returning and keep writing after. The `*websocket.Conn` it receives is allocated for that upgrade alone and never reused, so `Locals`, `Params`, `Query`, `Cookies`, `Headers` and `IP` keep answering with that connection's own data for as long as anything holds it. ```go app := fiber.New() app.Use(cache.New(cache.Config{ Next: func(c fiber.Ctx) bool { return strings.Contains(c.Route().Path, "/ws") }, })) cfg := Config{ RecoverHandler: func(conn *Conn) { if err := recover(); err != nil { conn.WriteJSON(fiber.Map{"customError": "error occurred"}) } }, } app.Get("/ws/:id", websocket.New(func(c *websocket.Conn) {}, cfg)) ``` ### Note for WebSocket subprotocols The config `Subprotocols` only helps you negotiate subprotocols and sets a `Sec-Websocket-Protocol` header if it has a suitable subprotocol. For more about negotiates process, check the comment for `Subprotocols` in [fasthttp.Upgrader](https://pkg.go.dev/github.com/fasthttp/websocket#Upgrader) . All connections will be sent to the handler function no matter whether the subprotocol negotiation is successful or not. You can get the selected subprotocol from `conn.Subprotocol()`. If a connection includes the `Sec-Websocket-Protocol` header in the request but the protocol negotiation fails, the browser will immediately disconnect the connection after receiving the upgrade response. --- ## WebSocket Event Plain WebSocket event helper for [Fiber](https://github.com/gofiber/fiber), built on top of `github.com/gofiber/contrib/v3/websocket`. This package is for applications that want the legacy event-bus behavior over ordinary WebSocket clients. It does not implement the Engine.IO or Socket.IO protocol. Use `github.com/gofiber/contrib/v3/socketio` when you need compatibility with the official `socket.io-client` package. If your application used the older `socketio` package as a plain WebSocket event bus, migrate the import to `github.com/gofiber/contrib/v3/websocket/event`. The API intentionally stays close to that legacy event helper, while the `socketio` package is reserved for the Socket.IO protocol. **Compatible with Fiber v3.** ### Install ```sh go get -u github.com/gofiber/contrib/v3/websocket ``` The event helper is the `event` subpackage of that module: ```go import "github.com/gofiber/contrib/v3/websocket/event" ``` ### Signatures Create a handler: ```go func New(callback func(kws *event.Websocket), config ...websocket.Config) fiber.Handler func NewWithConfig(callback func(kws *event.Websocket), eventCfg event.Config, wsConfig ...websocket.Config) fiber.Handler ``` Register listeners. Package-level `On` is process-global; the `(*Websocket)` method form is scoped to a single connection (see [Listeners](#listeners)): ```go type EventCallback func(payload *event.EventPayload) func On(name string, callback EventCallback) // global: fires for every connection func Off(name string) // removes global listeners for name func (kws *Websocket) On(name string, callback EventCallback) // this connection only func (kws *Websocket) Off(name string) // remove this connection's listeners ``` Send messages. The `(*Websocket)` method forms operate on / from a specific connection and fire `EventError` on failure; the package-level forms address connections in the global pool and do not fire `EventError` (see [Sending messages](#sending-messages)): ```go // Method forms (fire EventError on failure). func (kws *Websocket) Emit(message []byte, mType ...int) func (kws *Websocket) EmitTo(uuid string, message []byte, mType ...int) error func (kws *Websocket) EmitToList(uuids []string, message []byte, mType ...int) func (kws *Websocket) Broadcast(message []byte, except bool, mType ...int) func (kws *Websocket) Fire(name string, data []byte) // Package forms (do not fire EventError). func EmitTo(uuid string, message []byte, mType ...int) error func EmitToList(uuids []string, message []byte, mType ...int) func Broadcast(message []byte, mType ...int) func Fire(name string, data []byte) ``` Connection identity and attributes: ```go func (kws *Websocket) GetUUID() string func (kws *Websocket) SetUUID(uuid string) error // returns ErrorUUIDDuplication on conflict func (kws *Websocket) SetAttribute(key string, value interface{}) func (kws *Websocket) GetAttribute(key string) interface{} ``` Graceful shutdown: ```go func Drain() func IsDraining() bool func CloseAll(ctx context.Context, code int, reason string) error ``` ### Listeners `On` registers a **process-global** listener: the callback fires for the given event on **every** connection created by `New` / `NewWithConfig`, regardless of route or `Config`. Listeners are additive and stay registered until removed with `Off`. This matches the legacy `socketio` event bus. ```go event.On(event.EventMessage, func(ep *event.EventPayload) { /* ... */ }) event.Off(event.EventMessage) // remove again, e.g. on reconfiguration or in tests ``` For listeners that should fire for a single connection only, use the `(*Websocket).On` method (typically from the `New` callback). Per-connection listeners fire in addition to the global ones and are discarded automatically when the connection disconnects: ```go app.Get("/ws/:id", event.New(func(kws *event.Websocket) { kws.On(event.EventMessage, func(ep *event.EventPayload) { // only fires for this connection }) })) ``` ### Sending messages There are two flavors of `EmitTo` / `EmitToList` / `Broadcast`, and the difference is easy to miss: | Form | Targets | On failure | |:-----|:--------|:-----------| | `(*Websocket).EmitTo` | a UUID in the pool | fires `EventError` on `kws` and returns the error | | `(*Websocket).EmitToList` | a list of UUIDs | fires `EventError` on `kws` per failed UUID | | `(*Websocket).Broadcast` | all connections (`except` skips `kws` itself) | fires `EventError` on `kws` per failed UUID | | `EmitTo` (package) | a UUID in the pool | returns the error, does **not** fire `EventError` | | `EmitToList` (package) | a list of UUIDs | silently ignores per-UUID errors | | `Broadcast` (package) | all connections | fire-and-forget, no error feedback | Use the method forms when you want delivery failures surfaced as `EventError` events; use the package forms for fire-and-forget fan-out. `Emit` enqueues the message on the connection's outbound queue, which a dedicated goroutine drains in order; if the queue is full it blocks until a slot frees up or the connection closes. ### Configuration Per-instance tuning via `event.Config` passed to `NewWithConfig`. A zero value falls back to the default below. `PingInterval` consults the package-level `PongTimeout` first, and `SendQueueSize`, `MaxSendRetry` and `RetrySendTimeout` the package-level vars of the same name; the other fields have no package-level counterpart. | Config field | Default | Description | |:--------------------|:--------|:------------| | `PingInterval` | `25s` | Interval between server-originated Ping frames. Socket.IO's default; must be less than any upstream proxy or load balancer idle timeout. | | `PingTimeout` | `20s` | How long a peer may stay silent after a Ping before it is considered dead. Socket.IO's default. | | `ReadIdleTimeout` | `PingInterval + PingTimeout` | Maximum silence before the connection is disconnected, 45s by default. | | `WriteTimeout` | `10s` | Bounds a single `WriteMessage` / `WriteControl` call. | | `MaxMessageSize` | `1 MiB` | Inbound frame size limit. Set to `math.MaxInt64` to opt out. | | `SendQueueSize` | `100` | Per-connection outbound message queue capacity. | | `MaxSendRetry` | `5` | Max retries for transient socket write readiness issues. | | `RetrySendTimeout` | `20ms` | Backoff between retries while the connection is not ready. | | `RecoverHandler` | `nil` | Called on a panic inside a user `On` callback. If `nil`, panics are recovered silently. | The legacy package-level vars (`PongTimeout`, `RetrySendTimeout`, `MaxSendRetry`, `SendQueueSize`, `ReadTimeout`) are still read once per connection at upgrade time for backwards compatibility, but mutating them after a connection is established has no effect on running goroutines. Prefer `NewWithConfig` for new code. ### Thread safety `On`, `Off`, `Fire`, the package-level `EmitTo` / `EmitToList` / `Broadcast`, and the connection pool are safe for concurrent use. A single `*Websocket` is safe to use from multiple goroutines: reads and writes are serialized through a per-connection mutex and an outbound send queue. Listener callbacks run on the helper's goroutines, so a callback that blocks holds up event delivery for that connection; offload long work to your own goroutine. ### Graceful Shutdown The helper keeps an in-process pool of active connections. Use `event.Drain` and `event.CloseAll` together with a Fiber shutdown hook so clients receive a clean `1001 Going Away` close frame instead of an abrupt TCP reset: ```go app.Hooks().OnShutdown(func() error { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() event.Drain() return event.CloseAll(ctx, websocket.CloseGoingAway, "server shutting down") }) ``` `CloseAll` returns once every connection has been sent its close frame and marked disconnected; the connections' own goroutines then wind down by themselves. If `ctx` expires before that point, `CloseAll` force-closes the connections still in the pool and returns `ctx.Err()`. The close frame is normalised before it goes on the wire. Only a `code` a conformant peer accepts on receive is forwarded — `1000-1003`, `1007-1013` and the `3000-4999` application range — and anything else is replaced with `1000 Normal Closure`, since a peer answers such codes with a protocol error instead of reading a close. `1005` is the exception: it is sent as an empty close frame, which is how "no status" is expressed on the wire, so a `reason` passed with it is necessarily dropped. `reason` is otherwise scrubbed to valid UTF-8 and cut back to 123 bytes on a rune boundary (RFC 6455 section 5.5.1). `Drain` only flips the draining flag; it does **not** refuse new connections by itself. Gate the upgrade route on `IsDraining` to stop accepting clients during shutdown: ```go app.Use("/ws", func(c fiber.Ctx) error { if event.IsDraining() { return fiber.NewError(fiber.StatusServiceUnavailable, "shutting down") } if websocket.IsWebSocketUpgrade(c) { return c.Next() } return fiber.ErrUpgradeRequired }) ``` ### Example ```go package main import ( "log" "github.com/gofiber/contrib/v3/websocket" "github.com/gofiber/contrib/v3/websocket/event" "github.com/gofiber/fiber/v3" ) func main() { app := fiber.New() app.Use("/ws", func(c fiber.Ctx) error { if websocket.IsWebSocketUpgrade(c) { return c.Next() } return fiber.ErrUpgradeRequired }) event.On(event.EventMessage, func(ep *event.EventPayload) { ep.Kws.Emit([]byte("echo: "+string(ep.Data)), event.TextMessage) }) app.Get("/ws/:id", event.New(func(kws *event.Websocket) { // kws.Params / kws.Locals / kws.Query / kws.Cookies wrap the Fiber // request context captured at upgrade time. kws.SetAttribute("user_id", kws.Params("id")) })) log.Fatal(app.Listen(":3000")) } ``` #### Custom events Event names are arbitrary strings. Register a listener with `On` and trigger it with `Fire` (on one connection) or the package-level `Fire` (on all): ```go event.On("notify", func(ep *event.EventPayload) { log.Printf("notify %s: %s", ep.SocketUUID, ep.Data) }) // from a connection: kws.Fire("notify", []byte("hello")) // to every active connection: event.Fire("notify", []byte("broadcast")) ``` ### Supported Events | Const | Event | Description | |:------------------|:-------------|:--------------------------------------------------------| | `EventMessage` | `message` | Fired when a text or binary message is received. | | `EventPing` | `ping` | Fired when a WebSocket ping control frame is received. | | `EventPong` | `pong` | Fired when a WebSocket pong control frame is received. | | `EventDisconnect` | `disconnect` | Fired when the connection is closed. On an error close, `EventError` fires too. | | `EventConnect` | `connect` | Fired after the `New` callback runs, before the read loop starts. | | `EventClose` | `close` | Fired when the server actively closes the connection. | | `EventError` | `error` | Fired on a failed `EmitTo`, a dropped outbound message, or an error-driven disconnect. | ### Event Payload | Field | Type | Description | |:-------------------|:-----------------------|:-------------------------------------------------| | `Kws` | `*event.Websocket` | The connection object. | | `Name` | `string` | The event name. | | `SocketUUID` | `string` | Unique connection UUID. | | `SocketAttributes` | `map[string]any` | Snapshot of optional connection attributes. | | `Error` | `error` | Optional error for disconnect and error events. | | `Data` | `[]byte` | Data used on message, custom, and error events. | --- ## Zap ![Release](https://img.shields.io/github/v/tag/gofiber/contrib?filter=*zap*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://github.com/gofiber/contrib/workflows/Test%20zap/badge.svg) [Zap](https://github.com/uber-go/zap) logging support for Fiber. **Compatible with Fiber v3.** ### Go version support We only support the latest two versions of Go. Visit [https://go.dev/doc/devel/release](https://go.dev/doc/devel/release) for more information. ### Install ```sh go get -u github.com/gofiber/fiber/v3 go get -u github.com/gofiber/contrib/v3/zap go get -u go.uber.org/zap ``` #### Signature ```go zap.New(config ...zap.Config) fiber.Handler ``` #### Config | Property | Type | Description | Default | | :--------- | :------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------- | | Next | `func(fiber.Ctx) bool` | Define a function to skip this middleware when returned true | `nil` | | Logger | `*zap.Logger` | Add custom zap logger. | `zap.NewProduction()` | | Fields | `[]string` | Add fields that you want to see. | `[]string{"latency", "status", "method", "url"}` | | FieldsFunc | `func(fiber.Ctx) []zap.Field` | Define a function to add custom fields. | `nil` | | Messages | `[]string` | Custom response messages. | `[]string{"Server error", "Client error", "Success"}` | | Levels | `[]zapcore.Level` | Custom response levels. | `[]zapcore.Level{zapcore.ErrorLevel, zapcore.WarnLevel, zapcore.InfoLevel}` | | SkipURIs | `[]string` | Skip logging these URI. | `[]string{}` | | GetResBody | `func(c fiber.Ctx) []byte` | Define a function to get response body when return non-nil. eg: When use compress middleware, resBody is unreadable. you can set GetResBody func to get readable resBody. | `nil` | #### Example ```go package main import ( "log" middleware "github.com/gofiber/contrib/v3/zap" "github.com/gofiber/fiber/v3" "go.uber.org/zap" ) func main() { app := fiber.New() logger, _ := zap.NewProduction() defer logger.Sync() app.Use(middleware.New(middleware.Config{ Logger: logger, })) app.Get("/", func(c fiber.Ctx) error { return c.SendString("Hello, World!") }) log.Fatal(app.Listen(":3000")) } ``` ### NewLogger #### Signature ```go zap.NewLogger(config ...zap.LoggerConfig) *zap.LoggerConfig ``` #### LoggerConfig | Property | Type | Description | Default | | :---------- | :------------- | :------------------------------------------------------------------------------------------------------- | :----------------------------- | | CoreConfigs | `[]CoreConfig` | Define Config for zapcore | `zap.LoggerConfigDefault` | | SetLogger | `*zap.Logger` | Add custom zap logger. if not nil, `ZapOptions`, `CoreConfigs`, `SetLevel`, `SetOutput` will be ignored. | `nil` | | ExtraKeys | `[]string` | Allow users log extra values from context. | `[]string{}` | | ZapOptions | `[]zap.Option` | Allow users to configure the zap.Option supplied by zap. | `[]zap.Option{}` | #### Example ```go package main import ( "context" middleware "github.com/gofiber/contrib/v3/zap" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/log" ) func main() { app := fiber.New() logger := middleware.NewLogger(middleware.LoggerConfig{ ExtraKeys: []string{"request_id"}, }) log.SetLogger(logger) defer logger.Sync() app.Use(func(c fiber.Ctx) error { ctx := context.WithValue(c.Context(), "request_id", "123") c.SetContext(ctx) return c.Next() }) app.Get("/", func(c fiber.Ctx) error { log.WithContext(c.Context()).Info("Hello, World!") return c.SendString("Hello, World!") }) log.Fatal(app.Listen(":3000")) } ``` --- ## Zerolog ![Release](https://img.shields.io/github/v/tag/gofiber/contrib?filter=*zerolog*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://github.com/gofiber/contrib/workflows/Test%20zerolog/badge.svg) [Zerolog](https://github.com/rs/zerolog/) logging support for Fiber. **Compatible with Fiber v3.** ### Go version support We only support the latest two versions of Go. Visit [https://go.dev/doc/devel/release](https://go.dev/doc/devel/release) for more information. ### Install ```sh go get -u github.com/gofiber/fiber/v3 go get -u github.com/gofiber/contrib/v3/zerolog go get -u github.com/rs/zerolog/log ``` ### Signature ```go zerolog.New(config ...zerolog.Config) fiber.Handler ``` ### Config | Property | Type | Description | Default | |:----------------|:------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------| | Next | `func(fiber.Ctx) bool` | Define a function to skip this middleware when it returns true. | `nil` | | Skip | `func(fiber.Ctx) bool` | Define a function that is called **after** the request has been processed but **before** the log entry is assembled. If it returns true, logging is skipped entirely with zero overhead. This gives access to the full request context, including data set by downstream middlewares. | `nil` | | Logger | `*zerolog.Logger` | Add a custom zerolog logger. | `zerolog.New(os.Stderr).With().Timestamp().Logger()` | | GetLogger | `func(fiber.Ctx) zerolog.Logger` | Get a custom zerolog logger. If set, the returned logger replaces `Logger`. | `nil` | | Fields | `[]string` | Add the fields you want to log. | `[]string{"latency", "status", "method", "url", "error"}` | | SkipField | `func(string, fiber.Ctx) bool` | Skip logging a field when it returns true. | `nil` | | SkipHeader | `func(string, fiber.Ctx) bool` | Skip logging a header when it returns true. | `nil` | | RedactHeader | `func(string, fiber.Ctx) bool` | Replace every value of a header with `[REDACTED]` when it returns true. Common credential-bearing headers such as `Authorization`, `Cookie`, `Set-Cookie`, `Location`, `X-Api-Key`, `X-Auth-Token`, and CSRF-token headers are redacted by default. | built-in sensitive-header filter | | WrapHeaders | `bool` | Wrap headers into a dictionary. If false: `{"method":"POST", "header-key":"header value"}` If true: `{"method":"POST", "reqHeaders":{"header-key":"header value"}}` | `false` | | FieldsSnakeCase | `bool` | Use snake case for `FieldResBody`, `FieldQueryParams`, `FieldBytesReceived`, `FieldBytesSent`, `FieldRequestID`, `FieldReqHeaders`, `FieldResHeaders`. If false: `{"method":"POST", "resBody":"v", "queryParams":"v"}` If true: `{"method":"POST", "res_body":"v", "query_params":"v"}` | `false` | | Messages | `[]string` | Custom response messages. | `[]string{"Server error", "Client error", "Success"}` | | Levels | `[]zerolog.Level` | Custom response levels. | `[]zerolog.Level{zerolog.ErrorLevel, zerolog.WarnLevel, zerolog.InfoLevel}` | | GetResBody | `func(c fiber.Ctx) []byte` | Define a function to get the response body when it returns non-nil. For example, with compress middleware the body can be unreadable; `GetResBody` lets you provide a readable body. | `nil` | ### Example ```go package main import ( "os" middleware "github.com/gofiber/contrib/v3/zerolog" "github.com/gofiber/fiber/v3" "github.com/rs/zerolog" ) func main() { app := fiber.New() logger := zerolog.New(os.Stderr).With().Timestamp().Logger() app.Use(middleware.New(middleware.Config{ Logger: &logger, })) app.Get("/", func(c fiber.Ctx) error { return c.SendString("Hello, World!") }) if err := app.Listen(":3000"); err != nil { logger.Fatal().Err(err).Msg("Fiber app error") } } ``` --- ## Retry Addon The Retry addon for [Fiber](https://github.com/gofiber/fiber) retries failed network operations using exponential backoff with jitter. It repeatedly invokes a function until it succeeds or the maximum number of attempts is exhausted. Jitter at each step breaks client synchronization and helps avoid collisions. If all attempts fail, the addon returns an error. ### Table of Contents - [Signatures](#signatures) - [Examples](#examples) - [Default Config](#default-config) - [Custom Config](#custom-config) - [Config](#config) - [Default Config Example](#default-config-example) ### Signatures ```go func NewExponentialBackoff(config ...retry.Config) *retry.ExponentialBackoff func (e *retry.ExponentialBackoff) Retry(f func() error) error ``` `Retry` calls `f` up to `MaxRetryCount` times in total and returns `nil` as soon as `f` returns `nil`. If every attempt fails, it returns the error of the last attempt. Each call to `Retry` starts from `InitialInterval` with its own state, so one `ExponentialBackoff` can be shared. A field left at zero or below takes its default from the [Default Config](#default-config-example), also on a `retry.ExponentialBackoff` you build as a struct literal instead of with `NewExponentialBackoff`. Even `retry.ExponentialBackoff{}` therefore calls `f` at least once, and up to 10 times when it keeps failing, waiting between the attempts as the default does: about 160 seconds in all. A literal that sets only some fields waits as well, so `retry.ExponentialBackoff{MaxRetryCount: 2}` no longer retries at once. To retry without waiting, set `MaxBackoffTime` to a short duration such as `time.Nanosecond`. ### Examples ```go package main import ( "fmt" "github.com/gofiber/fiber/v3/addon/retry" "github.com/gofiber/fiber/v3/client" ) func main() { expBackoff := retry.NewExponentialBackoff(retry.Config{}) // Local variables used inside Retry var resp *client.Response var err error // Retry a network request and return an error to signal another attempt err = expBackoff.Retry(func() error { client := client.New() resp, err = client.Get("https://gofiber.io") if err != nil { return fmt.Errorf("GET gofiber.io failed: %w", err) } if resp.StatusCode() != 200 { return fmt.Errorf("GET gofiber.io did not return 200 OK") } return nil }) // If all retries failed, panic if err != nil { panic(err) } fmt.Printf("GET gofiber.io succeeded with status code %d\n", resp.StatusCode()) } ``` ### Default Config ```go retry.NewExponentialBackoff() ``` ### Custom Config ```go retry.NewExponentialBackoff(retry.Config{ InitialInterval: 2 * time.Second, MaxBackoffTime: 64 * time.Second, Multiplier: 2.0, MaxRetryCount: 15, }) ``` ### Config ```go // Config defines the config for addon. type Config struct { // InitialInterval defines the initial time interval for backoff algorithm. // // Optional. Default: 1 * time.Second InitialInterval time.Duration // MaxBackoffTime defines maximum time duration for backoff algorithm. When // the algorithm is reached this time, rest of the retries will be maximum // 32 seconds. // // Optional. Default: 32 * time.Second MaxBackoffTime time.Duration // Multiplier defines multiplier number of the backoff algorithm. // // Optional. Default: 2.0 Multiplier float64 // MaxRetryCount defines maximum retry count for the backoff algorithm. // // Optional. Default: 10 MaxRetryCount int // currentInterval tracks the current waiting time. // // Optional. Default: 1 * time.Second currentInterval time.Duration } ``` ### Default Config Example ```go // DefaultConfig is the default config for retry. var DefaultConfig = Config{ InitialInterval: 1 * time.Second, MaxBackoffTime: 32 * time.Second, Multiplier: 2.0, MaxRetryCount: 10, currentInterval: 1 * time.Second, } ``` --- ## 🚀 App Use the index to jump straight to any `App` method; filter by name or by category: ### Routing #### Route Handlers Registers a route bound to a specific [HTTP method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods). The canonical handler is `func(fiber.Ctx) error`; Fiber also accepts `func(fiber.Ctx)` and runs it as if it returned `nil`. ```go title="Signatures" // HTTP methods func (app *App) Get(path string, handler any, handlers ...any) Router func (app *App) Head(path string, handler any, handlers ...any) Router func (app *App) Post(path string, handler any, handlers ...any) Router func (app *App) Put(path string, handler any, handlers ...any) Router func (app *App) Delete(path string, handler any, handlers ...any) Router func (app *App) Connect(path string, handler any, handlers ...any) Router func (app *App) Options(path string, handler any, handlers ...any) Router func (app *App) Trace(path string, handler any, handlers ...any) Router func (app *App) Patch(path string, handler any, handlers ...any) Router func (app *App) Query(path string, handler any, handlers ...any) Router // Add registers the same handlers on multiple methods at once. // The handlers run in order, starting with `handler` and then the variadic `handlers`. func (app *App) Add(methods []string, path string, handler any, handlers ...any) Router // All registers the route on every HTTP method at the EXACT path // (unlike Use, which is prefix-matched). func (app *App) All(path string, handler any, handlers ...any) Router ``` ```go title="Examples" // Simple GET handler app.Get("/api/list", func(c fiber.Ctx) error { return c.SendString("I'm a GET request!") }) // Simple POST handler app.Post("/api/register", func(c fiber.Ctx) error { return c.SendString("I'm a POST request!") }) ``` Beyond the native `func(fiber.Ctx)` forms, Fiber also adapts Express-style, `net/http`, and `fasthttp` handlers. See [Handler types](../guide/routing.md#handler-types) in the routing guide for the full list of supported shapes. #### Use `Use` mounts middleware on a **prefix** (or **mount**) path: it runs for every request whose path begins with that prefix, on any HTTP method. Prefixes require either an exact match or a slash boundary, so `/john` matches `/john` and `/john/doe` but not `/johnnnnn`. Parameter tokens like `:name`, `:name?`, `*`, and `+` are still expanded before the boundary check runs. Called without a path, `Use` matches every request. ```go title="Signature" func (app *App) Use(args ...any) Router // Fiber inspects args to support these common usage patterns: // - app.Use(handler, handlers ...any) // - app.Use(path string, handler, handlers ...any) // - app.Use(paths []string, handler, handlers ...any) // - app.Use(path string, subApp *App) // - app.Use(paths []string, subApp *App) ``` Each handler argument can independently be a Fiber handler (with or without an `error` return), an Express-style callback, a `net/http` handler, or any other supported shape including fasthttp callbacks that return errors. ```go title="Examples" // Match any request app.Use(func(c fiber.Ctx) error { return c.Next() }) // Match request starting with /api app.Use("/api", func(c fiber.Ctx) error { return c.Next() }) // Match requests starting with /api or /home (multiple-prefix support) app.Use([]string{"/api", "/home"}, func(c fiber.Ctx) error { return c.Next() }) // Attach multiple handlers (they run in order; each must call c.Next() to continue) app.Use("/api", func(c fiber.Ctx) error { c.Set("X-Custom-Header", "value") return c.Next() }, func(c fiber.Ctx) error { return c.Next() }) // Mount a sub-app app.Use("/api", api) ``` #### Mounting Mount another Fiber instance with [`app.Use`](#use), similar to Express's [`router.use`](https://expressjs.com/en/api.html#router.use). ```go title="Example" package main import ( "log" "github.com/gofiber/fiber/v3" ) func main() { app := fiber.New() micro := fiber.New() // Mount the micro app on the "/john" route app.Use("/john", micro) // GET /john/doe -> 200 OK micro.Get("/doe", func(c fiber.Ctx) error { return c.SendStatus(fiber.StatusOK) }) log.Fatal(app.Listen(":3000")) } ``` :::caution Unlike Express, Fiber does not strip the mount prefix. Inside the mounted app, `c.Path()` still returns the full request path (`/john/doe`, not `/doe`). The counterpart of `req.baseUrl` is [`c.MountPath()`](./ctx.md#mountpath), which returns the prefix the running route's sub-app was mounted under (`/john`). ::: Passing a slice of prefixes mounts the sub-app under each of them (`app.Use([]string{"/john", "/jane"}, micro)`). Mounting an app onto itself panics. :::note The routes of a mounted app are matched under the settings of the top-level app, at every nesting depth: its [`CaseSensitive`](./fiber.md#casesensitive), [`StrictRouting`](./fiber.md#strictrouting) and [`UnescapePath`](./fiber.md#unescapepath) apply, not the sub-app's own and not those of the app it is mounted on. A sub-app that relies on one of them for its routes needs the top-level app to set it too. The settings that handlers read at request time, such as `JSONEncoder`, `StructValidator` and `PassLocalsToViews`, and those of the server itself, such as `BodyLimit`, come from the top-level app as well. A sub-app's own [`ErrorHandler`](#errorhandler), `Views` and `ViewsLayout` still apply to the requests under its mount path, but only when the whole mount path, from the top-level app down, is literal. Under a parameter, as in `app.Use("/t/:tenant", tenantApp)`, the routes match and run while the nearest enclosing mount with a literal path, or the top-level app, supplies the error handler and the view engine. A [`Domain`](#domain) mount matches a parameterized path as a pattern, so its sub-app keeps its own. The [`RegexHandler`](./fiber.md#regexhandler) of a sub-app mounted on the top-level app compiles the `regex()` constraints in the full path of every route below it, the mount prefix included, so an app mounted deeper does not use its own. ::: #### MountPath `MountPath` returns the path pattern on which a sub-app was mounted, or an empty string for an app that is not mounted. An app mounted under several prefixes reports the last one. ```go title="Signature" func (app *App) MountPath() string ``` ```go title="Example" package main import ( "fmt" "github.com/gofiber/fiber/v3" ) func main() { app := fiber.New() one := fiber.New() two := fiber.New() three := fiber.New() two.Use("/three", three) one.Use("/two", two) app.Use("/one", one) fmt.Println("Mount paths:") fmt.Println("one.MountPath():", one.MountPath()) // "/one" fmt.Println("two.MountPath():", two.MountPath()) // "/one/two" fmt.Println("three.MountPath():", three.MountPath()) // "/one/two/three" fmt.Println("app.MountPath():", app.MountPath()) // "" } ``` :::caution Mounting order is important for `MountPath`. To get mount paths properly, you should start mounting from the deepest app. ::: #### Group You can group routes by creating a `*Group` struct. ```go title="Signature" func (app *App) Group(prefix string, handlers ...any) Router ``` ```go title="Example" package main import ( "log" "github.com/gofiber/fiber/v3" ) func main() { app := fiber.New() api := app.Group("/api", handler) // /api v1 := api.Group("/v1", handler) // /api/v1 v1.Get("/list", handler) // /api/v1/list v1.Get("/user", handler) // /api/v1/user v2 := api.Group("/v2", handler) // /api/v2 v2.Get("/list", handler) // /api/v2/list v2.Get("/user", handler) // /api/v2/user log.Fatal(app.Listen(":3000")) } func handler(c fiber.Ctx) error { return c.SendString("Handler response") } ``` #### RouteChain Returns an instance of a single route, which you can then use to handle HTTP verbs with optional middleware. Similar to [`Express`](https://expressjs.com/en/api.html#app.route). ```go title="Signature" func (app *App) RouteChain(path string) Register ```
Click here to see the `Register` interface ```go type Register interface { All(handler any, handlers ...any) Register Get(handler any, handlers ...any) Register Head(handler any, handlers ...any) Register Post(handler any, handlers ...any) Register Put(handler any, handlers ...any) Register Delete(handler any, handlers ...any) Register Connect(handler any, handlers ...any) Register Options(handler any, handlers ...any) Register Trace(handler any, handlers ...any) Register Patch(handler any, handlers ...any) Register Query(handler any, handlers ...any) Register Add(methods []string, handler any, handlers ...any) Register RouteChain(path string) Register } ```
```go title="Example" package main import ( "log" "github.com/gofiber/fiber/v3" ) func main() { app := fiber.New() // Use `RouteChain` as a chainable route declaration method app.RouteChain("/test").Get(func(c fiber.Ctx) error { return c.SendString("GET /test") }) app.RouteChain("/events").All(func(c fiber.Ctx) error { // Runs for all HTTP verbs first // Think of it as route-specific middleware! return c.Next() }). Get(func(c fiber.Ctx) error { return c.SendString("GET /events") }). Post(func(c fiber.Ctx) error { // Maybe add a new event... return c.SendString("POST /events") }) // Combine multiple routes app.RouteChain("/reports").RouteChain("/daily").Get(func(c fiber.Ctx) error { return c.SendString("GET /reports/daily") }) // Use multiple methods app.RouteChain("/api").Get(func(c fiber.Ctx) error { return c.SendString("GET /api") }).Post(func(c fiber.Ctx) error { return c.SendString("POST /api") }) log.Fatal(app.Listen(":3000")) } ``` #### Route Defines routes with a common prefix inside the supplied function. Internally it uses [`Group`](#group) to create a sub-router and accepts an optional name prefix. ```go title="Signature" func (app *App) Route(prefix string, fn func(router Router), name ...string) Router ``` ```go title="Example" app.Route("/test", func(api fiber.Router) { api.Get("/foo", handler).Name("foo") // /test/foo (name: test.foo) api.Get("/bar", handler).Name("bar") // /test/bar (name: test.bar) }, "test.") ``` #### Domain Creates a router scoped to a specific hostname pattern. Routes registered through the returned `Router` only match requests whose hostname (from `c.Hostname()`) matches the pattern. Domain names are matched case-insensitively per [RFC 4343](https://www.rfc-editor.org/rfc/rfc4343). When `TrustProxy` is enabled and the proxy is trusted, the hostname may be derived from the `X-Forwarded-Host` header instead of the `Host` header. To prevent header spoofing, you must both enable `TrustProxy` and configure [`TrustProxyConfig`](https://docs.gofiber.io/api/fiber#trustproxyconfig) with the IPs or ranges of your trusted proxies. See the [TrustProxy documentation](https://docs.gofiber.io/api/fiber#trustproxy) for details. The pattern can contain parameters prefixed with `:`. Use [`DomainParam`](#domainparam) to retrieve them inside handlers. `Domain` panics when the pattern is empty, has an empty label, has more than 16 labels or 253 characters, has a constant label of more than 63 characters, or has a label with a character other than an ASCII letter, a digit or `-` (a parameter name may also contain `_`). A request hostname that breaks the same limits never matches. Groups offer `Domain` too: `api.Domain("api.example.com")` keeps the group prefix and adds the host filter. Domain routing has **zero performance impact** on routes that don't use it — the hostname check is applied as a handler wrapper, not a change to the core router. :::note Because domain filtering is applied at handler-execution time (not during route matching), Fiber's `405 Method Not Allowed` logic may advertise methods from domain-scoped routes even when the requesting host does not match the domain pattern. This is a known trade-off of the handler-wrapping approach — it avoids core router changes while keeping non-domain routes unaffected. ::: :::note When mounting sub-applications via `Domain(...).Use(*fiber.App)`, routes are cloned from the sub-app at mount time. This means the same sub-app can safely be mounted on multiple domains without double-wrapping, but routes registered on the sub-app **after** mounting will not inherit domain filtering. Register all sub-app routes before mounting. Apps that the sub-app has mounted itself are cloned along with it, so nested routes are domain-filtered as well. The same "register before mounting" rule applies to them. A domain-mounted sub-app's `ErrorHandler` and `Views` are host-scoped like its routes: they apply to requests whose hostname matches the pattern, and requests on other hosts fall back to the parent's. Two sub-apps mounted at the same path on different domains therefore keep their own configuration. A sub-app that sets none of its own inherits them from the app it is mounted inside, as with a plain mount; a separately mounted app that merely covers the same paths and hosts is not inherited from. ::: ```go title="Signature" func (app *App) Domain(host string) Router ``` ```go title="Example" package main import ( "log" "github.com/gofiber/fiber/v3" ) func main() { app := fiber.New() // Static domain — only matches requests to api.example.com app.Domain("api.example.com").Get("/users", func(c fiber.Ctx) error { return c.SendString("API users list") }) // Domain with parameter app.Domain(":user.blog.example.com").Get("/", func(c fiber.Ctx) error { user := fiber.DomainParam(c, "user") return c.SendString(user + "'s blog") }) // Composable with groups and middleware admin := app.Domain("admin.example.com") admin.Use(func(c fiber.Ctx) error { // Only runs for admin.example.com c.Set("X-Admin", "true") return c.Next() }) admin.Get("/dashboard", func(c fiber.Ctx) error { return c.SendString("Admin Dashboard") }) // Mount sub-applications on domain routers subApp := fiber.New() subApp.Get("/users", func(c fiber.Ctx) error { return c.SendString("Users list") }) app.Domain("api.example.com").Use("/api", subApp) // Fallback for unmatched domains app.Get("/", func(c fiber.Ctx) error { return c.SendString("Default site") }) log.Fatal(app.Listen(":3000")) } ``` ##### DomainParam Returns the value of a domain parameter captured by a [`Domain`](#domain) pattern. If the key is not found, the optional default value is returned. ```go title="Signature" func DomainParam(c Ctx, key string, defaultValue ...string) string ``` ```go title="Example" // Pattern: ":tenant.example.com" // Request Host: acme.example.com app.Domain(":tenant.example.com").Get("/", func(c fiber.Ctx) error { tenant := fiber.DomainParam(c, "tenant") // "acme" missing := fiber.DomainParam(c, "missing", "none") // "none" return c.SendString(tenant + " " + missing) }) ``` #### HandlersCount Returns the number of registered handlers. ```go title="Signature" func (app *App) HandlersCount() uint32 ``` #### Stack Returns the underlying router stack: one slice of routes for each entry of [`RequestMethods`](./fiber.md#requestmethods), in that order, and `nil` for a method that has no routes. The `HEAD` routes Fiber generates for `GET` routes only appear once the app has started or [`RebuildTree`](#rebuildtree) has run. ```go title="Signature" func (app *App) Stack() [][]*Route ``` ```go title="Example" package main import ( "encoding/json" "fmt" "log" "github.com/gofiber/fiber/v3" ) var handler = func(c fiber.Ctx) error { return nil } func main() { app := fiber.New() app.Get("/john/:age", handler) app.Post("/register", handler) // Listen generates the HEAD routes when it starts the app. // RebuildTree generates them now, so the stack below shows them. app.RebuildTree() data, _ := json.MarshalIndent(app.Stack(), "", " ") fmt.Println(string(data)) log.Fatal(app.Listen(":3000")) } ```
Click here to see the result ```json [ [ { "params": [ "age" ], "method": "GET", "name": "", "path": "/john/:age" } ], [ { "params": [ "age" ], "method": "HEAD", "name": "", "path": "/john/:age" } ], [ { "params": null, "method": "POST", "name": "", "path": "/register" } ], null, null, null, null, null, null, null ] ```
#### Name This method assigns the name to the latest created route. When the latest call registered several methods at once, as `Add` or `All` do, every one of those routes receives the name. The `HEAD` route that Fiber generates for a named `GET` route carries the same name. ```go title="Signature" func (app *App) Name(name string) Router ``` ```go title="Example" package main import ( "encoding/json" "fmt" "log" "github.com/gofiber/fiber/v3" ) func main() { var handler = func(c fiber.Ctx) error { return nil } app := fiber.New() app.Get("/", handler) app.Name("index") app.Get("/doe", handler).Name("home") app.Trace("/tracer", handler).Name("tracert") app.Delete("/delete", handler).Name("delete") a := app.Group("/a") a.Name("fd.") a.Get("/test", handler).Name("test") // Generate the HEAD routes now, as Listen does when it starts the app. app.RebuildTree() data, _ := json.MarshalIndent(app.Stack(), "", " ") fmt.Println(string(data)) log.Fatal(app.Listen(":3000")) } ```
Click here to see the result ```json [ [ { "params": null, "method": "GET", "name": "index", "path": "/" }, { "params": null, "method": "GET", "name": "home", "path": "/doe" }, { "params": null, "method": "GET", "name": "fd.test", "path": "/a/test" } ], [ { "params": null, "method": "HEAD", "name": "index", "path": "/" }, { "params": null, "method": "HEAD", "name": "home", "path": "/doe" }, { "params": null, "method": "HEAD", "name": "fd.test", "path": "/a/test" } ], null, null, [ { "params": null, "method": "DELETE", "name": "delete", "path": "/delete" } ], null, null, [ { "params": null, "method": "TRACE", "name": "tracert", "path": "/tracer" } ], null, null ] ```
#### GetRoute This method retrieves a route by its name. The returned `Route` can be inspected or used to generate a URL directly with `route.URL(params)`. When no route has that name, `GetRoute` returns the zero `Route`, and its `URL` method returns `ErrNotFound`. ```go title="Signature" func (app *App) GetRoute(name string) Route ``` ```go title="Example" package main import ( "encoding/json" "fmt" "log" "github.com/gofiber/fiber/v3" ) var handler = func(c fiber.Ctx) error { return nil } func main() { app := fiber.New() app.Get("/", handler).Name("index") app.Get("/user/:name/:id", handler).Name("user") route := app.GetRoute("index") data, _ := json.MarshalIndent(route, "", " ") fmt.Println(string(data)) userRoute := app.GetRoute("user") location, _ := userRoute.URL(fiber.Map{"name": "john", "id": 1}) fmt.Println(location) // /user/john/1 log.Fatal(app.Listen(":3000")) } ```
Click here to see the result ```json { "params": null, "method": "GET", "name": "index", "path": "/" } ```
:::note `Route.URL` automatically percent-encodes parameter values with URL path-segment rules. Greedy parameters (`*` and `+`) preserve `/` inside the matched path tail; delimiters such as `?` and `#` remain parameter data instead of restructuring the generated URL. [`GetRouteURL`](./ctx.md#getrouteurl) and [`Redirect().Route`](./redirect.md#route) apply the same encoding. These take the value as data, not as URL text. Representable values use the same encoding under any configuration. With `UnescapePath` off (the default) `c.Params` returns the value still percent-encoded, so forwarding it straight back encodes the `%` a second time. Turn `UnescapePath` on, or decode with [`url.PathUnescape`](https://pkg.go.dev/net/url#PathUnescape) first. An app with `UnescapePath` enabled decodes `%2F` before route matching, so an ordinary single-segment parameter holding a `/` cannot round-trip and returns `ErrRouteNotRepresentable`. Greedy (`*` or `+`), adjacent single-byte parameters and required parameters with a single-byte non-slash terminator retain their existing slash-matching rules. An optional parameter with such a terminator does not match a `/`, so a decoded `/` in its value returns `ErrRouteNotRepresentable`. Dot-containing values are also rejected when the composed path has a `.` or `..` segment that would be removed during normalization. Surrounding route constants are considered: `:name.txt` with `name="."` remains representable. Pass a different value or use a route that can represent it; do not pre-encode it. ::: #### GetRoutes This method retrieves all routes. ```go title="Signature" func (app *App) GetRoutes(filterUseOption ...bool) []Route ``` When `filterUseOption` is set to `true`, it filters out routes registered by middleware. ```go title="Example" package main import ( "encoding/json" "fmt" "log" "github.com/gofiber/fiber/v3" ) func main() { app := fiber.New() app.Post("/", func(c fiber.Ctx) error { return c.SendString("Hello, World!") }).Name("index") routes := app.GetRoutes(true) data, _ := json.MarshalIndent(routes, "", " ") fmt.Println(string(data)) log.Fatal(app.Listen(":3000")) } ```
Click here to see the result ```json [ { "params": null, "method": "POST", "name": "index", "path": "/" } ] ```
### Config `Config` returns the [app config](./fiber.md#config) as a value (read-only). ```go title="Signature" func (app *App) Config() Config ``` ### Handler `Handler` returns the server handler that can be used to serve custom [`*fasthttp.RequestCtx`](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx) requests. ```go title="Signature" func (app *App) Handler() fasthttp.RequestHandler ``` :::caution When Fiber starts its own server, it closes the connection after the response to a request that carries both `Content-Length` and `Transfer-Encoding`, using a `HeaderReceived` callback it installs on that server. A `fasthttp.Server` of your own that serves `Handler()` does not get that callback and needs the same check in its own `HeaderReceived`. See [Protocol error responses](../whats_new.md#protocol-error-responses). ::: ### AcquireCtx / ReleaseCtx `AcquireCtx` takes a context from the app's pool and binds it to a [`*fasthttp.RequestCtx`](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx); `ReleaseCtx` puts it back. Use them when you run a handler yourself instead of going through [`Handler`](#handler), as the [adaptor middleware](../middleware/adaptor.md) does. ```go title="Signature" func (app *App) AcquireCtx(ctx *fasthttp.RequestCtx) CustomCtx func (app *App) ReleaseCtx(c CustomCtx) ``` ```go title="Example" func serve(app *fiber.App, ctx *fasthttp.RequestCtx, handler fiber.Handler) { c := app.AcquireCtx(ctx) defer app.ReleaseCtx(c) if err := handler(c); err != nil { _ = app.ErrorHandler(c, err) } } ``` Release every context you acquire, and don't use it afterwards: the next request reuses it. `ReleaseCtx` does not return an abandoned context (see [`Abandon`](./ctx.md#abandon)) to the pool, which is how the [timeout middleware](../middleware/timeout.md) keeps a context away from the pool while its handler is still running. When [`ScheduleReclaim`](./ctx.md#abandon) armed the context, the call only tells the reclaimer that the request handler is done, and the reclaimer pools it once the other goroutine finishes too. ### ErrorHandler `ErrorHandler` executes the process defined for the application in case of errors. This is used in some cases in middlewares. ```go title="Signature" func (app *App) ErrorHandler(ctx Ctx, err error) error ``` When the request path falls under a mounted sub-app that sets its own `ErrorHandler`, that handler runs instead of the parent's. The deepest mount covering the path wins, and a sub-app that sets none inherits the handler of the nearest mount around it. The mount path, taken from the top-level app down, is compared with the request path as literal text, so a sub-app mounted under a parameter such as `/t/:tenant` is never found, nor is an app mounted inside it: their routes run, but the nearest enclosing mount with a literal path, or the top-level app, supplies the handler. A sub-app mounted through [`Domain`](#domain) matches a parameterized path as a pattern, and contributes its handler only for requests whose hostname matches the pattern. `ErrorHandler` also runs for a request that the HTTP server refuses while reading it, with a `*fiber.Error` carrying the status Fiber chose, such as `413` for a body over `BodyLimit` or `431` for headers over `ReadBufferSize`. See [Errors Before a Handler Runs](../guide/error-handling.md#errors-before-a-handler-runs) for the full list. When the request method is a known one, middleware registered with `Use` still runs for such a request and sees that status. A request whose response fasthttp already holds as a timeout response, as after the [timeout middleware](../middleware/timeout.md) has answered it, is left alone: `ErrorHandler` returns `nil` without running the configured handler, since anything written now would be ignored and the handler that timed out may still be using the context. The error still reaches the outer middleware. ### NewWithCustomCtx `NewWithCustomCtx` creates a new `*App` and sets the custom context factory function at construction time. ```go title="Signature" func NewWithCustomCtx(fn func(app *App) CustomCtx, config ...Config) *App ``` ```go title="Example" package main import ( "log" "github.com/gofiber/fiber/v3" ) type CustomCtx struct { fiber.DefaultCtx } func (c *CustomCtx) Params(key string, defaultValue ...string) string { return "prefix_" + c.DefaultCtx.Params(key) } func main() { app := fiber.NewWithCustomCtx(func(app *fiber.App) fiber.CustomCtx { return &CustomCtx{ DefaultCtx: *fiber.NewDefaultCtx(app), } }) app.Get("/:id", func(c fiber.Ctx) error { return c.SendString(c.Params("id")) }) log.Fatal(app.Listen(":3000")) } ``` `NewWithCustomCtx` works with global and route middleware. Fiber passes your custom context through `c.Next()`, so overridden methods (for example a custom `JSON`) still run after `app.Use(...)`. ```go title="Custom JSON with middleware" package main import ( "log" "github.com/gofiber/fiber/v3" ) type APICtx struct { fiber.DefaultCtx } type envelope struct { Code int `json:"code"` Data any `json:"data"` Message string `json:"message"` } func (c *APICtx) JSON(data any, ctype ...string) error { return c.DefaultCtx.JSON(envelope{ Code: fiber.StatusOK, Data: data, Message: "OK", }, ctype...) } func main() { app := fiber.NewWithCustomCtx(func(app *fiber.App) fiber.CustomCtx { return &APICtx{DefaultCtx: *fiber.NewDefaultCtx(app)} }) app.Use(func(c fiber.Ctx) error { return c.Next() }) app.Get("/vvv", func(c fiber.Ctx) error { // Uses APICtx.JSON even though middleware called Next return c.JSON(fiber.Map{"a": "b"}) }) log.Fatal(app.Listen(":3000")) } ``` :::tip Prefer embedding `fiber.DefaultCtx` (by value or pointer) and constructing it with `fiber.NewDefaultCtx(app)`. Overridden methods are resolved on the concrete `CustomCtx` type Fiber stores for the request. ::: ### RegisterCustomBinder You can register custom binders to use with [`Bind().Custom("name")`](bind.md#custom). They should be compatible with the `CustomBinder` interface. ```go title="Signature" func (app *App) RegisterCustomBinder(binder CustomBinder) ``` ```go title="Example" package main import ( "log" "github.com/gofiber/fiber/v3" "gopkg.in/yaml.v2" ) type User struct { Name string `yaml:"name"` } type customBinder struct{} func (*customBinder) Name() string { return "custom" } func (*customBinder) MIMETypes() []string { return []string{"application/yaml"} } func (*customBinder) Parse(c fiber.Ctx, out any) error { // Parse YAML body return yaml.Unmarshal(c.Body(), out) } func main() { app := fiber.New() // Register custom binder app.RegisterCustomBinder(&customBinder{}) app.Post("/custom", func(c fiber.Ctx) error { var user User // Use Custom binder by name if err := c.Bind().Custom("custom", &user); err != nil { return err } return c.JSON(user) }) app.Post("/normal", func(c fiber.Ctx) error { var user User // Custom binder is used by the MIME type if err := c.Bind().Body(&user); err != nil { return err } return c.JSON(user) }) log.Fatal(app.Listen(":3000")) } ``` ### RegisterCustomConstraint `RegisterCustomConstraint` allows you to register custom constraints. ```go title="Signature" func (app *App) RegisterCustomConstraint(constraint CustomConstraint) ``` See the [Custom Constraint](../guide/routing.md#custom-constraint) section for more information. ### SetTLSHandler Use `SetTLSHandler` to set [`ClientHelloInfo`](https://datatracker.ietf.org/doc/html/rfc8446#section-4.1.2) when using TLS with a `Listener`. ```go title="Signature" func (app *App) SetTLSHandler(tlsHandler *TLSHandler) ``` `Listen` sets a handler up itself when it serves `CertFile` and `CertKeyFile`. With `Listener`, register a `TLSHandler` and use its `GetClientInfo` method as the `GetCertificate` callback of the `tls.Config` the listener runs with. [`Ctx.ClientHelloInfo`](./ctx.md#clienthelloinfo) then returns the hello message of the connection a request arrived on. It returns `nil` where `crypto/tls` does not call `GetCertificate`: for a resumed TLS 1.3 connection, whatever the configuration, and, with `Certificates` set, as in the example below and in the config `Listen` builds, for a client that sends no server name (SNI). ```go title="Example" cert, err := tls.LoadX509KeyPair("server.crt", "server.key") if err != nil { log.Fatal(err) } handler := &fiber.TLSHandler{} app.SetTLSHandler(handler) ln, err := tls.Listen("tcp", ":443", &tls.Config{ MinVersion: tls.VersionTLS12, Certificates: []tls.Certificate{cert}, GetCertificate: handler.GetClientInfo, // records the ClientHelloInfo of each connection }) if err != nil { log.Fatal(err) } app.Get("/", func(c fiber.Ctx) error { if info := c.ClientHelloInfo(); info != nil { return c.SendString(info.ServerName) } return c.SendStatus(fiber.StatusNoContent) }) log.Fatal(app.Listener(ln)) ``` ### State / SharedState `State()` returns in-process state (local to the current process). `SharedState()` returns storage-backed state intended for prefork/multi-process sharing. ```go title="Signature" func (app *App) State() *State func (app *App) SharedState() *SharedState ``` See [State Management](./state.md) for usage and examples. ### Test Testing your application is done with the `Test` method. Use this method for creating `_test.go` files or when you need to debug your routing logic. The default timeout is `1s`; to disable a timeout altogether, pass a `TestConfig` struct with `Timeout: 0`. ```go title="Signature" func (app *App) Test(req *http.Request, config ...TestConfig) (*http.Response, error) ``` ```go title="Example" package main import ( "fmt" "io" "net/http/httptest" "github.com/gofiber/fiber/v3" ) func main() { app := fiber.New() // Create route with GET method for test: app.Get("/", func(c fiber.Ctx) error { fmt.Println(c.BaseURL()) // => http://google.com fmt.Println(c.Get("X-Custom-Header")) // => hi return c.SendString("hello, World!") }) // Create http.Request req := httptest.NewRequest("GET", "http://google.com", nil) req.Header.Set("X-Custom-Header", "hi") // Perform the test resp, _ := app.Test(req) // Do something with the results: if resp.StatusCode == fiber.StatusOK { body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) // => hello, World! } } ``` If not provided, TestConfig is set to the following defaults: ```go title="Default TestConfig" config := fiber.TestConfig{ Timeout: time.Second, FailOnTimeout: true, } ``` When the handler has not answered within `Timeout`, `Test` returns `os.ErrDeadlineExceeded` if `FailOnTimeout` is `true`. If it is `false`, `Test` waits up to one more second and then reads whatever the handler has written by then. When the handler sends no response at all, for example after `c.Drop()`, `Test` returns `fiber.ErrTestGotEmptyResponse`. :::caution Calling `app.Test(req)` uses the defaults above. Supplying an empty `fiber.TestConfig{}` instead is **not** equivalent; it is the same as supplying: ```go title="Empty TestConfig" cfg := fiber.TestConfig{ Timeout: 0, FailOnTimeout: false, } ``` This would make a Test that has no timeout. ::: ### Hooks `Hooks` is a method to return the [hooks](./hooks.md) property. ```go title="Signature" func (app *App) Hooks() *Hooks ``` ### Route Management Routes are normally defined before the app starts. You can also add or remove them at runtime with the methods below, but these operations are **not thread-safe** and are performance-intensive, so use them sparingly and only in development. #### RebuildTree The `RebuildTree` method is designed to rebuild the route tree and enable dynamic route registration. It returns a pointer to the `App` instance. GET routes registered since the last rebuild also receive their [automatic HEAD route](../guide/routing.md#automatic-head-routes) here. ```go title="Signature" func (app *App) RebuildTree() *App ``` **Note:** Use this method with caution. It is **not** thread-safe and calling it can be very performance-intensive, so it should be used sparingly and only in development mode. Avoid using it concurrently. Here’s an example of how to define and register routes dynamically: ```go title="Example" package main import ( "log" "github.com/gofiber/fiber/v3" ) func main() { app := fiber.New() app.Get("/define", func(c fiber.Ctx) error { // Define a new route dynamically app.Get("/dynamically-defined", func(c fiber.Ctx) error { return c.SendStatus(fiber.StatusOK) }) // Rebuild the route tree to register the new route app.RebuildTree() return c.SendStatus(fiber.StatusOK) }) log.Fatal(app.Listen(":3000")) } ``` In this example, a new route is defined and then `RebuildTree()` is called to ensure the new route is registered and available. #### RemoveRoute This method removes a route by path. You must call the `RebuildTree()` method after the removal to finalize the update and rebuild the routing tree. If no methods are specified, the route will be removed for all HTTP methods defined in the app. To limit removal to specific methods, provide them as additional arguments. ```go title="Signature" func (app *App) RemoveRoute(path string, methods ...string) ``` ```go title="Example" package main import ( "log" "github.com/gofiber/fiber/v3" ) func main() { app := fiber.New() app.Get("/api/feature-a", func(c fiber.Ctx) error { app.RemoveRoute("/api/feature", fiber.MethodGet) app.RebuildTree() // Redefine route app.Get("/api/feature", func(c fiber.Ctx) error { return c.SendString("Testing feature-a") }) app.RebuildTree() return c.SendStatus(fiber.StatusOK) }) app.Get("/api/feature-b", func(c fiber.Ctx) error { app.RemoveRoute("/api/feature", fiber.MethodGet) app.RebuildTree() // Redefine route app.Get("/api/feature", func(c fiber.Ctx) error { return c.SendString("Testing feature-b") }) app.RebuildTree() return c.SendStatus(fiber.StatusOK) }) log.Fatal(app.Listen(":3000")) } ``` #### RemoveRouteByName This method removes a route by name. If no methods are specified, the route will be removed for all HTTP methods defined in the app. To limit removal to specific methods, provide them as additional arguments. ```go title="Signature" func (app *App) RemoveRouteByName(name string, methods ...string) ``` #### RemoveRouteFunc This method removes a route by function having `*Route` parameter. The `Path` field the function sees is the path as it was registered, not the normalized one. If no methods are specified, the route will be removed for all HTTP methods defined in the app. To limit removal to specific methods, provide them as additional arguments. ```go title="Signature" func (app *App) RemoveRouteFunc(matchFunc func(r *Route) bool, methods ...string) ``` ### Helpers #### GetString Returns `s` unchanged when [`Immutable`](./fiber.md#immutable) is disabled or `s` resides in read-only memory. Otherwise, it returns a detached copy using `strings.Clone`. ```go title="Signature" func (app *App) GetString(s string) string ``` #### GetBytes Returns `b` unchanged when [`Immutable`](./fiber.md#immutable) is disabled or `b` resides in read-only memory. Otherwise, it returns a detached copy. ```go title="Signature" func (app *App) GetBytes(b []byte) []byte ``` #### ReloadViews Reloads the configured view engine on demand by calling its `Load` method. The engines of mounted sub-apps are reloaded too. Use this helper in development workflows (e.g., file watchers or debug-only routes) to pick up template changes without restarting the server. Returns an error if reloading fails, and `ErrNoViewEngineConfigured` if neither the app nor a mounted sub-app has a view engine (a typed nil `Views` counts as none). ```go title="Signature" func (app *App) ReloadViews() error ``` ```go title="Example" app := fiber.New(fiber.Config{Views: engine}) app.Get("/dev/reload", func(c fiber.Ctx) error { if err := app.ReloadViews(); err != nil { return err } return c.SendString("Templates reloaded") }) ``` #### Render `Render` writes a template through the configured view engine without creating an HTTP response. It applies `ViewsLayout` when no layout is supplied and is safe to use concurrently with `ReloadViews`. It returns `ErrNoViewEngineConfigured` when the app has no view engine, a typed nil `Views` included. ```go title="Signature" func (app *App) Render(out io.Writer, name string, binding any, layouts ...string) error ``` Capture the app, rather than a pooled request context, when rendering from an asynchronous stream writer: ```go app.Get("/events", func(c fiber.Ctx) error { return c.SendStreamWriter(func(w *bufio.Writer) { if err := app.Render(w, "partial", fiber.Map{"Title": "update"}); err != nil { return } if err := w.Flush(); err != nil { return } }) }) ``` --- ## 📎 Bind Bindings parse request bodies, query parameters, cookies, headers, path parameters, and response headers into structs or [maps](#binding-into-maps). :::info Bound string values alias the request buffers, so they are valid only within the handler: copy the data you need to keep. The [**`Immutable`**](./ctx.md) setting does not apply to bound values. [Read more...](../#zero-allocation) ::: ### Binders - [All](#all) - [Body](#body) - [CBOR](#cbor) - [Form](#form) - [JSON](#json) - [MsgPack](#msgpack) - [XML](#xml) - [Cookie](#cookie) - [Header](#header) - [Query](#query) - [RespHeader](#respheader) - [URI](#uri) #### All The `All` function binds data from URL parameters, the request body, query parameters, headers, and cookies into `out`. Sources are applied in the following order using struct field tags. ##### Precedence Order The binding sources have the following precedence: 1. **URL Parameters (URI)** 2. **Request Body (e.g., JSON or form data)** 3. **Query Parameters** 4. **Request Headers** 5. **Cookies** :::info The request body is only included as a binding source when the request has both a non-empty body **and** a non-empty `Content-Type` header. ::: Each source fills only the top-level fields of `out` that are still at their zero value, so the first source in the order that provides a non-zero value for a field wins. A non-zero value you set on `out` before the call is never overwritten. A zero one counts as unset, so a source can fill it, and a zero value from an earlier source (`0`, `""`, `false`) does not stop a later source from filling the field. A nested struct, slice or map that an earlier source set, even partly, is kept as a whole, so a later source cannot fill its remaining zero fields. A configured [`StructValidator`](#validation) runs once, on the merged struct. `out` must be a non-nil pointer to a struct. Anything else, such as a map, returns `fiber.ErrUnprocessableEntity`. If a source fails to bind, `All` stops and returns that error without trying the remaining sources. ```go title="Signature" func (b *Bind) All(out any) error ``` ```go title="Example" type User struct { Name string `query:"name" json:"name" form:"name"` Email string `json:"email" form:"email"` Role string `header:"X-User-Role"` SessionID string `json:"session_id" cookie:"session_id"` ID int `uri:"id" query:"id" json:"id" form:"id"` } app.Post("/users", func(c fiber.Ctx) error { user := new(User) if err := c.Bind().All(user); err != nil { return err } // All available data is now bound to the user struct return c.JSON(user) }) ``` ##### Custom Precedence By default, the `All` method binds data in the following precedence order: `URI params -> Body -> Query -> Headers -> Cookies`. If you need to override this behavior, you can define a custom precedence order for your struct using the `binding_source` tag. ```go title="Example" type CustomPrecedenceReq struct { // Specify the priority for binding using the binding_source tag. // In this example, query parameters take highest priority, followed by headers. // The tag can be placed on any top-level field in the struct. Name string `binding_source:"query,header,cookie,body,uri" query:"name" header:"x-name" json:"name"` } app.Post("/users", func(c fiber.Ctx) error { req := new(CustomPrecedenceReq) if err := c.Bind().All(req); err != nil { return err } // req.Name will take the value from the query parameter if it exists, otherwise header, etc. return c.JSON(req) }) ``` :::info For maximum performance, Fiber caches the precedence resolution per `reflect.Type` to reduce repeated parsing overhead. Additionally, the `binding_source` tag must be placed on a **top-level** field of the struct (tags inside embedded structs are ignored), and a struct may only contain **one** `binding_source` tag. - **Source Names**: The valid names are `uri`, `body`, `query`, `header`, and `cookie`. They are case-sensitive, surrounding spaces are ignored, and a name listed twice counts once, at its first position. - **Partial Lists**: If you omit sources from the tag (e.g., `binding_source:"query"`), the omitted sources (body, header, etc.) will **not** be bound at all. - **Unrecognized Sources**: If an invalid source name is provided in the tag (e.g., `binding_source:"invalid"`), or the struct carries more than one `binding_source` tag, `All` returns a plain error. It is not a `*BindError`: the mistake is in your struct, not in the request, so the default error handler answers with 500, even when `WithAutoHandling` is on. ::: #### Body Binds the request body to a struct. Use tags that match the content type. For example, to parse a JSON body with a `Pass` field, declare `json:"pass"`. | Content-Type | Struct Tag | | ----------------------------------- | ---------- | | `application/x-www-form-urlencoded` | `form` | | `multipart/form-data` | `form` | | `application/json` | `json` | | `application/xml` | `xml` | | `text/xml` | `xml` | | `application/vnd.msgpack` | `msgpack` | | `application/cbor` | `cbor` | The decoder is chosen from the `Content-Type` header. The media type is matched case-insensitively and its parameters, such as `charset`, are ignored. A structured syntax suffix selects the matching format, so `application/vnd.api+json` binds as JSON, `application/problem+xml` as XML and `application/foo+cbor` as CBOR. MsgPack matches only `application/vnd.msgpack`, not a `+msgpack` suffix. [Custom binders](#mimetypes-and-body) are consulted first. If no decoder matches, or the header is empty, `Body` returns `fiber.ErrUnprocessableEntity` (422). MsgPack and CBOR bodies also need a decoder in the app config, see [MsgPack](#msgpack) and [CBOR](#cbor). ```go title="Signature" func (b *Bind) Body(out any) error ``` ```go title="Example" 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 } log.Println(p.Name) // john log.Println(p.Pass) // doe // ... }) ``` Test the handler with these `curl` commands: ```bash # JSON curl -X POST -H "Content-Type: application/json" --data "{\"name\":\"john\",\"pass\":\"doe\"}" localhost:3000 # MsgPack curl -X POST -H "Content-Type: application/vnd.msgpack" --data-binary $'\x82\xa4name\xa4john\xa4pass\xa3doe' localhost:3000 # XML curl -X POST -H "Content-Type: application/xml" --data "johndoe" localhost:3000 # Form URL-Encoded curl -X POST -H "Content-Type: application/x-www-form-urlencoded" --data "name=john&pass=doe" localhost:3000 # Multipart Form curl -X POST -F name=john -F pass=doe http://localhost:3000 ``` #### CBOR > **Note:** Before using any CBOR-related features, make sure to follow the [CBOR setup instructions](../guide/advance-format.md#cbor). Fiber does not ship a CBOR implementation: until `CBORDecoder` is set in the app config, binding fails with `binder.ErrUnimplementedCBOR`. Binds the request CBOR body to a struct. It is important to specify the correct struct tag based on the content type to be parsed. For example, if you want to parse a CBOR body with a field called `Pass`, you would use a struct field with `cbor:"pass"`. ```go title="Signature" func (b *Bind) CBOR(out any) error ``` ```go title="Example" // Field names should start with an uppercase letter type Person struct { Name string `cbor:"name"` Pass string `cbor:"pass"` } app.Post("/", func(c fiber.Ctx) error { p := new(Person) if err := c.Bind().CBOR(p); err != nil { return err } log.Println(p.Name) // john log.Println(p.Pass) // doe // ... }) ``` Test it with this `curl` command, once `CBORDecoder` is set: ```bash curl -X POST -H "Content-Type: application/cbor" --data-binary $'\xa2dnamedjohndpasscdoe' localhost:3000 ``` #### Form Binds the request or multipart form body data to a struct. It is important to specify the correct struct tag based on the content type to be parsed. For example, if you want to parse a form body with a field called `Pass`, you would use a struct field with `form:"pass"`. ```go title="Signature" func (b *Bind) Form(out any) error ``` ```go title="Example" type Person struct { Name string `form:"name"` Pass string `form:"pass"` } app.Post("/", func(c fiber.Ctx) error { p := new(Person) if err := c.Bind().Form(p); err != nil { return err } log.Println(p.Name) // john log.Println(p.Pass) // doe // ... }) ``` Run tests with the following `curl` commands for both `application/x-www-form-urlencoded` and `multipart/form-data`: ```bash curl -X POST -H "Content-Type: application/x-www-form-urlencoded" --data "name=john&pass=doe" localhost:3000 ``` ```bash curl -X POST -H "Content-Type: multipart/form-data" -F "name=john" -F "pass=doe" localhost:3000 ``` :::info If you need to bind multipart file, you can use `*multipart.FileHeader`, `*[]*multipart.FileHeader` or `[]*multipart.FileHeader` as a field type. ::: ```go title="Example" type Person struct { Name string `form:"name"` Pass string `form:"pass"` Avatar *multipart.FileHeader `form:"avatar"` } app.Post("/", func(c fiber.Ctx) error { p := new(Person) if err := c.Bind().Form(p); err != nil { return err } log.Println(p.Name) // john log.Println(p.Pass) // doe log.Println(p.Avatar.Filename) // file.txt // ... }) ``` Run tests with the following `curl` command: ```bash curl -X POST -H "Content-Type: multipart/form-data" -F "name=john" -F "pass=doe" -F 'avatar=@filename' localhost:3000 ``` :::info `Form` reads only the request body, never the query string. A `multipart/form-data` body is read with the app's [`BodyLimit`](fiber.md#bodylimit) as its size limit, which also bounds the size of a `Content-Encoding: gzip` body after decompression. A larger body fails with an error that wraps `fasthttp.ErrBodyTooLarge`. On a form request, `Form` and `Body` also lowercase the case-insensitive parts of the request's own `Content-Type` (the media type and the parameter names, never the `boundary` value), so copy a value you read earlier from `Get(HeaderContentType)` if you need it to outlive the call, see [FormValue](./ctx.md#formvalue). Other body types leave the header untouched. ::: ##### Nested and Array Form Fields A form body is a flat list of key/value pairs, so structure lives in the field name. The notations below work for both `application/x-www-form-urlencoded` and `multipart/form-data`, and identically for [query parameters](#query). | Notation | Example | Binds to | | ------------------ | ---------------------------- | ------------------------------------ | | Repeated key | `colors=red&colors=blue` | slice of scalars | | Empty brackets | `colors[]=red&colors[]=blue` | slice of scalars | | Bracket field | `address[city]=Berlin` | field of a nested struct | | Dot field | `address.city=Berlin` | field of a nested struct | | Indexed brackets | `items[0][sku]=A1` | field of a struct slice element | | Dot index | `items.0.sku=A1` | field of a struct slice element | | Mixed index/field | `items[0].sku=A1` | field of a struct slice element | Bracket and dot notation are interchangeable and can be nested to any depth: ```go title="Struct" type Address struct { City string `form:"city"` Zip string `form:"zip"` } type Item struct { SKU string `form:"sku"` Qty int `form:"qty"` } type Order struct { Colors []string `form:"colors"` Address Address `form:"address"` Items []Item `form:"items"` } ``` ```bash title="curl" curl -X POST http://localhost:3000/orders \ --data "colors[]=red&colors[]=blue" \ --data "address[city]=Berlin&address.zip=10115" \ --data "items[0][sku]=A1&items[0].qty=2&items.1.sku=B2&items.1.qty=5" ``` ```go title="Result" Order{ Colors: []string{"red", "blue"}, Address: Address{City: "Berlin", Zip: "10115"}, Items: []Item{{SKU: "A1", Qty: 2}, {SKU: "B2", Qty: 5}}, } ``` :::caution An index only ever addresses a struct field. `colors[0]=red` and `colors.0=red` do **not** fill a `[]string`. With the default decoder the value is dropped and no error is returned; after `SetParserDecoder(ParserConfig{IgnoreUnknownKeys: false})` the same input fails with `schema: invalid path "colors.0"`. Use a repeated key or `colors[]` for slices of scalars. Indexes above 1000 are rejected: `items.1001.sku=A1` fails with `schema: index exceeds parser limit`. ::: :::note The struct field tag never contains the brackets or the index. `colors[]`, `colors[0]` and `colors.0` all resolve against `form:"colors"`. ::: #### JSON Binds the request JSON body to a struct. It is important to specify the correct struct tag based on the content type to be parsed. For example, if you want to parse a JSON body with a field called `Pass`, you would use a struct field with `json:"pass"`. ```go title="Signature" func (b *Bind) JSON(out any) error ``` ```go title="Example" type Person struct { Name string `json:"name"` Pass string `json:"pass"` } app.Post("/", func(c fiber.Ctx) error { p := new(Person) if err := c.Bind().JSON(p); err != nil { return err } log.Println(p.Name) // john log.Println(p.Pass) // doe // ... }) ``` Run tests with the following `curl` command: ```bash curl -X POST -H "Content-Type: application/json" --data "{\"name\":\"john\",\"pass\":\"doe\"}" localhost:3000 ``` #### MsgPack > **Note:** Before using any MsgPack-related features, make sure to follow the [MsgPack setup instructions](../guide/advance-format.md#msgpack). Binds the request MsgPack body to a struct. It is important to specify the correct struct tag based on the content type to be parsed. For example, if you want to parse a Msgpack body with a field called `Pass`, you would use a struct field with `msgpack:"pass"`. > Fiber does not bundle a MsgPack implementation. Set `MsgPackDecoder` in the app config to the `Unmarshal` function of the library you picked; until then, binding fails with `binder.ErrMsgPackNotConfigured`. Libraries such as [shamaton-msgpack](https://github.com/shamaton/msgpack) read `msgpack` struct tags. If you use another library, you may need to update the struct tags accordingly. ```go title="Signature" func (b *Bind) MsgPack(out any) error ``` ```go title="Example" type Person struct { Name string `msgpack:"name"` Pass string `msgpack:"pass"` } app.Post("/", func(c fiber.Ctx) error { p := new(Person) if err := c.Bind().MsgPack(p); err != nil { return err } log.Println(p.Name) // john log.Println(p.Pass) // doe // ... }) ``` Run tests with the following `curl` command, once `MsgPackDecoder` is set: ```bash curl -X POST -H "Content-Type: application/vnd.msgpack" --data-binary $'\x82\xa4name\xa4john\xa4pass\xa3doe' localhost:3000 ``` #### XML Binds the request XML body to a struct. It is important to specify the correct struct tag based on the content type to be parsed. For example, if you want to parse an XML body with a field called `Pass`, you would use a struct field with `xml:"pass"`. ```go title="Signature" func (b *Bind) XML(out any) error ``` ```go title="Example" // Field names should start with an uppercase letter type Person struct { Name string `xml:"name"` Pass string `xml:"pass"` } app.Post("/", func(c fiber.Ctx) error { p := new(Person) if err := c.Bind().XML(p); err != nil { return err } log.Println(p.Name) // john log.Println(p.Pass) // doe // ... }) ``` Run tests with the following `curl` command: ```bash curl -X POST -H "Content-Type: application/xml" --data "johndoe" localhost:3000 ``` #### Cookie This method is similar to [Body Binding](#body), but for cookie parameters. It is important to use the struct tag `cookie`. For example, if you want to parse a cookie with a field called `Age`, you would use a struct field with `cookie:"age"`. ```go title="Signature" func (b *Bind) Cookie(out any) error ``` ```go title="Example" type Person struct { Name string `cookie:"name"` Age int `cookie:"age"` Job bool `cookie:"job"` } app.Get("/", func(c fiber.Ctx) error { p := new(Person) if err := c.Bind().Cookie(p); err != nil { return err } log.Println(p.Name) // Joseph log.Println(p.Age) // 23 log.Println(p.Job) // true }) ``` Run tests with the following `curl` command: ```bash curl --cookie "name=Joseph; age=23; job=true" http://localhost:8000/ ``` #### Header This method is similar to [Body Binding](#body), but for request headers. It is important to use the struct tag `header`. For example, if you want to parse a request header with a field called `Pass`, you would use a struct field with `header:"pass"`. ```go title="Signature" func (b *Bind) Header(out any) error ``` ```go title="Example" type Person struct { Name string `header:"name"` Pass string `header:"pass"` Products []string `header:"products"` } app.Get("/", func(c fiber.Ctx) error { p := new(Person) if err := c.Bind().Header(p); err != nil { return err } log.Println(p.Name) // john log.Println(p.Pass) // doe // Depending on fiber.Config{EnableSplittingOnParsers: false} - default log.Println(p.Products) // ["shoe,hat"] // With fiber.Config{EnableSplittingOnParsers: true} // log.Println(p.Products) // ["shoe", "hat"] // ... }) ``` Run tests with the following `curl` command: ```bash curl "http://localhost:3000/" -H "name: john" -H "pass: doe" -H "products: shoe,hat" ``` Header names are matched case-insensitively. A header sent on several lines, such as `-H "products: shoe" -H "products: hat"`, fills a slice with one element per line, with or without `EnableSplittingOnParsers`. #### Query This method is similar to [Body Binding](#body), but for query parameters. It is important to use the struct tag `query`. For example, if you want to parse a query parameter with a field called `Pass`, you would use a struct field with `query:"pass"`. ```go title="Signature" func (b *Bind) Query(out any) error ``` ```go title="Example" type Person struct { Name string `query:"name"` Pass string `query:"pass"` Products []string `query:"products"` } app.Get("/", func(c fiber.Ctx) error { p := new(Person) if err := c.Bind().Query(p); err != nil { return err } log.Println(p.Name) // john log.Println(p.Pass) // doe // Depending on fiber.Config{EnableSplittingOnParsers: false} - default log.Println(p.Products) // ["shoe,hat"] // With fiber.Config{EnableSplittingOnParsers: true} // log.Println(p.Products) // ["shoe", "hat"] // ... }) ``` Run tests with the following `curl` command: ```bash curl "http://localhost:3000/?name=john&pass=doe&products=shoe,hat" ``` :::info `EnableSplittingOnParsers` applies to the Cookie, Form, Header, Query, and RespHeader binders. For more parser settings, please refer to [Config](fiber.md#enablesplittingonparsers) ::: ##### Array Query Parameters Fiber supports several formats for passing array values via query parameters. The following table gives an overview: | Format | Example | Requires `EnableSplittingOnParsers` | | ------------------------ | ---------------------------------------------- | ----------------------------------- | | Repeated key | `?colors=red&colors=blue` | No | | Bracket notation | `?colors[]=red&colors[]=blue` | No | | Comma-separated | `?colors=red,blue` | **Yes** | | Indexed bracket notation | `?posts[0][title]=Hello&posts[1][title]=World` | No | | Nested bracket notation | `?preferences[tags]=golang,api` | No (comma splitting: **Yes**) | | Dot notation | `?user.name=Alice&posts.0.title=Hello` | No | ###### Repeated Key The most common approach. Repeat the same query key for each value: ```text GET /api?colors=red&colors=blue&colors=green ``` ```go title="Struct" type Filter struct { Colors []string `query:"colors"` } // Result: Colors = ["red", "blue", "green"] ``` ```bash title="curl" curl "http://localhost:3000/api?colors=red&colors=blue&colors=green" ``` A repeated key bound into a field that is not a slice keeps its **last** value, so `?name=a&name=b` gives `b`. ###### Bracket Notation Append `[]` to the key name. This is common in PHP-style and JavaScript frameworks: ```text GET /api?colors[]=red&colors[]=blue&colors[]=green ``` ```go title="Struct" type Filter struct { Colors []string `query:"colors"` } // Result: Colors = ["red", "blue", "green"] ``` ```bash title="curl" curl "http://localhost:3000/api?colors[]=red&colors[]=blue&colors[]=green" ``` :::note The struct field tag stays `query:"colors"` (without brackets). Fiber strips the `[]` automatically. ::: ###### Comma-Separated Values Pass multiple values in a single parameter, separated by commas. This format requires [`EnableSplittingOnParsers`](fiber.md#enablesplittingonparsers) to be set to `true`. ```text GET /api?colors=red,blue,green ``` ```go title="Struct" type Filter struct { Colors []string `query:"colors"` } ``` ```go title="App Setup" // EnableSplittingOnParsers is required for comma splitting app := fiber.New(fiber.Config{ EnableSplittingOnParsers: true, }) // Result: Colors = ["red", "blue", "green"] ``` Without `EnableSplittingOnParsers`, the entire string `"red,blue,green"` is treated as a **single** element. With it, only slice fields are split: a `string` field keeps its commas. ```go title="Default behavior (EnableSplittingOnParsers: false)" // GET /api?colors=red,blue,green // Result: Colors = ["red,blue,green"] ← single element ``` ```bash title="curl" curl "http://localhost:3000/api?colors=red,blue,green" ``` You can also mix comma-separated values with repeated keys when splitting is enabled: ```text GET /api?hobby=soccer&hobby=basketball,football ``` ```go type Query struct { Hobby []string `query:"hobby"` } // With EnableSplittingOnParsers: true // Result: Hobby = ["soccer", "basketball", "football"] ← 3 elements ``` ###### Indexed Bracket Notation (Nested Structs) Use indexed brackets to bind arrays of nested structs: ```text GET /api?posts[0][title]=Hello&posts[0][author]=Alice&posts[1][title]=World&posts[1][author]=Bob ``` ```go title="Struct" type Post struct { Title string `query:"title"` Author string `query:"author"` } type Request struct { Posts []Post `query:"posts"` } // Result: Posts = [{Title: "Hello", Author: "Alice"}, {Title: "World", Author: "Bob"}] ``` ```bash title="curl" curl "http://localhost:3000/api?posts[0][title]=Hello&posts[0][author]=Alice&posts[1][title]=World&posts[1][author]=Bob" ``` ###### Nested Bracket Notation (Without Index) Use bracket notation to access fields of a nested struct: ```text GET /api?preferences[tags]=golang,api ``` ```go title="Struct" type Preferences struct { Tags *[]string `query:"tags"` } type Profile struct { Prefs *Preferences `query:"preferences"` } // With EnableSplittingOnParsers: true // Result: *Prefs.Tags = ["golang", "api"] ``` ```bash title="curl" curl "http://localhost:3000/api?preferences[tags]=golang,api" ``` :::note Pointer fields (`*[]string`, `*Preferences`) let you distinguish between a missing parameter (`nil`) and an empty one. When the parameter is present, Fiber allocates the pointer automatically. ::: ###### Dot Notation Nested fields and indexes have a dot equivalent, and the two can be mixed within one request. Empty brackets are the exception, `colors[]` has no dot form: ```text GET /api?user.name=Alice&posts.0.title=Hello&posts[1].title=World ``` ```go title="Struct" type Post struct { Title string `query:"title"` } type User struct { Name string `query:"name"` } type Request struct { User User `query:"user"` Posts []Post `query:"posts"` } // Result: User = {Name: "Alice"}, Posts = [{Title: "Hello"}, {Title: "World"}] ``` Query and form binding share the same decoder, so the full notation reference in [Nested and Array Form Fields](#nested-and-array-form-fields) applies here too, including the caveat that an index never fills a slice of scalars. #### RespHeader This method is similar to [Body Binding](#body), but for response headers. It is important to use the struct tag `respHeader`. For example, if you want to parse a response header with a field called `Pass`, you would use a struct field with `respHeader:"pass"`. It reads the headers of the response being built, such as those set by earlier handlers or middleware, not the request headers. ```go title="Signature" func (b *Bind) RespHeader(out any) error ``` ```go title="Example" type Person struct { Name string `respHeader:"name"` Pass string `respHeader:"pass"` Products []string `respHeader:"products"` } app.Get("/", func(c fiber.Ctx) error { // Response headers, set here for the example c.Set("name", "john") c.Set("pass", "doe") c.Set("products", "shoe,hat") p := new(Person) if err := c.Bind().RespHeader(p); err != nil { return err } log.Println(p.Name) // john log.Println(p.Pass) // doe // Depending on fiber.Config{EnableSplittingOnParsers: false} - default log.Println(p.Products) // ["shoe,hat"] // With fiber.Config{EnableSplittingOnParsers: true} // log.Println(p.Products) // ["shoe", "hat"] // ... return nil }) ``` #### URI This method is similar to [Body Binding](#body), but for path parameters. It is important to use the struct tag `uri`. For example, if you want to parse a path parameter with a field called `Pass`, you would use a struct field with `uri:"pass"`. ```go title="Signature" func (b *Bind) URI(out any) error ``` ```go title="Example" // GET http://example.com/user/111 app.Get("/user/:id", func(c fiber.Ctx) error { param := struct { ID uint `uri:"id"` }{} if err := c.Bind().URI(¶m); err != nil { return err } // ... return c.SendString(fmt.Sprintf("User ID: %d", param.ID)) }) ``` :::note `URI` binds the values as `c.Params` returns them. With [`UnescapePath`](fiber.md#unescapepath) off (the default) they are still percent-encoded, so `/user/a%20b` binds `a%20b`. ::: ### Binding into Maps The Cookie, Form, Header, Query, RespHeader, and URI binders can fill a `map[string]string` or a `map[string][]string` instead of a struct. Pass the map or a pointer to it. The keys are the names found in the request, so no struct tags are needed. `Body` does the same for form content types; JSON, XML, CBOR, and MsgPack bodies are decoded by their own library. ```go title="Example" // GET /?color=red&color=blue&size=m app.Get("/", func(c fiber.Ctx) error { all := make(map[string][]string) if err := c.Bind().Query(&all); err != nil { return err } log.Println(all["color"]) // [red blue] last := make(map[string]string) if err := c.Bind().Query(&last); err != nil { return err } log.Println(last["color"]) // blue return nil }) ``` - `map[string]string` keeps only the **last** value of a repeated key and never splits values on commas. `map[string][]string` keeps every value and, with [`EnableSplittingOnParsers`](fiber.md#enablesplittingonparsers), splits comma-separated ones. - Bound keys are added to the map and replace entries of the same name. Other entries stay in place. - A nil map passed by value fails with `binder.ErrMapNilDestination`. Pass a pointer, such as `&m`, and Fiber allocates the map. A nil pointer to a map fails as well. - Only `map[string]string` and `map[string][]string` themselves are filled. A map with string or slice elements of any other type, such as `map[string][]int` or a named type like `type Headers map[string]string`, fails with `binder.ErrMapNotConvertible`. Maps with other element kinds, for example `map[string]int` or `map[string]any`, are left untouched and no error is returned. - `Query` and `Form` rewrite bracket notation to dots, so the key `filter[name]` is stored as `filter.name` and `ids[]` as `ids`. - File fields of a multipart form are not added to a map. - [`All`](#all) needs a pointer to a struct, and the [struct validator](#validation) is skipped for maps. ### BindError When a bind method fails to parse (e.g. invalid JSON, bad type conversion), the behavior depends on the error-handling mode. In **manual handling** (the default), the binder returns a `*BindError` wrapping the underlying error — use `errors.As` to extract it and branch on the binding source or field. An error that is, or wraps, a `*fiber.Error`, such as one returned by a custom binder's `Parse` or by a custom `JSONDecoder`, is returned as that `*fiber.Error` instead. In **automatic handling** (enabled via `WithAutoHandling`), parse failures are instead converted to a `*fiber.Error` with HTTP status 400; `*BindError` is never surfaced to the caller in that mode. If you are using `WithAutoHandling`, check for `*fiber.Error` or an HTTP 400 response rather than using `errors.As` for `*BindError`. The default error handler answers a `*BindError` with `500 Internal Server Error`, so return a client error yourself, as in the example below, when the request is at fault. ```go type BindError struct { Err error // underlying error; use errors.As to inspect Source string // "uri", "query", "body", "header", "cookie", or "respHeader"; the binder's name for Custom Field string // struct field or tag key that failed (best-effort, may be empty) } ``` Source constants: `BindSourceURI`, `BindSourceQuery`, `BindSourceHeader`, `BindSourceCookie`, `BindSourceBody`, `BindSourceRespHeader`. #### Branching on source Use `errors.As` to extract `*BindError` and branch on `Source` for RFC-correct status codes (e.g. 404 for URI failures vs 400 for body/query): ```go title="Example" // With manual handling mode (default behavior) // Will not work with WithAutoHandling() var req struct { ID int `uri:"id"` Name string `json:"name"` } if err := c.Bind().All(&req); err != nil { var be *fiber.BindError if errors.As(err, &be) && be.Source == fiber.BindSourceURI { return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "not found"}) } return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid request"}) } ``` #### Inspecting the underlying error `BindError` unwraps to the error of the decoder, so `errors.As` and `errors.Is` see through it. Fiber re-exports the types you are most likely to look for: - The Cookie, Form, Header, Query, RespHeader, and URI binders (and `Body` for form content) fail with a `fiber.MultiError`, a `map[string]error` keyed by the request key that failed. Its entries are a `fiber.ConversionError` when a value cannot be converted to the field type, a `fiber.EmptyFieldError` when a [`required`](#required-fields) key is missing or empty, and a `fiber.UnknownKeyError` for a key that names no field (only reported when `IgnoreUnknownKeys` is `false`, see [SetParserDecoder](#setparserdecoder)). Not every failure is a `MultiError`: a key with unmatched square brackets, such as `?a[=1`, is refused earlier with a plain `binder.ErrUnmatchedBrackets`, so keep a fallback for other errors. - With the default `JSONDecoder`, a JSON body fails with the errors of `encoding/json`: `*fiber.UnmarshalTypeError` for a value of the wrong JSON type, `*fiber.SyntaxError` for malformed JSON, and `*fiber.InvalidUnmarshalError` for a destination that is not a non-nil pointer. `Field` is taken from a `ConversionError`, `UnknownKeyError`, `EmptyFieldError`, `MultiError` (one of its keys, so any of them when several fields failed), or `UnmarshalTypeError`. For any other error it stays empty. ```go title="Example" var req struct { Age int `query:"age,required"` } if err := c.Bind().Query(&req); err != nil { var fields fiber.MultiError if errors.As(err, &fields) { problems := make(map[string]string, len(fields)) for key, fieldErr := range fields { var empty fiber.EmptyFieldError if errors.As(fieldErr, &empty) { problems[key] = "required" } else { problems[key] = "invalid" } } return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"errors": problems}) } return err } ``` #### Validation vs binding errors Validation errors (from `StructValidator`) are **not** wrapped in `BindError`. Use `errors.As(err, &be)` to distinguish: it succeeds only for parsing/binding failures, not for validation failures. ### Custom To use custom binders, you have to use this method. You can register them using the [RegisterCustomBinder](./app.md#registercustombinder) method of the Fiber instance. ```go title="Signature" func (b *Bind) Custom(name string, dest any) error ``` ```go title="Example" app := fiber.New() // My custom binder type customBinder struct{} func (cb *customBinder) Name() string { return "custom" } func (cb *customBinder) MIMETypes() []string { return []string{"application/yaml"} } func (cb *customBinder) Parse(c fiber.Ctx, out any) error { // parse YAML body return yaml.Unmarshal(c.Body(), out) } // Register custom binder app.RegisterCustomBinder(&customBinder{}) type User struct { Name string `yaml:"name"` } // curl -X POST http://localhost:3000/custom -H "Content-Type: application/yaml" -d "name: John" app.Post("/custom", func(c fiber.Ctx) error { var user User // Use Custom binder by name if err := c.Bind().Custom("custom", &user); err != nil { return err } return c.JSON(user) }) ``` `Custom` returns `fiber.ErrCustomBinderNotFound` when no binder is registered under `name`. With manual handling, an error returned by `Parse` comes back as a `*BindError` whose `Source` is the binder's name (when the binder is reached through `Body`, the `Source` is `BindSourceBody`). An error that is, or wraps, a `*fiber.Error` is the exception: that `*fiber.Error` comes back as it is, without a `*BindError` around it, and the error that wrapped it is dropped. Return `fiber.NewError(...)` from `Parse` to choose the status of the response. With `WithAutoHandling`, every error from `Parse`, a `*fiber.Error` included, comes back as a `*fiber.Error` with status `400` and the message `Bad request: ...`. The [struct validator](#validation) runs on `dest` after a successful `Parse`. #### MIMETypes and Body [`Body`](#body) consults the registered custom binders before its own content-type switch, so `MIMETypes` decides how a binder can be reached: | `MIMETypes()` returns | `Bind().Custom(name, dest)` | `Bind().Body(dest)` | | ---------------------------------- | --------------------------- | --------------------------------- | | a content type no built-in handles | yes | yes, for that content type | | a content type a built-in handles | yes | yes, the built-in no longer runs | | `nil` | yes | no | Returning `nil` makes a binder opt-in: it is reachable only by name, and the built-in decoders keep handling `Body`. `Body` compares each entry of `MIMETypes()` with the media type normalized as described under [Body](#body): lower case, without parameters, and with a structured syntax suffix resolved. List the entries in that form. `Application/YAML` and `application/toml; charset=utf-8` never match, and neither does `application/vnd.api+json`, which a request reaches as `application/json`. #### Overriding a built-in format Claiming a content type that already has a decoder takes precedence over the built-in one for every `Bind().Body()` call in the application, not only on the route you had in mind. `Body` uses the first registered binder whose `MIMETypes()` contains the request's content type, so a second binder claiming `fiber.MIMEApplicationJSON` never runs. To decode strictly on selected routes instead, return `nil` and call the binder by name: ```go title="Example" type strictJSON struct{} func (strictJSON) Name() string { return "strict" } func (strictJSON) MIMETypes() []string { return nil } func (strictJSON) Parse(c fiber.Ctx, out any) error { d := json.NewDecoder(bytes.NewReader(c.Body())) d.DisallowUnknownFields() return d.Decode(out) } app.RegisterCustomBinder(strictJSON{}) // curl -X POST http://localhost:3000/strict -H "Content-Type: application/json" -d '{"name":"John","admin":true}' app.Post("/strict", func(c fiber.Ctx) error { var user User // Rejects the unknown "admin" field; c.Bind().JSON(&user) still accepts it if err := c.Bind().Custom("strict", &user); err != nil { return err } return c.JSON(user) }) ``` ### Options For more control over error handling, you can use the following methods. :::note `c.Bind()` returns the same `Bind` for the whole request, so a setting made with one of these methods stays in effect for every later bind of that request, in a handler or in middleware, until you change it back. ::: #### WithAutoHandling If you want to handle binder errors automatically, you can use `WithAutoHandling`. If binding fails, it will return a `*fiber.Error` with the message `Bad request: ...` and set HTTP status to `400 Bad Request`. This function does NOT panic therefore you must still return on error explicitly ```go title="Signature" func (b *Bind) WithAutoHandling() *Bind ``` Auto handling covers failures to bind the request data. A validation error is converted to `400 Bad Request` only by [`All`](#all); the other bind methods return the validator's error as is, which the default error handler answers with 500. Errors that point at a mistake in your code, such as `ErrCustomBinderNotFound` or an invalid `binding_source` tag, are returned unchanged as well. #### WithoutAutoHandling To handle binder errors manually, you can use the `WithoutAutoHandling` method. It's the default behavior of the binder. ```go title="Signature" func (b *Bind) WithoutAutoHandling() *Bind ``` #### SkipValidation To enable or disable validation, use `SkipValidation`. By default, validation is enabled (`skip = false`). The setting is not limited to one call: it applies to every later bind of the request until you call `SkipValidation` again. ```go title="Signature" func (b *Bind) SkipValidation(skip bool) *Bind ``` ### SetParserDecoder Allows you to configure the decoder behind the Cookie, Form, Header, Query, RespHeader, and URI binders (and `Body` for form content) based on schema options, providing the possibility to add custom types for parsing. JSON, XML, CBOR, and MsgPack bodies are not affected. The function lives in the `github.com/gofiber/fiber/v3/binder` package. ```go title="Signature" func SetParserDecoder(parserConfig binder.ParserConfig) ``` The call is global and replaces the whole configuration. Fields you leave out are not kept at Fiber's defaults, they fall back to their zero values. Fiber starts with `IgnoreUnknownKeys: true` and `ZeroEmpty: true`, so set both explicitly when you only want to add a converter. `binder.ParserConfig` has the following fields: ```go type ParserConfig struct { IgnoreUnknownKeys bool ParserType []ParserType ZeroEmpty bool SetAliasTag string // ignored: every binder uses its own tag (query, form, header, ...) } type ParserType struct { CustomType any Converter func(string) reflect.Value } ``` - `IgnoreUnknownKeys`: when `false`, a request key that names no field fails the bind with `schema: invalid path ""`. Fiber's default is `true`. - `ParserType`: registers a `Converter` for the type of `CustomType`. The converter returns `reflect.Value{}` to reject a value, which fails the bind with a `fiber.ConversionError`. - `ZeroEmpty`: when `true`, an empty value sets the field to its zero value, which for a slice field is one zero element and for a pointer field a non-nil pointer to the zero value. When `false`, an empty value leaves a plain field unchanged and a slice field empty, while a pointer field still gets a non-nil pointer to the zero value. Fiber's default is `true`. ```go title="Example" type CustomTime time.Time // String returns the time in string format func (ct *CustomTime) String() string { t := time.Time(*ct).String() return t } // Converter for CustomTime type with format "2006-01-02" var timeConverter = func(value string) reflect.Value { fmt.Println("timeConverter:", value) if v, err := time.Parse("2006-01-02", value); err == nil { return reflect.ValueOf(CustomTime(v)) } return reflect.Value{} } customTime := binder.ParserType{ CustomType: CustomTime{}, Converter: timeConverter, } // Add custom type to the Decoder settings binder.SetParserDecoder(binder.ParserConfig{ IgnoreUnknownKeys: true, ParserType: []binder.ParserType{customTime}, ZeroEmpty: true, }) // Example using CustomTime with non-RFC3339 format type Demo struct { Date CustomTime `form:"date" query:"date"` Title string `form:"title" query:"title"` Body string `form:"body" query:"body"` } app.Post("/body", func(c fiber.Ctx) error { var d Demo if err := c.Bind().Body(&d); err != nil { return err } fmt.Println("d.Date:", d.Date.String()) return c.JSON(d) }) app.Get("/query", func(c fiber.Ctx) error { var d Demo if err := c.Bind().Query(&d); err != nil { return err } fmt.Println("d.Date:", d.Date.String()) return c.JSON(d) }) ``` Run tests with the following `curl` commands: ```bash # Body Binding curl -X POST -F title=title -F body=body -F date=2021-10-20 http://localhost:3000/body # Query Binding curl -X GET "http://localhost:3000/query?title=title&body=body&date=2021-10-20" ``` ### Validation Validation is also possible with the binding methods. You can specify your validation rules using the `validate` struct tag. Specify your struct validator in the [config](./fiber.md#structvalidator). The validator must implement the `StructValidator` interface, which requires a `Validate` method that takes an `any` type and returns an error. ```go title="Interface" type StructValidator interface { Validate(out any) error } ``` #### Setup Your Validator in the Config ```go title="Example" import "github.com/go-playground/validator/v10" type structValidator struct { validate *validator.Validate } // Validate method implementation func (v *structValidator) Validate(out any) error { return v.validate.Struct(out) } // Setup your validator in the Fiber config app := fiber.New(fiber.Config{ StructValidator: &structValidator{validate: validator.New()}, }) ``` Fiber only runs `StructValidator` for struct destinations (or pointers to structs). Binding into maps and other non-struct types skips the validator step. Validation runs after a bind succeeds, on the value you passed in. If binding fails, the validator is not called. [`All`](#all) validates once, on the merged struct. The validator's error is returned as is: it is not wrapped in a [`BindError`](#validation-vs-binding-errors), and the default error handler answers it with `500 Internal Server Error` (only [`All`](#all) under [`WithAutoHandling`](#withautohandling) converts it to a `400 Bad Request`), so return a client error yourself when the request is at fault, as shown in the [Validation guide](../guide/validation.md). To bypass the validator, use [`SkipValidation`](#skipvalidation). #### Usage of Validation in Binding Methods ```go title="Example" 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 { // Receives validation errors return err } return c.JSON(p) }) ``` ### Default Fields You can set default values for fields in the struct with the `default:` option of the field's tag, as in `query:"name,default:john"`. A separate `default` struct tag is not read. Supported types: - `bool` - Float variants (`float32`, `float64`) - Int variants (`int`, `int8`, `int16`, `int32`, `int64`) - Uint variants (`uint`, `uint8`, `uint16`, `uint32`, `uint64`) - `string` - A slice of the above types. Use `|` to separate slice items. - A pointer to one of the above types (**pointers to slices and slices of pointers are not supported**). Defaults are applied by the decoder behind the `query`, `form`, `header`, `cookie`, `respHeader`, and `uri` tags. They have no effect on JSON, XML, CBOR, or MsgPack bodies. A default applies when the key is absent from the source. A key that is present but empty, such as `?name=`, does not get it: with Fiber's default `ZeroEmpty` a plain field gets its zero value, a slice field gets one zero element (`?ids=` gives `[0]` for an `[]int`) and a pointer field gets a non-nil pointer to the zero value. The `uri` tag is the exception: the binder supplies every parameter of the matched route, an optional one the request omitted as an empty string, so a default never applies to a route parameter. It applies only to a `uri` name that the route does not declare. ```go title="Example" type Person struct { Name string `query:"name,default:john"` Pass string `query:"pass"` Products []string `query:"products,default:shoe|hat"` } app.Get("/", func(c fiber.Ctx) error { p := new(Person) if err := c.Bind().Query(p); err != nil { return err } log.Println(p.Name) // john log.Println(p.Pass) // doe log.Println(p.Products) // ["shoe", "hat"] // ... }) ``` Run tests with the following `curl` command: ```bash curl "http://localhost:3000/?pass=doe" ``` ### Required Fields Add the `required` option to a tag to make the bind fail when the key is missing or empty. It works with the `query`, `form`, `header`, `cookie`, `respHeader`, and `uri` tags. On a `uri` tag, a route parameter that the request omitted, such as an optional `:id?`, counts as empty. A field cannot have both `required` and `default`. ```go title="Example" type Search struct { Term string `query:"term,required"` } ``` A missing `term` fails with a [`*BindError`](#binderror) that wraps a `fiber.MultiError` holding a `fiber.EmptyFieldError` (message `term is empty`). With `WithAutoHandling`, the response is `400 Bad Request` with the message `Bad request: term is empty`. --- ## 📋 Constants #### HTTP methods (mirrors `net/http`) ```go const ( MethodGet = "GET" // RFC 7231, 4.3.1 MethodHead = "HEAD" // RFC 7231, 4.3.2 MethodPost = "POST" // RFC 7231, 4.3.3 MethodPut = "PUT" // RFC 7231, 4.3.4 MethodPatch = "PATCH" // RFC 5789 MethodDelete = "DELETE" // RFC 7231, 4.3.5 MethodConnect = "CONNECT" // RFC 7231, 4.3.6 MethodOptions = "OPTIONS" // RFC 7231, 4.3.7 MethodTrace = "TRACE" // RFC 7231, 4.3.8 MethodQuery = "QUERY" // RFC 10008 methodUse = "USE" ) ``` #### Common MIME types ```go const ( MIMETextXML = "text/xml" MIMETextHTML = "text/html" MIMETextPlain = "text/plain" MIMETextJavaScript = "text/javascript" MIMETextCSS = "text/css" MIMETextEventStream = "text/event-stream" MIMEApplicationXML = "application/xml" MIMEApplicationJSON = "application/json" MIMEApplicationJavaScript = "application/javascript" MIMEApplicationCBOR = "application/cbor" MIMEApplicationForm = "application/x-www-form-urlencoded" MIMEOctetStream = "application/octet-stream" MIMEMultipartForm = "multipart/form-data" MIMEApplicationMsgPack = "application/vnd.msgpack" MIMETextXMLCharsetUTF8 = "text/xml; charset=utf-8" MIMETextHTMLCharsetUTF8 = "text/html; charset=utf-8" MIMETextPlainCharsetUTF8 = "text/plain; charset=utf-8" MIMETextJavaScriptCharsetUTF8 = "text/javascript; charset=utf-8" MIMETextCSSCharsetUTF8 = "text/css; charset=utf-8" MIMEApplicationXMLCharsetUTF8 = "application/xml; charset=utf-8" MIMEApplicationJSONCharsetUTF8 = "application/json; charset=utf-8" ) ``` #### HTTP status codes (mirrors `net/http`) ```go const ( StatusContinue = 100 // RFC 9110, 15.2.1 StatusSwitchingProtocols = 101 // RFC 9110, 15.2.2 StatusProcessing = 102 // RFC 2518, 10.1 StatusEarlyHints = 103 // RFC 8297 StatusOK = 200 // RFC 9110, 15.3.1 StatusCreated = 201 // RFC 9110, 15.3.2 StatusAccepted = 202 // RFC 9110, 15.3.3 StatusNonAuthoritativeInformation = 203 // RFC 9110, 15.3.4 StatusNoContent = 204 // RFC 9110, 15.3.5 StatusResetContent = 205 // RFC 9110, 15.3.6 StatusPartialContent = 206 // RFC 9110, 15.3.7 StatusMultiStatus = 207 // RFC 4918, 11.1 StatusAlreadyReported = 208 // RFC 5842, 7.1 StatusIMUsed = 226 // RFC 3229, 10.4.1 StatusMultipleChoices = 300 // RFC 9110, 15.4.1 StatusMovedPermanently = 301 // RFC 9110, 15.4.2 StatusFound = 302 // RFC 9110, 15.4.3 StatusSeeOther = 303 // RFC 9110, 15.4.4 StatusNotModified = 304 // RFC 9110, 15.4.5 StatusUseProxy = 305 // RFC 9110, 15.4.6 StatusSwitchProxy = 306 // RFC 9110, 15.4.7 (Unused) StatusTemporaryRedirect = 307 // RFC 9110, 15.4.8 StatusPermanentRedirect = 308 // RFC 9110, 15.4.9 StatusBadRequest = 400 // RFC 9110, 15.5.1 StatusUnauthorized = 401 // RFC 9110, 15.5.2 StatusPaymentRequired = 402 // RFC 9110, 15.5.3 StatusForbidden = 403 // RFC 9110, 15.5.4 StatusNotFound = 404 // RFC 9110, 15.5.5 StatusMethodNotAllowed = 405 // RFC 9110, 15.5.6 StatusNotAcceptable = 406 // RFC 9110, 15.5.7 StatusProxyAuthRequired = 407 // RFC 9110, 15.5.8 StatusRequestTimeout = 408 // RFC 9110, 15.5.9 StatusConflict = 409 // RFC 9110, 15.5.10 StatusGone = 410 // RFC 9110, 15.5.11 StatusLengthRequired = 411 // RFC 9110, 15.5.12 StatusPreconditionFailed = 412 // RFC 9110, 15.5.13 StatusRequestEntityTooLarge = 413 // RFC 9110, 15.5.14 StatusRequestURITooLong = 414 // RFC 9110, 15.5.15 StatusUnsupportedMediaType = 415 // RFC 9110, 15.5.16 StatusRequestedRangeNotSatisfiable = 416 // RFC 9110, 15.5.17 StatusExpectationFailed = 417 // RFC 9110, 15.5.18 StatusTeapot = 418 // RFC 9110, 15.5.19 (Unused) StatusMisdirectedRequest = 421 // RFC 9110, 15.5.20 StatusUnprocessableEntity = 422 // RFC 9110, 15.5.21 StatusLocked = 423 // RFC 4918, 11.3 StatusFailedDependency = 424 // RFC 4918, 11.4 StatusTooEarly = 425 // RFC 8470, 5.2. StatusUpgradeRequired = 426 // RFC 9110, 15.5.22 StatusPreconditionRequired = 428 // RFC 6585, 3 StatusTooManyRequests = 429 // RFC 6585, 4 StatusRequestHeaderFieldsTooLarge = 431 // RFC 6585, 5 StatusUnavailableForLegalReasons = 451 // RFC 7725, 3 StatusInternalServerError = 500 // RFC 9110, 15.6.1 StatusNotImplemented = 501 // RFC 9110, 15.6.2 StatusBadGateway = 502 // RFC 9110, 15.6.3 StatusServiceUnavailable = 503 // RFC 9110, 15.6.4 StatusGatewayTimeout = 504 // RFC 9110, 15.6.5 StatusHTTPVersionNotSupported = 505 // RFC 9110, 15.6.6 StatusVariantAlsoNegotiates = 506 // RFC 2295, 8.1 StatusInsufficientStorage = 507 // RFC 4918, 11.5 StatusLoopDetected = 508 // RFC 5842, 7.2 StatusNotExtended = 510 // RFC 2774, 7 StatusNetworkAuthenticationRequired = 511 // RFC 6585, 6 ) ``` #### Errors ```go var ( ErrBadRequest = NewError(StatusBadRequest) // 400 ErrUnauthorized = NewError(StatusUnauthorized) // 401 ErrPaymentRequired = NewError(StatusPaymentRequired) // 402 ErrForbidden = NewError(StatusForbidden) // 403 ErrNotFound = NewError(StatusNotFound) // 404 ErrMethodNotAllowed = NewError(StatusMethodNotAllowed) // 405 ErrNotAcceptable = NewError(StatusNotAcceptable) // 406 ErrProxyAuthRequired = NewError(StatusProxyAuthRequired) // 407 ErrRequestTimeout = NewError(StatusRequestTimeout) // 408 ErrConflict = NewError(StatusConflict) // 409 ErrGone = NewError(StatusGone) // 410 ErrLengthRequired = NewError(StatusLengthRequired) // 411 ErrPreconditionFailed = NewError(StatusPreconditionFailed) // 412 ErrRequestEntityTooLarge = NewError(StatusRequestEntityTooLarge) // 413 ErrRequestURITooLong = NewError(StatusRequestURITooLong) // 414 ErrUnsupportedMediaType = NewError(StatusUnsupportedMediaType) // 415 ErrRequestedRangeNotSatisfiable = NewError(StatusRequestedRangeNotSatisfiable) // 416 ErrExpectationFailed = NewError(StatusExpectationFailed) // 417 ErrTeapot = NewError(StatusTeapot) // 418 ErrMisdirectedRequest = NewError(StatusMisdirectedRequest) // 421 ErrUnprocessableEntity = NewError(StatusUnprocessableEntity) // 422 ErrLocked = NewError(StatusLocked) // 423 ErrFailedDependency = NewError(StatusFailedDependency) // 424 ErrTooEarly = NewError(StatusTooEarly) // 425 ErrUpgradeRequired = NewError(StatusUpgradeRequired) // 426 ErrPreconditionRequired = NewError(StatusPreconditionRequired) // 428 ErrTooManyRequests = NewError(StatusTooManyRequests) // 429 ErrRequestHeaderFieldsTooLarge = NewError(StatusRequestHeaderFieldsTooLarge) // 431 ErrUnavailableForLegalReasons = NewError(StatusUnavailableForLegalReasons) // 451 ErrInternalServerError = NewError(StatusInternalServerError) // 500 ErrNotImplemented = NewError(StatusNotImplemented) // 501 ErrBadGateway = NewError(StatusBadGateway) // 502 ErrServiceUnavailable = NewError(StatusServiceUnavailable) // 503 ErrGatewayTimeout = NewError(StatusGatewayTimeout) // 504 ErrHTTPVersionNotSupported = NewError(StatusHTTPVersionNotSupported) // 505 ErrVariantAlsoNegotiates = NewError(StatusVariantAlsoNegotiates) // 506 ErrInsufficientStorage = NewError(StatusInsufficientStorage) // 507 ErrLoopDetected = NewError(StatusLoopDetected) // 508 ErrNotExtended = NewError(StatusNotExtended) // 510 ErrNetworkAuthenticationRequired = NewError(StatusNetworkAuthenticationRequired) // 511 ) ``` HTTP Headers were copied from net/http. ```go const ( HeaderAuthorization = "Authorization" HeaderProxyAuthenticate = "Proxy-Authenticate" HeaderProxyAuthorization = "Proxy-Authorization" HeaderWWWAuthenticate = "WWW-Authenticate" HeaderAge = "Age" HeaderCacheControl = "Cache-Control" HeaderClearSiteData = "Clear-Site-Data" HeaderExpires = "Expires" HeaderPragma = "Pragma" HeaderWarning = "Warning" HeaderAcceptCH = "Accept-CH" HeaderAcceptCHLifetime = "Accept-CH-Lifetime" HeaderContentDPR = "Content-DPR" HeaderDPR = "DPR" HeaderEarlyData = "Early-Data" HeaderSaveData = "Save-Data" HeaderViewportWidth = "Viewport-Width" HeaderWidth = "Width" HeaderETag = "ETag" HeaderIfMatch = "If-Match" HeaderIfModifiedSince = "If-Modified-Since" HeaderIfNoneMatch = "If-None-Match" HeaderIfUnmodifiedSince = "If-Unmodified-Since" HeaderLastModified = "Last-Modified" HeaderVary = "Vary" HeaderConnection = "Connection" HeaderKeepAlive = "Keep-Alive" HeaderAccept = "Accept" HeaderAcceptCharset = "Accept-Charset" HeaderAcceptEncoding = "Accept-Encoding" HeaderAcceptLanguage = "Accept-Language" HeaderCookie = "Cookie" HeaderExpect = "Expect" HeaderMaxForwards = "Max-Forwards" HeaderSetCookie = "Set-Cookie" HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials" HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers" HeaderAccessControlAllowMethods = "Access-Control-Allow-Methods" HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin" HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers" HeaderAccessControlMaxAge = "Access-Control-Max-Age" HeaderAccessControlRequestHeaders = "Access-Control-Request-Headers" HeaderAccessControlRequestMethod = "Access-Control-Request-Method" HeaderOrigin = "Origin" HeaderTimingAllowOrigin = "Timing-Allow-Origin" HeaderXPermittedCrossDomainPolicies = "X-Permitted-Cross-Domain-Policies" HeaderDNT = "DNT" HeaderTk = "Tk" HeaderContentDisposition = "Content-Disposition" HeaderContentEncoding = "Content-Encoding" HeaderContentLanguage = "Content-Language" HeaderContentLength = "Content-Length" HeaderContentLocation = "Content-Location" HeaderContentType = "Content-Type" HeaderForwarded = "Forwarded" HeaderVia = "Via" HeaderXForwardedFor = "X-Forwarded-For" HeaderXForwardedHost = "X-Forwarded-Host" HeaderXForwardedProto = "X-Forwarded-Proto" HeaderXForwardedProtocol = "X-Forwarded-Protocol" HeaderXForwardedSsl = "X-Forwarded-Ssl" HeaderXUrlScheme = "X-Url-Scheme" HeaderLocation = "Location" HeaderFrom = "From" HeaderHost = "Host" HeaderReferer = "Referer" HeaderReferrerPolicy = "Referrer-Policy" HeaderUserAgent = "User-Agent" HeaderAllow = "Allow" HeaderServer = "Server" HeaderAcceptRanges = "Accept-Ranges" HeaderContentRange = "Content-Range" HeaderIfRange = "If-Range" HeaderRange = "Range" HeaderContentSecurityPolicy = "Content-Security-Policy" HeaderContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only" HeaderCrossOriginResourcePolicy = "Cross-Origin-Resource-Policy" HeaderExpectCT = "Expect-CT" HeaderPermissionsPolicy = "Permissions-Policy" HeaderPublicKeyPins = "Public-Key-Pins" HeaderPublicKeyPinsReportOnly = "Public-Key-Pins-Report-Only" HeaderStrictTransportSecurity = "Strict-Transport-Security" HeaderUpgradeInsecureRequests = "Upgrade-Insecure-Requests" HeaderXContentTypeOptions = "X-Content-Type-Options" HeaderXDownloadOptions = "X-Download-Options" HeaderXFrameOptions = "X-Frame-Options" HeaderXPoweredBy = "X-Powered-By" HeaderXXSSProtection = "X-XSS-Protection" HeaderLastEventID = "Last-Event-ID" HeaderNEL = "NEL" HeaderPingFrom = "Ping-From" HeaderPingTo = "Ping-To" HeaderReportTo = "Report-To" HeaderTE = "TE" HeaderTrailer = "Trailer" HeaderTransferEncoding = "Transfer-Encoding" HeaderSecFetchSite = "Sec-Fetch-Site" HeaderSecWebSocketAccept = "Sec-WebSocket-Accept" HeaderSecWebSocketExtensions = "Sec-WebSocket-Extensions" HeaderSecWebSocketKey = "Sec-WebSocket-Key" HeaderSecWebSocketProtocol = "Sec-WebSocket-Protocol" HeaderSecWebSocketVersion = "Sec-WebSocket-Version" HeaderAcceptPatch = "Accept-Patch" HeaderAcceptPushPolicy = "Accept-Push-Policy" HeaderAcceptSignature = "Accept-Signature" HeaderAltSvc = "Alt-Svc" HeaderDate = "Date" HeaderIndex = "Index" HeaderLargeAllocation = "Large-Allocation" HeaderLink = "Link" HeaderPushPolicy = "Push-Policy" HeaderRetryAfter = "Retry-After" HeaderServerTiming = "Server-Timing" HeaderSignature = "Signature" HeaderSignedHeaders = "Signed-Headers" HeaderSourceMap = "SourceMap" HeaderUpgrade = "Upgrade" HeaderXDNSPrefetchControl = "X-DNS-Prefetch-Control" HeaderXPingback = "X-Pingback" HeaderXRequestID = "X-Request-ID" HeaderXRequestedWith = "X-Requested-With" HeaderXResponseTime = "X-Response-Time" HeaderXRobotsTag = "X-Robots-Tag" HeaderXUACompatible = "X-UA-Compatible" HeaderAccessControlAllowPrivateNetwork = "Access-Control-Allow-Private-Network" HeaderAccessControlRequestPrivateNetwork = "Access-Control-Request-Private-Network" ) ``` #### Network types Network names for [`ListenConfig.ListenerNetwork`](./fiber.md#listenernetwork). ```go const ( NetworkTCP = "tcp" NetworkTCP4 = "tcp4" NetworkTCP6 = "tcp6" NetworkUnix = "unix" ) ``` #### Content coding names Content coding names, as they appear in `Content-Encoding` and `Accept-Encoding` header values. Fiber reads them when it decodes a request body and when it negotiates an encoding. ```go const ( StrGzip = "gzip" StrCompress = "compress" StrIdentity = "identity" StrBr = "br" StrDeflate = "deflate" StrBrotli = "brotli" StrZstd = "zstd" ) ``` #### Cookie SameSite modes Values for the `SameSite` field of [`fiber.Cookie`](./ctx.md#cookie) and for the `CookieSameSite` option of the [CSRF](../middleware/csrf.md) and [session](../middleware/session.md) middleware. They are matched case-insensitively. ```go const ( CookieSameSiteDisabled = "disabled" // not in RFC, just control "SameSite" attribute will not be set. CookieSameSiteLaxMode = "Lax" CookieSameSiteStrictMode = "Strict" CookieSameSiteNoneMode = "None" ) ``` #### Route constraint names Names of the built-in route [constraints](../guide/routing.md#constraints), as written in a route pattern such as `/user/:id`. Built-in constraint names match case-insensitively, so `ConstraintMinLen` and `ConstraintMinLenLower` name the same constraint. ```go const ( ConstraintInt = "int" ConstraintBool = "bool" ConstraintFloat = "float" ConstraintAlpha = "alpha" ConstraintGUID = "guid" ConstraintMinLen = "minLen" ConstraintMaxLen = "maxLen" ConstraintLen = "len" ConstraintBetweenLen = "betweenLen" ConstraintMinLenLower = "minlen" ConstraintMaxLenLower = "maxlen" ConstraintBetweenLenLower = "betweenlen" ConstraintMin = "min" ConstraintMax = "max" ConstraintRange = "range" ConstraintDatetime = "datetime" ConstraintRegex = "regex" ) ``` --- ## 🧠 Ctx Use the index to jump straight to any `Ctx` method; filter by name or by category: #### Abandon Marks the context as abandoned. An abandoned context will not be returned to the pool when `ReleaseCtx` is called. This is used internally by the [timeout middleware](../middleware/timeout.md) and the [SSE middleware](../middleware/sse.md) to return immediately while a goroutine continues to use the context safely. ```go title="Signature" func (c fiber.Ctx) Abandon() func (c fiber.Ctx) IsAbandoned() bool func (c fiber.Ctx) ForceRelease() func (c fiber.Ctx) ScheduleReclaim(handlerDone <-chan struct{}, cancel context.CancelFunc) ``` | Method | Description | |:---------------|:----------------------------------------------------------------------------| | `Abandon()` | Marks the context as abandoned. ReleaseCtx becomes a no-op for this context. | | `IsAbandoned()`| Returns `true` if `Abandon()` was called on this context. | | `ForceRelease()`| Releases an abandoned context back to the pool. Must only be called after every goroutine that touches the context has finished, Fiber's own request handler and `ErrorHandler` included; calling it earlier races with them. | | `ScheduleReclaim(handlerDone, cancel)`| Abandons the context and returns it to the pool by itself once that is safe: after `handlerDone` is closed and Fiber has released the context. `cancel`, when not `nil`, is called as soon as `handlerDone` is closed. | `ScheduleReclaim` is the race-free alternative to calling `ForceRelease` yourself, and the way the timeout middleware hands the context to a handler that outlives the request. It calls `Abandon` itself, so calling `Abandon` first is harmless. Close `handlerDone` once the goroutine that still uses the context has completely finished. If it never closes, because the handler never returns, the context is deliberately never reclaimed. :::note Only `*fiber.DefaultCtx` signals `ReleaseCtx` that reclamation is armed. With a custom `Ctx` type that embeds it the signal never arrives, so the context is never reclaimed. The timeout middleware uses `Abandon` alone for such types. ::: :::caution These methods are primarily for internal use and advanced middleware development. Most applications should not need to call them directly. ::: #### App Returns the [\*App](app.md) reference so you can easily access all application settings. ```go title="Signature" func (c fiber.Ctx) App() *App ``` ```go title="Example" app.Get("/stack", func(c fiber.Ctx) error { return c.JSON(c.App().Stack()) }) ``` #### Bind Bind returns a helper for decoding the request body, query string, headers, cookies, and more. For full details, see the [Bind](./bind.md) documentation. ```go title="Signature" func (c fiber.Ctx) Bind() *Bind ``` ```go title="Example" app.Post("/", func(c fiber.Ctx) error { user := new(User) // Bind the request body to a struct: return c.Bind().Body(user) }) ``` #### Context Returns a `context.Context` that was previously set with [`SetContext`](#setcontext). If no context was set, it returns `context.Background()`. Unlike `fiber.Ctx` itself, the returned context is safe to use after the handler completes. ```go title="Signature" func (c fiber.Ctx) Context() context.Context ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { ctx := c.Context() go doWork(ctx) return nil }) ``` :::info Graceful shutdown The default `Context()` is **not** canceled when the app shuts down. In-flight handlers that pass `c.Context()` to database clients or other cancellation-aware work keep running until they finish (or until you cancel a derived context yourself). [`RequestCtx`](#requestctx) implements `context.Context` differently: its `Done()` channel is closed when the underlying fasthttp server starts shutting down. Prefer `c.Context()` (or a context you derive with `context.WithCancel` / timeouts) for work that must outlive the start of graceful shutdown. ::: #### context.Context `Ctx` implements `context.Context`, but as a context that can never be canceled: `Deadline()` reports no deadline, `Done()` returns `nil` and `Err()` returns `nil`, regardless of what you pass to [`SetContext`](#setcontext). The `fiber.Ctx` instance is pooled and reused after the handler returns, which is why it cannot carry cancellation of its own. Call [`Context`](#context) within the handler to obtain a real `context.Context`, and pass that to anything that is cancellation-aware or that outlives the handler. ```go title="Signature" func (c fiber.Ctx) Deadline() (deadline time.Time, ok bool) func (c fiber.Ctx) Done() <-chan struct{} func (c fiber.Ctx) Err() error func (c fiber.Ctx) Value(key any) any ``` ```go title="Example" func doSomething(ctx context.Context) { // ... } app.Get("/", func(c fiber.Ctx) error { doSomething(c) return nil }) ``` :::caution Passing `c` satisfies the compiler but carries no cancellation, so a call that honors `context.Context` will never be interrupted. Derive from [`Context`](#context) and pass that instead: ::: ```go title="Example" app.Get("/", func(c fiber.Ctx) error { ctx, cancel := context.WithTimeout(c.Context(), 5*time.Second) defer cancel() // The driver respects the 5s timeout. Passing c would never time out. rows, err := db.QueryContext(ctx, "SELECT ...") if err != nil { return err } defer rows.Close() // ... return nil }) ``` [`SetContext`](#setcontext) replaces what `Context()` returns for the rest of the request, so downstream middleware and handlers observe it. It does not make `c` itself cancelable. ##### Value Value can be used to retrieve [**`Locals`**](#locals). ```go title="Example" app.Get("/", func(c fiber.Ctx) error { c.Locals(userKey, "admin") user := c.Value(userKey) // returns "admin" return c.JSON(user) }) ``` #### Elapsed Returns how long this request has been handled so far, measured from [`StartTime`](#starttime). Called after the handler chain has run, it is the request latency. ```go title="Signature" func (c fiber.Ctx) Elapsed() time.Duration ``` ```go title="Example" app.Use(func(c fiber.Ctx) error { err := c.Next() metrics.Observe(c.Route().Path, c.Elapsed()) return err }) ``` #### Endpoint Returns the route that will handle this request, without advancing the handler chain. Useful inside global middleware for access control or logging when you need the target route's `Path` or `Name` before calling `Next`. Returns `nil` when no endpoint will run: on `404` and `405`, and while the error handler replays the chain for a request rejected at the protocol level, such as one over `BodyLimit`. ```go title="Signature" func (c fiber.Ctx) Endpoint() *Route ``` ```go title="Example" app.Use(func(c fiber.Ctx) error { route := c.Endpoint() // e.g. "/api/users/:id" named "user.show" if route == nil { return c.Next() } // enforce access control using route.Path or route.Name return c.Next() }) app.Get("/api/users/:id", handler).Name("user.show") ``` :::info `Endpoint` looks ahead, while its neighbors look back: [`Route`](#route) is the route currently executing, which inside middleware is the middleware itself, and [`Matched`](#matched) reports whether an endpoint has been selected yet. Mounted sub-apps report the flattened, prefixed route. The result is the route the router *would* reach, not a promise that it runs: an earlier middleware can still end the request first. It scans the remaining routes in the request's tree bucket, so calling it from global middleware costs a second router scan per request. That is cheap for routes spread over many prefixes and noticeable when a hundred or more share one, as with everything under `/api/v1`. ::: #### Error Returns an [`*fiber.Error`](./fiber.md#newerror) carrying the given status code, so a handler can reject a request in one line. The message defaults to the status text when omitted. Returning the error hands it to the app's `ErrorHandler`, which is what writes the response; `Error` itself sets nothing on the response. ```go title="Signature" func (c fiber.Ctx) Error(status int, message ...string) error ``` ```go title="Example" app.Get("/user/:id", func(c fiber.Ctx) error { id, err := strconv.Atoi(c.Params("id")) if err != nil { return c.Error(fiber.StatusBadRequest, "id must be numeric") } if !exists(id) { return c.Error(fiber.StatusNotFound) // => "Not Found" } return c.JSON(load(id)) }) ``` #### FullPath Returns the full path of the matched route. This includes any prefixes that were added by [groups](../guide/routing.md#grouping) or mounts. ```go title="Signature" func (c fiber.Ctx) FullPath() string ``` ```go title="Example" api := app.Group("/api") api.Get("/users/:id", func(c fiber.Ctx) error { return c.JSON(fiber.Map{ "route": c.FullPath(), // "/api/users/:id" }) }) app.Use(func(c fiber.Ctx) error { beforeNext := c.FullPath() // "/" if err := c.Next(); err != nil { return err } afterNext := c.FullPath() // "/api/users/:id" // ... react to the downstream handler's route path return nil }) ``` #### GetHeaders Returns the HTTP request headers as a map, like [`GetReqHeaders`](#getreqheaders): each key maps to a slice with all values for that header. `Req` and `Res` both carry a `GetHeaders`, so on `Ctx` the request wins, as it does for [`Get`](#get). `c.GetHeaders()` and `c.Req().GetHeaders()` return the request headers, while `c.Res().GetHeaders()` returns the response headers, the same map [`GetRespHeaders`](#getrespheaders) returns. ```go title="Signature" func (c fiber.Ctx) GetHeaders() map[string][]string func (r fiber.Req) GetHeaders() map[string][]string func (r fiber.Res) GetHeaders() map[string][]string ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { // X-Forwarded-For: 10.0.0.1 // X-Forwarded-For: 10.0.0.2 fmt.Println(c.GetHeaders()["X-Forwarded-For"]) // => [10.0.0.1 10.0.0.2] c.Set("X-Request-Id", "8d7ad5e3") fmt.Println(c.Res().GetHeaders()["X-Request-Id"]) // => [8d7ad5e3] // ... }) ``` :::info The returned value is valid only within the handler. Do not store references. Make copies or use the [**`Immutable`**](./fiber.md#immutable) setting instead. [Read more...](../#zero-allocation) ::: #### GetReqHeaders Returns the HTTP request headers as a map. Because a header can appear multiple times in a request, each key maps to a slice with all values for that header. It is the `Ctx` name for [`GetHeaders`](#getheaders). ```go title="Signature" func (c fiber.Ctx) GetReqHeaders() map[string][]string ``` :::info The returned value is valid only within the handler. Do not store references. Make copies or use the [**`Immutable`**](./fiber.md#immutable) setting instead. [Read more...](../#zero-allocation) ::: #### GetRespHeader Returns the HTTP response header specified by the field. On `Res` the same method is called `Get`, so `c.GetRespHeader(key)` and `c.Res().Get(key)` are the same call. :::tip The match is **case-insensitive**. ::: ```go title="Signature" func (c fiber.Ctx) GetRespHeader(key string, defaultValue ...string) string func (r fiber.Res) Get(key string, defaultValue ...string) string ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { c.GetRespHeader("X-Request-Id") // "8d7ad5e3-aaf3-450b-a241-2beb887efd54" c.GetRespHeader("Content-Type") // "text/plain" c.GetRespHeader("something", "john") // "john" // .. }) ``` :::info The returned value is valid only within the handler. Do not store references. Make copies or use the [**`Immutable`**](./fiber.md#immutable) setting instead. [Read more...](../#zero-allocation) ::: #### GetRespHeaders Returns the HTTP response headers as a map. Since a header can be set multiple times in a single request, the values of the map are slices of strings containing all the different values of the header. On `Res` the same method is called [`GetHeaders`](#getheaders). ```go title="Signature" func (c fiber.Ctx) GetRespHeaders() map[string][]string func (r fiber.Res) GetHeaders() map[string][]string ``` :::info The returned value is valid only within the handler. Do not store references. Make copies or use the [**`Immutable`**](./fiber.md#immutable) setting instead. [Read more...](../#zero-allocation) ::: #### GetRouteURL Generates URLs to named routes, with parameters. URLs are relative, for example: "/user/1831" ```go title="Signature" func (c fiber.Ctx) GetRouteURL(routeName string, params Map) (string, error) ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { return c.SendString("Home page") }).Name("home") app.Get("/user/:id", func(c fiber.Ctx) error { return c.SendString(c.Params("id")) }).Name("user.show") app.Get("/test", func(c fiber.Ctx) error { location, _ := c.GetRouteURL("user.show", fiber.Map{"id": 1}) return c.SendString(location) }) // /test returns "/user/1" ``` :::note Each `params` value is automatically percent-encoded with URL path-segment rules. Greedy parameters (`*` and `+`) preserve `/` inside the matched path tail; delimiters such as `?` and `#` remain parameter data instead of adding a query or fragment. [`Route.URL`](./app.md#getroute) and [`Redirect().Route`](./redirect.md#route) apply the same encoding. These take the value as data, not as URL text. Representable values use the same encoding under any configuration. With `UnescapePath` off (the default) `c.Params` returns the value still percent-encoded, so forwarding it straight back encodes the `%` a second time. Turn `UnescapePath` on, or decode with [`url.PathUnescape`](https://pkg.go.dev/net/url#PathUnescape) first. An app with `UnescapePath` enabled decodes `%2F` before route matching, so an ordinary single-segment parameter holding a `/` cannot round-trip and returns `ErrRouteNotRepresentable`. Greedy (`*` or `+`), adjacent single-byte parameters and required parameters with a single-byte non-slash terminator retain their existing slash-matching rules. An optional parameter with such a terminator does not match a `/`, so a decoded `/` in its value returns `ErrRouteNotRepresentable`. Dot-containing values are also rejected when the composed path has a `.` or `..` segment that would be removed during normalization. Surrounding route constants are considered: `:name.txt` with `name="."` remains representable. Pass a different value or use a route that can represent it; do not pre-encode it. ::: #### Hijack Registers a handler that takes over the connection once the current response is sent, for protocols Fiber does not speak itself. The handler runs on a goroutine of its own once the response has been written, after which the connection is closed unless the server has `KeepHijackedConns` set. :::caution The `Ctx` is pooled and reused, so the hijack handler must not touch it. ::: ```go title="Signature" func (c fiber.Ctx) Hijack(handler fasthttp.HijackHandler) ``` ```go title="Example" app.Get("/raw", func(c fiber.Ctx) error { c.Hijack(func(conn net.Conn) { _, _ = conn.Write([]byte("speaking something else now")) }) return c.SendStatus(fiber.StatusSwitchingProtocols) }) ``` #### Hijacked Returns `true` if [`Hijack`](#hijack) has been called on this request, so a later handler can tell that the connection is already spoken for and leave the response alone. ```go title="Signature" func (c fiber.Ctx) Hijacked() bool ``` ```go title="Example" app.Use(func(c fiber.Ctx) error { if err := c.Next(); err != nil { return err } if c.Hijacked() { return nil // Nothing to add to a hijacked connection. } c.Set("X-Served-By", "fiber") return nil }) ``` #### ID Returns the connection-unique identifier assigned to this request. It is cheap and always present, unlike [`RequestID`](#requestid), which reads a header a client or proxy has to have set. :::note The value is not unique across processes or restarts, so it is for correlating log lines within one server run. ::: ```go title="Signature" func (c fiber.Ctx) ID() uint64 ``` ```go title="Example" app.Use(func(c fiber.Ctx) error { log.Printf("[%d] %s %s", c.ID(), c.Method(), c.Path()) return c.Next() }) ``` #### IsFinal Returns `true` only for the last handler of a matched, non-middleware route, so nothing further on that route runs. It is `false` when no route matched at all — as [`IsMiddleware`](#ismiddleware) also is, so the two are not exact complements. :::caution This describes the route, not the whole request. A route registered with `Use` is never final even when it is the last thing that runs, and another route can still match after a final handler calls [`Next`](#next) — a specific path followed by a catch-all is the ordinary case. Use it to tell an endpoint handler from a middleware one, not to decide that the response is finished. ::: ```go title="Signature" func (c fiber.Ctx) IsFinal() bool ``` ```go title="Example" app.Use(func(c fiber.Ctx) error { c.IsFinal() // => false, there is always a c.Next() from here return c.Next() }) app.Get("/", func(c fiber.Ctx) error { c.IsFinal() // => true return nil }) ``` #### IsMiddleware Returns `true` if the current request handler was registered as middleware: a `Use` handler, or any handler of a route other than its last one. ```go title="Signature" func (c fiber.Ctx) IsMiddleware() bool ``` ```go title="Example" app.Get("/route", func(c fiber.Ctx) error { fmt.Println(c.IsMiddleware()) // true return c.Next() }, func(c fiber.Ctx) error { fmt.Println(c.IsMiddleware()) // false return c.SendStatus(fiber.StatusOK) }) ``` #### LocalAddr Returns the server-side address of the connection this request arrived on. [`IP`](#ip) returns the client address as a string; this is the full `net.Addr`, so the port, network, and unix socket path survive. ```go title="Signature" func (c fiber.Ctx) LocalAddr() net.Addr ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { c.LocalAddr().String() // => "127.0.0.1:3000" // ... }) ``` #### Locals Stores variables scoped to the request, making them available only to matching routes. The variables are removed after the request completes. If a stored value implements `io.Closer`, Fiber calls its `Close` method before removal. :::tip This is useful if you want to pass some **specific** data to the next middleware. Remember to perform type assertions when retrieving the data to ensure it is of the expected type. You can also use a non-exported type as a key to avoid collisions. ::: ```go title="Signature" func (c fiber.Ctx) Locals(key any, value ...any) any ``` :::note Storing a value with `c.Locals(key, value)` allocates the one-element slice its variadic parameter is packed into on every call, because the call goes through the `Ctx` interface. On hot paths use [`SetLocal`](#setlocal), which stores the same value without building that slice. ::: ```go title="Example" // keyType is an unexported type for keys defined in this package. // This prevents collisions with keys defined in other packages. type keyType int // userKey is the key for user.User values in Contexts. It is // unexported; clients use user.NewContext and user.FromContext // instead of using this key directly. var userKey keyType app.Use(func(c fiber.Ctx) error { c.Locals(userKey, "admin") // Stores the string "admin" under a non-exported type key return c.Next() }) app.Get("/admin", func(c fiber.Ctx) error { user, ok := c.Locals(userKey).(string) // Retrieves the data stored under the key and performs a type assertion if ok && user == "admin" { return c.Status(fiber.StatusOK).SendString("Welcome, admin!") } return c.SendStatus(fiber.StatusForbidden) }) ``` An alternative version of the `Locals` method that takes advantage of Go's generics feature is also available. This version allows for the manipulation and retrieval of local values within a request's context with a more specific data type. ```go title="Signature" func Locals[V any](c fiber.Ctx, key any, value ...V) V ``` ```go title="Example" app.Use(func(c fiber.Ctx) error { fiber.Locals[string](c, "john", "doe") fiber.Locals[int](c, "age", 18) fiber.Locals[bool](c, "isHuman", true) return c.Next() }) app.Get("/test", func(c fiber.Ctx) error { fiber.Locals[string](c, "john") // "doe" fiber.Locals[int](c, "age") // 18 fiber.Locals[bool](c, "isHuman") // true return nil }) ``` Make sure to understand and correctly implement the `Locals` method in both its standard and generic form for better control over route-specific data within your application. #### Matched Returns `true` if the current request path was matched by the router. ```go title="Signature" func (c fiber.Ctx) Matched() bool ``` ```go title="Example" app.Use(func(c fiber.Ctx) error { if c.Matched() { return c.Next() } return c.Status(fiber.StatusNotFound).SendString("Not Found") }) ``` #### MountPath Returns the prefix the sub-app owning the current route was mounted under, or an empty string when the route belongs to the top-level app. Fiber mounts by cloning a sub-app's routes into the app that serves them with the prefix already baked in, so [`Path`](#path) inside a mounted handler is the whole requested path, not one relative to the mount. `MountPath` is what tells such a handler which prefix it is living under, to build links back into its own app or to strip the prefix itself. :::note It answers from the route that is running, unlike [`App.MountPath`](./app.md#mountpath), which describes the app it is called on however that app is being served — a sub-app that is mounted *and* listened on directly reports its mount prefix there even for its own traffic, where this reports `""`. ::: :::caution The prefix is the owning app's, so mounting one `*fiber.App` at two prefixes reports the one it was mounted at last — the same limitation `App.MountPath` has, since that is where the prefix is recorded. Mount separate instances when each needs to know its own prefix. ::: ```go title="Signature" func (c fiber.Ctx) MountPath() string ``` ```go title="Example" micro := fiber.New() micro.Get("/doe", func(c fiber.Ctx) error { c.MountPath() // => "/john" c.Path() // => "/john/doe" // ... }) app := fiber.New() app.Use("/john", micro) ``` #### Next When **Next** is called, it executes the next method in the stack that matches the current route and returns the error that method returned. Return a non-`nil` error from a handler to end the chaining and call the [error handler](../guide/error-handling). ```go title="Signature" func (c fiber.Ctx) Next() error ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { fmt.Println("1st route!") return c.Next() }) app.Get("*", func(c fiber.Ctx) error { fmt.Println("2nd route!") return c.Next() }) app.Get("/", func(c fiber.Ctx) error { fmt.Println("3rd route!") return c.SendString("Hello, World!") }) ``` #### OverrideParam Overwrites the value of an existing route parameter. :::note If the parameter does not exist, this method does nothing. ::: ```go title="Signature" func (c fiber.Ctx) OverrideParam(name, value string) ``` ```go title="Example" // GET http://example.com/user app.Get("/user/:name", func(c fiber.Ctx) error { // mutate parameter c.OverrideParam("name", "new value") return c.SendString(c.Params("name")) // sends "new value" }) // GET http://example.com/shop/tech/1 app.Get("/shop/*", func(c fiber.Ctx) error { // mutate parameter c.OverrideParam("*", "new tech") // replaces "tech/1" with "new tech" return c.SendString(c.Params("*")) // sends "new tech" }) ``` Unnamed route parameters can be accessed by their character (`*` or `+`) followed by their position index (e.g., `*1` for the first wildcard, `*2` for the second). ```go title="Example" // GET /v1/brand/4/shop/blue/xs app.Get("/v1/*/shop/*", func(c fiber.Ctx) error { // mutate parameter c.OverrideParam("*1", "updated brand") c.OverrideParam("*2", "updated data") param1 := c.Params("*1") // "updated brand" param2 := c.Params("*2") // "updated data" // ... }) ``` #### Redirect Returns the Redirect reference. For detailed information, check the [Redirect](./redirect.md) documentation. ```go title="Signature" func (c fiber.Ctx) Redirect() *Redirect ``` ```go title="Example" app.Get("/coffee", func(c fiber.Ctx) error { return c.Redirect().To("/teapot") }) app.Get("/teapot", func(c fiber.Ctx) error { return c.Status(fiber.StatusTeapot).SendString("🍵 short and stout 🍵") }) ``` #### RemoteAddr Returns the address of the immediate peer, which is the proxy rather than the client when the app sits behind one. Use [`IP`](#ip) or [`IPs`](#ips) for the client address a trusted proxy forwarded. ```go title="Signature" func (c fiber.Ctx) RemoteAddr() net.Addr ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { c.RemoteAddr().String() // => "192.168.1.10:54321" // ... }) ``` #### Request Returns the [*fasthttp.Request](https://pkg.go.dev/github.com/valyala/fasthttp#Request) pointer. ```go title="Signature" func (c fiber.Ctx) Request() *fasthttp.Request ``` :::info Returns `nil` if the context has been released (e.g., after the handler completes and the context is returned to the pool). ::: ```go title="Example" app.Get("/", func(c fiber.Ctx) error { c.Request().Header.Method() // => []byte("GET") }) ``` #### RequestCtx Returns [\*fasthttp.RequestCtx](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx) that is compatible with the `context.Context` interface that requires a deadline, a cancellation signal, and other values across API boundaries. ```go title="Signature" func (c fiber.Ctx) RequestCtx() *fasthttp.RequestCtx ``` :::info Please read the [Fasthttp Documentation](https://pkg.go.dev/github.com/valyala/fasthttp?tab=doc) for more information. ::: #### Reset Resets the context fields by the given request when using server handlers. ```go title="Signature" func (c fiber.Ctx) Reset(fctx *fasthttp.RequestCtx) ``` It is used outside of the Fiber Handlers to reset the context for the next request. #### Response Returns the [\*fasthttp.Response](https://pkg.go.dev/github.com/valyala/fasthttp#Response) pointer. ```go title="Signature" func (c fiber.Ctx) Response() *fasthttp.Response ``` :::info Returns `nil` if the context has been released (e.g., after the handler completes and the context is returned to the pool). ::: ```go title="Example" app.Get("/", func(c fiber.Ctx) error { c.Response().BodyWriter().Write([]byte("Hello, World!")) // => "Hello, World!" return nil }) ``` #### RestartRouting Instead of executing the next method when calling [Next](ctx.md#next), **RestartRouting** restarts execution from the first method that matches the current route. This may be helpful after overriding the path, i.e., an internal redirect. Note that handlers might be executed again, which could result in an infinite loop. ```go title="Signature" func (c fiber.Ctx) RestartRouting() error ``` ```go title="Example" app.Get("/new", func(c fiber.Ctx) error { return c.SendString("From /new") }) app.Get("/old", func(c fiber.Ctx) error { c.Path("/new") return c.RestartRouting() }) ``` #### Route Returns the matched [Route](https://pkg.go.dev/github.com/gofiber/fiber/v3#Route) struct. ```go title="Signature" func (c fiber.Ctx) Route() *Route ``` ```go title="Example" // http://localhost:8080/hello app.Get("/hello/:name", func(c fiber.Ctx) error { r := c.Route() fmt.Println(r.Method, r.Path, r.Params, r.Handlers) // GET /hello/:name handler [name] // ... }) ``` :::caution `c.Route()` returns the **last executed route**. Inside middleware that runs before your handler it reflects the middleware route itself. Use [`c.Endpoint()`](#endpoint) to look up the downstream handler without advancing the chain. ::: ```go title="Example" func MyMiddleware() fiber.Handler { return func(c fiber.Ctx) error { beforeNext := c.Route().Path // Will be '/' err := c.Next() afterNext := c.Route().Path // Will be '/hello/:name' return err } } ``` #### RouteName Returns the name of the route currently executing, or an empty string when the route is unnamed. Inside middleware this is the middleware's own route; use `Endpoint().Name` to look ahead to the route that will handle the request. ```go title="Signature" func (c fiber.Ctx) RouteName() string ``` ```go title="Example" app.Get("/home", func(c fiber.Ctx) error { c.RouteName() // => "home" // ... }).Name("home") ``` #### SetContext Sets the base `context.Context` used by [`Context`](#context). Use this to propagate deadlines, cancellation signals, or values to asynchronous operations. ```go title="Signature" func (c fiber.Ctx) SetContext(ctx context.Context) ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { c.SetContext(context.WithValue(context.Background(), "user", "alice")) ctx := c.Context() go doWork(ctx) return nil }) ``` #### SetLocal Stores a value under a key scoped to the request, exactly like [`Locals`](#locals) with a value. It is available to all following routes that match the request, removed after the request completes, and `Close` is called on it first if it implements `io.Closer`. Unlike `Locals(key, value)`, the value is not passed variadically, so the one-element slice that call allocates is never built. Boxing the value itself can still allocate, exactly as it does for `Locals`. Prefer it in middleware and other per-request code. ```go title="Signature" func (c fiber.Ctx) SetLocal(key, value any) ``` ```go title="Example" app.Use(func(c fiber.Ctx) error { c.SetLocal(userKey, "admin") return c.Next() }) app.Get("/admin", func(c fiber.Ctx) error { user, ok := c.Locals(userKey).(string) if ok && user == "admin" { return c.SendString("Welcome, admin!") } return c.SendStatus(fiber.StatusForbidden) }) ``` #### StartTime Returns the time the server began handling this request. It is the reference point [`Elapsed`](#elapsed) measures from. ```go title="Signature" func (c fiber.Ctx) StartTime() time.Time ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { deadline := c.StartTime().Add(2 * time.Second) // ... }) ``` #### String Returns a unique string representation of the context. ```go title="Signature" func (c fiber.Ctx) String() string ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { c.String() // => "#0000000100000001 - 127.0.0.1:3000 <-> 127.0.0.1:61516 - GET http://localhost:3000/" // ... }) ``` #### ViewBind Adds variables to the default view variable map binding to the template engine. Variables are read by the `Render` method and may be overwritten. ```go title="Signature" func (c fiber.Ctx) ViewBind(vars Map) error ``` ```go title="Example" app.Use(func(c fiber.Ctx) error { c.ViewBind(fiber.Map{ "Title": "Hello, World!", }) return c.Next() }) app.Get("/", func(c fiber.Ctx) error { return c.Render("xxx.tmpl", fiber.Map{}) // Render will use the Title variable }) ``` ### Request Methods which operate on the incoming request. :::tip Use `c.Req()` to limit gopls suggestions to only these methods! ::: Each entry lists both forms it can be called in. `DefaultCtx` embeds `DefaultReq`, so the method is promoted: `c.UserAgent()` and `c.Req().UserAgent()` are the same call, and the second is what the `Req` interface exposes on its own. Four entries here are request-related but live on `Ctx` alone and so list only the `fiber.Ctx` form: [`ClientHelloInfo`](#clienthelloinfo), [`RequestID`](#requestid), [`SaveFile`](#savefile) and [`SaveFileToStorage`](#savefiletostorage). `Req` also carries [`App`](#app), [`Endpoint`](#endpoint), [`Locals`](#locals), [`SetLocal`](#setlocal), [`Request`](#request), [`RequestCtx`](#requestctx) and [`Route`](#route), which are documented at the top of this page under their `Ctx` signatures. #### AcceptEncoding Returns the `Accept-Encoding` request header. Repeated field lines are combined into one comma-joined list ([RFC 9110 Section 5.2](https://www.rfc-editor.org/rfc/rfc9110#section-5.2)), which is the list [`AcceptsEncodings`](#accepts) negotiates on. ```go title="Signature" func (c fiber.Ctx) AcceptEncoding() string func (r fiber.Req) AcceptEncoding() string ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { c.AcceptEncoding() // "gzip, br" return nil }) ``` #### AcceptLanguage Returns the `Accept-Language` request header. Repeated field lines are combined into one comma-joined list ([RFC 9110 Section 5.2](https://www.rfc-editor.org/rfc/rfc9110#section-5.2)), which is the list [`AcceptsLanguages`](#accepts) negotiates on. ```go title="Signature" func (c fiber.Ctx) AcceptLanguage() string func (r fiber.Req) AcceptLanguage() string ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { c.AcceptLanguage() // "en-US,en;q=0.9" return nil }) ``` #### Accepts Checks if the specified **extensions** or **content** **types** are acceptable. :::info Based on the request’s [Accept](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept) HTTP header. Repeated field lines are combined into one list ([RFC 9110 Section 5.2](https://www.rfc-editor.org/rfc/rfc9110#section-5.2)), and the field name is matched case-insensitively. The other `Accepts*` methods do the same for their own headers. ::: ```go title="Signature" func (c fiber.Ctx) Accepts(offers ...string) string func (c fiber.Ctx) AcceptsCharsets(offers ...string) string func (c fiber.Ctx) AcceptsEncodings(offers ...string) string func (c fiber.Ctx) AcceptsLanguages(offers ...string) string func (c fiber.Ctx) AcceptsLanguagesExtended(offers ...string) string func (r fiber.Req) Accepts(offers ...string) string func (r fiber.Req) AcceptsCharsets(offers ...string) string func (r fiber.Req) AcceptsEncodings(offers ...string) string func (r fiber.Req) AcceptsLanguages(offers ...string) string func (r fiber.Req) AcceptsLanguagesExtended(offers ...string) string ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { c.Accepts("html") // "html" c.Accepts("text/html") // "text/html" c.Accepts("json", "text") // "json" c.Accepts("application/json") // "application/json" c.Accepts("text/plain", "application/json") // "application/json", due to quality c.Accepts("image/png") // "" c.Accepts("png") // "" // ... }) ``` ```go title="Example 2" // Accept: text/html, text/*, application/json, */*; q=0 app.Get("/", func(c fiber.Ctx) error { c.Accepts("text/plain", "application/json") // "application/json", due to specificity c.Accepts("application/json", "text/html") // "text/html", due to first match c.Accepts("image/png") // "", due to */* with q=0 is Not Acceptable // ... }) ``` The weight of an offer is the one of the most specific range matching it (RFC 9110 §12.5.1), so a broad range with a higher weight never overrides a more specific range that lowers or rejects that offer: ```go title="Example 3" // Accept: text/*;q=1, text/html;q=0.5 app.Get("/", func(c fiber.Ctx) error { c.Accepts("text/html", "text/plain") // "text/plain": text/html weighs 0.5, text/plain 1 c.Accepts("text/html") // "text/html": a lowered weight is still acceptable // ... }) ``` Media-Type parameters are supported. ```go title="Example 4" // Accept: text/plain, application/json; version=1; foo=bar app.Get("/", func(c fiber.Ctx) error { // Extra parameters in the accept are ignored c.Accepts("text/plain;format=flowed") // "text/plain;format=flowed" // An offer must contain all parameters present in the Accept type c.Accepts("application/json") // "" // Parameter order and capitalization do not matter. Quotes on values are stripped. c.Accepts(`application/json;foo="bar";VERSION=1`) // "application/json;foo="bar";VERSION=1" }) ``` ```go title="Example 5" // Accept: text/plain;format=flowed;q=0.9, text/plain // i.e., "I prefer text/plain;format=flowed less than other forms of text/plain" app.Get("/", func(c fiber.Ctx) error { // The offer text/plain;format=flowed is weighed by the most specific range // that matches it, text/plain;format=flowed;q=0.9, while text/plain is weighed // by text/plain (q=1). The client's preference wins in either order. c.Accepts("text/plain;format=flowed", "text/plain") // "text/plain" c.Accepts("text/plain", "text/plain;format=flowed") // "text/plain" }) ``` Fiber provides similar functions for the other accept headers. For `Accept-Language`, Fiber uses the [Basic Filtering](https://www.rfc-editor.org/rfc/rfc4647#section-3.3.1) algorithm. A language range matches an offer only if it exactly equals the tag or is a prefix followed by a hyphen. For example, the range `en` matches `en-US`, but `en-US` does not match `en`. `AcceptsLanguagesExtended` applies [Extended Filtering](https://www.rfc-editor.org/rfc/rfc4647#section-3.3.2) where `*` may match zero or more subtags and wildcard matches can slide across subtags unless blocked by a singleton like `x`. ```go // Accept-Charset: utf-8, iso-8859-1;q=0.2 // Accept-Encoding: gzip, compress;q=0.2 // Accept-Language: en;q=0.8, nl, ru app.Get("/", func(c fiber.Ctx) error { c.AcceptsCharsets("utf-16", "iso-8859-1") // "iso-8859-1" c.AcceptsEncodings("compress", "br") // "compress" c.AcceptsLanguages("pt", "nl", "ru") // "nl" c.AcceptsLanguagesExtended("en-US", "fr-CA") // depends on extended ranges in the request header // ... }) ``` For `Accept-Encoding`, [RFC 9110 §12.5.3](https://www.rfc-editor.org/rfc/rfc9110#section-12.5.3) adds three rules to the weights: - A request without the header accepts any coding, so the first offer is returned. - A request whose header is present but empty wants no content coding, so only an `identity` offer matches. - `identity` (no coding) is acceptable even when the header does not list it, unless the header excludes it with `identity;q=0`, or with `*;q=0` and no entry of its own for `identity`. An unlisted `identity` ranks below every coding the header does list. ```go title="Example 6" // Accept-Encoding: gzip c.AcceptsEncodings("gzip", "identity") // "gzip" c.AcceptsEncodings("br", "identity") // "identity": br is not listed, identity is acceptable unless excluded // Accept-Encoding: gzip, identity;q=0 c.AcceptsEncodings("br", "identity") // "": identity is excluded // Accept-Encoding: (present, empty) c.AcceptsEncodings("gzip", "identity") // "identity": the client wants no coding ``` #### AcceptsEventStream Returns `true` when the `Accept` header allows `text/event-stream`. ```go title="Signature" func (c fiber.Ctx) AcceptsEventStream() bool func (r fiber.Req) AcceptsEventStream() bool ``` ```go title="Example" // Accept: text/html, application/json;q=0.9 app.Get("/", func(c fiber.Ctx) error { c.AcceptsEventStream() // false return nil }) ``` #### AcceptsHTML Returns `true` when the `Accept` header allows HTML. ```go title="Signature" func (c fiber.Ctx) AcceptsHTML() bool func (r fiber.Req) AcceptsHTML() bool ``` ```go title="Example" // Accept: text/html, application/json;q=0.9 app.Get("/", func(c fiber.Ctx) error { c.AcceptsHTML() // true return nil }) ``` #### AcceptsJSON Returns `true` when the `Accept` header allows JSON. ```go title="Signature" func (c fiber.Ctx) AcceptsJSON() bool func (r fiber.Req) AcceptsJSON() bool ``` ```go title="Example" // Accept: text/html, application/json;q=0.9 app.Get("/", func(c fiber.Ctx) error { c.AcceptsJSON() // true return nil }) ``` #### AcceptsXML Returns `true` when the `Accept` header allows XML. ```go title="Signature" func (c fiber.Ctx) AcceptsXML() bool func (r fiber.Req) AcceptsXML() bool ``` ```go title="Example" // Accept: text/html, application/json;q=0.9 app.Get("/", func(c fiber.Ctx) error { c.AcceptsXML() // false return nil }) ``` #### AllCookies Returns the cookies sent with the request as a name/value map, or `nil` when the client sent none. A repeated name resolves to its **first** occurrence, which is the one [`Cookies`](#cookies) answers with too. The two have to agree: a client can shadow a cookie by sending the name twice, and code that validated one value while consuming the other would validate the wrong one. Use [`CookieNames`](#cookienames) to see that a name was repeated at all. :::note The keys and values are copies rather than views into the request buffer, so they stay valid past the handler. fasthttp rewrites those bytes in place when a handler edits a request cookie, which would not merely stale the map but rehash it into one whose own keys no longer find their entries. ::: ```go title="Signature" func (c fiber.Ctx) AllCookies() map[string]string func (r fiber.Req) AllCookies() map[string]string ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { // Cookie: session=abc; theme=dark c.AllCookies() // => map[session:abc theme:dark] // ... }) ``` #### Authorization Splits the `Authorization` request header into its auth-scheme and the credentials that follow (RFC 9110, Section 11.6.2). Both are empty when the header is absent. :::caution The credentials are returned verbatim — `token68` or an `auth-param` list — and are neither decoded nor validated. Returned values are only valid within the handler. Do not store any references: they view the request buffer, which the next request on the connection overwrites, so a credential cached past the handler silently becomes another request's. Make copies or use the [**`Immutable`**](./fiber.md#config) setting instead. ::: ```go title="Signature" func (c fiber.Ctx) Authorization() (scheme, credentials string) func (r fiber.Req) Authorization() (scheme, credentials string) ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { // Authorization: Basic dXNlcjpwYXNz scheme, credentials := c.Authorization() // scheme => "Basic" // credentials => "dXNlcjpwYXNz" // ... }) ``` #### BaseURL Returns the base URL (**protocol** + **host**) as a `string`. ```go title="Signature" func (c fiber.Ctx) BaseURL() string func (r fiber.Req) BaseURL() string ``` ```go title="Example" // GET https://example.com/page#chapter-1 app.Get("/", func(c fiber.Ctx) error { c.BaseURL() // "https://example.com" // ... }) ``` #### Bearer Returns the credentials of a `Bearer` `Authorization` header (RFC 6750, Section 2.1), or an empty string when the header is absent or names a different auth-scheme. :::caution The token is returned verbatim: it is not validated, decoded, or verified, so it still has to be authenticated before it is trusted. Use the [keyauth](../middleware/keyauth.md) middleware when you want that done for you. Returned value is only valid within the handler. Do not store any references: it views the request buffer, which the next request on the connection overwrites, so a token cached past the handler — or used as a key in a shared map — silently becomes another request's. ::: ```go title="Signature" func (c fiber.Ctx) Bearer() string func (r fiber.Req) Bearer() string ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { // Authorization: Bearer abc123 c.Bearer() // => "abc123" // Authorization: Basic dXNlcjpwYXNz c.Bearer() // => "" // ... }) ``` #### Body As per the header `Content-Encoding`, this method will try to perform a file decompression from the **body** bytes. It decodes `gzip`, `deflate`, `br` and `zstd`, and several codings are undone in reverse order. In case no `Content-Encoding` header is sent (or when it is set to `identity`), it will perform as [BodyRaw](#bodyraw). If an unknown or unsupported encoding is encountered, the response status will be `415 Unsupported Media Type` or `501 Not Implemented`. Decompression is bounded by the app [BodyLimit](./fiber.md#bodylimit): a body that decompresses to more than that sets `413 Request Entity Too Large`. Whenever decoding fails, the returned slice holds the error message instead of request data. ```go title="Signature" func (c fiber.Ctx) Body() []byte func (r fiber.Req) Body() []byte ``` ```go title="Example" // echo 'user=john' | gzip | curl -v -i --data-binary @- -H "Content-Encoding: gzip" http://localhost:8080 app.Post("/", func(c fiber.Ctx) error { // Decompress body from POST request based on the Content-Encoding and return the raw content: return c.Send(c.Body()) // []byte("user=john") }) ``` :::info The returned value is valid only within the handler. Do not store references. Make copies or use the [**`Immutable`**](./fiber.md#immutable) setting instead. [Read more...](../#zero-allocation) ::: #### BodyRaw Returns the raw request **body**. ```go title="Signature" func (c fiber.Ctx) BodyRaw() []byte func (r fiber.Req) BodyRaw() []byte ``` ```go title="Example" // curl -X POST http://localhost:8080 -d user=john app.Post("/", func(c fiber.Ctx) error { // Get raw body from POST request: return c.Send(c.BodyRaw()) // []byte("user=john") }) ``` :::info The returned value is valid only within the handler. Do not store references. Make copies or use the [**`Immutable`**](./fiber.md#immutable) setting instead. [Read more...](../#zero-allocation) ::: #### BodyStream Returns the request body as a stream. It is only non-`nil` when [`StreamRequestBody`](./fiber.md#config) is enabled and the body has not already been buffered. :::caution Reading from the returned reader consumes the body, so a later [`Body`](#body) call will not see it. ::: ```go title="Signature" func (c fiber.Ctx) BodyStream() io.Reader func (r fiber.Req) BodyStream() io.Reader ``` ```go title="Example" app := fiber.New(fiber.Config{StreamRequestBody: true}) app.Post("/upload", func(c fiber.Ctx) error { stream := c.BodyStream() if stream == nil { return c.Send(c.Body()) // Already buffered. } written, err := io.Copy(dst, stream) if err != nil { return err } return c.JSON(fiber.Map{"written": written}) }) ``` #### Charset Returns the `charset` parameter from the `Content-Type` header. Only the first `charset` parameter counts, and the result is empty when its value, once unquoted, is not a [token](https://www.rfc-editor.org/rfc/rfc9110#section-5.6.2): one or more letters, digits or ``!#$%&'*+-.^_`|~``. A broken quoted string also gives an empty result. ```go title="Signature" func (c fiber.Ctx) Charset() string func (r fiber.Req) Charset() string ``` ```go title="Example" // Content-Type: application/json; charset=utf-8 app.Post("/", func(c fiber.Ctx) error { c.Charset() // "utf-8" return nil }) ``` #### ClientHelloInfo `ClientHelloInfo` contains information from the ClientHello message of the TLS connection the request arrived on, to guide application logic in the `GetCertificate` and `GetConfigForClient` callbacks. Fiber records it when `crypto/tls` calls the `GetCertificate` callback that [`TLSHandler.GetClientInfo`](./app.md#settlshandler) provides. It is recorded per connection, so concurrent connections never see each other's handshake, and it is `nil` when the app has no TLS handler, when the request did not come in over a connection it negotiated, or when `crypto/tls` did not call that callback for the connection. `crypto/tls` does not call `GetCertificate` for a resumed TLS 1.3 handshake, whatever the configuration: it authenticates with a pre-shared key and picks no certificate. When `tls.Config.Certificates` is set, it also skips the callback for a client that sends no server name (SNI), such as one that connects to an IP address. `Listen` with `CertFile` and `CertKeyFile` is such a configuration. Every other handshake calls it, and `ServerName` is empty for a client that sends none. The record is dropped when the server reports the connection closed or hijacked, and it is found through the wrapper fasthttp puts around a connection when `Server.MaxConnsPerIP` is set. Refer to the [ClientHelloInfo](https://golang.org/pkg/crypto/tls/#ClientHelloInfo) struct documentation for details on the returned struct. ```go title="Signature" func (c fiber.Ctx) ClientHelloInfo() *tls.ClientHelloInfo ``` ```go title="Example" // GET http://example.com/hello app.Get("/hello", func(c fiber.Ctx) error { chi := c.ClientHelloInfo() // ... }) ``` #### ContentLength Returns the value of the `Content-Length` request header. A negative result is **not a length**: `-1` is reported for a chunked body, and `-2` for a `GET` or `HEAD` request that declares no body, with or without `Transfer-Encoding: identity`. Any other method with no declared body reports `0`, and so does a chunked body with no data unless [`StreamRequestBody`](./fiber.md#streamrequestbody) is enabled, which reports `-1` for every chunked request. Use [`HasBody`](#hasbody) to test whether there is a body at all, and read this only to find out how large a declared one is. It is no cap on the body either: a chunked body reports `-1`, which a comparison like the one in the example below never rejects, so cap the request body with the app [BodyLimit](./fiber.md#bodylimit). :::note `Req` and `Res` both carry a `ContentLength`, so on `Ctx` the request wins, as it does for [`Get`](#get). Use `c.Res().ContentLength()` for the length of the response. ::: ```go title="Signature" func (c fiber.Ctx) ContentLength() int func (r fiber.Req) ContentLength() int ``` ```go title="Example" app.Post("/", func(c fiber.Ctx) error { if c.ContentLength() > maxUpload { return c.Error(fiber.StatusRequestEntityTooLarge) } // ... }) ``` #### ContentType Returns the `Content-Type` request header, parameters included. [`MediaType`](#mediatype) returns the same header with the parameters stripped, and [`Charset`](#charset) returns just the charset one. :::note `Req` and `Res` both carry a `ContentType`, so on `Ctx` the request wins, as it does for [`Get`](#get). Use `c.Res().ContentType()` for the type the response will send. ::: :::caution Returned value is only valid within the handler. Do not store any references. Make copies or use the [**`Immutable`**](./fiber.md#config) setting to use the value outside the handler. ::: ```go title="Signature" func (c fiber.Ctx) ContentType() string func (r fiber.Req) ContentType() string ``` ```go title="Example" app.Post("/", func(c fiber.Ctx) error { // Content-Type: application/json; charset=utf-8 c.ContentType() // => "application/json; charset=utf-8" c.MediaType() // => "application/json" // ... }) ``` #### CookieNames Returns the names of the cookies sent with the request, in the order they appear in the `Cookie` header, or `nil` when the client sent none. A name the client repeated is returned once per occurrence, which is how a caller detects the shadowing [`AllCookies`](#allcookies) collapses. :::note Unlike [`Cookies`](#cookies), the returned strings are copies rather than views into the request buffer, so they stay valid past the handler. ::: ```go title="Signature" func (c fiber.Ctx) CookieNames() []string func (r fiber.Req) CookieNames() []string ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { // Cookie: session=abc; theme=dark c.CookieNames() // => ["session", "theme"] // ... }) ``` #### Cookies Gets a cookie value by key. You can pass an optional default value that will be returned if the cookie key does not exist. This reads the cookies the client sent; for the cookies the response is set to send, see [`GetCookies`](#getcookies). ```go title="Signature" func (c fiber.Ctx) Cookies(key string, defaultValue ...string) string func (r fiber.Req) Cookies(key string, defaultValue ...string) string ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { // Get cookie by key: c.Cookies("name") // "john" c.Cookies("empty", "doe") // "doe" // ... }) ``` :::info The returned value is valid only within the handler. Do not store references. Use [`App.GetString`](./app.md#getstring) or [`App.GetBytes`](./app.md#getbytes) when immutability is enabled, or manually copy values (for example with [`utils.CopyString`](https://github.com/gofiber/utils) / `utils.CopyBytes`) when it's disabled. [Read more...](../#zero-allocation) ::: #### FormFile MultipartForm files can be retrieved by name, the **first** file from the given key is returned. The form is parsed within the app [BodyLimit](./fiber.md#bodylimit). ```go title="Signature" func (c fiber.Ctx) FormFile(key string) (*multipart.FileHeader, error) func (r fiber.Req) FormFile(key string) (*multipart.FileHeader, error) ``` ```go title="Example" app.Post("/", func(c fiber.Ctx) error { // Get first file from form field "document": file, err := c.FormFile("document") // Save file to root directory: return c.SaveFile(file, fmt.Sprintf("./%s", file.Filename)) }) ``` #### FormValue Form values can be retrieved by name, the **first** value for the given key is returned. The query string is searched first, then the urlencoded body, then the multipart form, so a query parameter hides a form field of the same name. A multipart form is parsed within the app [BodyLimit](./fiber.md#bodylimit). :::caution On a form request this lowercases the case-insensitive parts of the request's own `Content-Type`, so a value obtained earlier from `Get(HeaderContentType)` — which aliases those bytes unless [Immutable](./fiber.md#immutable) is set — can change during the call. Copy it first if you need it to outlive one. ::: ```go title="Signature" func (c fiber.Ctx) FormValue(key string, defaultValue ...string) string func (r fiber.Req) FormValue(key string, defaultValue ...string) string ``` ```go title="Example" app.Post("/", func(c fiber.Ctx) error { // Get first value from form field "name": c.FormValue("name") // => "john" or "" if not exist // .. }) ``` :::info The returned value is valid only within the handler. Do not store references. Make copies or use the [**`Immutable`**](./fiber.md#immutable) setting instead. [Read more...](../#zero-allocation) ::: #### Fresh When the response is still **fresh** in the client's cache **true** is returned; otherwise, **false** is returned to indicate that the client cache is now stale and the full response should be sent. When a client sends the Cache-Control: no-cache request header to indicate an end-to-end reload request, `Fresh` will return false to make handling these requests transparent. `Fresh` only applies to GET and HEAD requests and returns false for any other method, since a 304 Not Modified response is only defined for those methods and RFC 9110 requires If-Modified-Since to be ignored otherwise. The request fields it reads (`If-None-Match`, `If-Modified-Since` and `Cache-Control`) are matched case-insensitively, which matters under [`DisableHeaderNormalizing`](./fiber.md#disableheadernormalizing), where the names are kept as the peer sent them. `If-None-Match` and `Cache-Control` are read across repeated field lines. Read more on [https://expressjs.com/en/4x/api.html\#req.fresh](https://expressjs.com/en/4x/api.html#req.fresh) ```go title="Signature" func (c fiber.Ctx) Fresh() bool func (r fiber.Req) Fresh() bool ``` #### FullURL Returns the full request URL (protocol + host + original URL). ```go title="Signature" func (c fiber.Ctx) FullURL() string func (r fiber.Req) FullURL() string ``` ```go title="Example" // GET http://example.com/search?q=fiber app.Get("/", func(c fiber.Ctx) error { c.FullURL() // "http://example.com/search?q=fiber" return nil }) ``` #### Get Returns the HTTP request header specified by the field. `Res` has a `Get` as well, which reads a response header; see [`GetRespHeader`](#getrespheader). :::tip The match is **case-insensitive**. ::: ```go title="Signature" func (c fiber.Ctx) Get(key string, defaultValue ...string) string func (r fiber.Req) Get(key string, defaultValue ...string) string ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { c.Get("Content-Type") // "text/plain" c.Get("CoNtEnT-TypE") // "text/plain" c.Get("something", "john") // "john" // .. }) ``` `GetReqHeader` is the generic counterpart: it parses the same header into a typed value and returns the default, or the zero value, when the header is missing or does not parse. See the [utils guide](../guide/utils.md#getreqheader). ```go title="Signature" func GetReqHeader[V GenericType](c fiber.Ctx, key string, defaultValue ...V) V ``` :::caution Repeated field lines When a request carries the same header on several field lines, `Get` returns the first line that holds a value and steps over an empty leading line. `net/http`'s `Header.Get` and fasthttp's `Peek` return the first line even when it is empty, so a `net/http` layer in front of Fiber, such as a server mounting the app through the [adaptor](../middleware/adaptor.md), can read a field as empty that `Get` returns a value for. Use [`GetAll`](#getall) to see every line, and do not authorize on a header read with `Get` unless the layer in front strips or refuses it on every line. The `basicauth` and `csrf` middleware refuse a request that repeats the single-value fields they read, and so does `keyauth` with its default `Authorization` extractor; a custom `extractors.FromHeader` extractor combines repeated lines into one value instead. ::: :::info The returned value is valid only within the handler. Do not store references. Make copies or use the [**`Immutable`**](./fiber.md#immutable) setting instead. [Read more...](../#zero-allocation) ::: #### GetAll Returns every field line of the request header specified by `key`. Field names are case-insensitive. A header repeated across field lines is semantically one comma-joined list (RFC 9110, Section 5.2). `GetAll` keeps the lines apart so a caller can inspect them individually, where [`Get`](#get) returns only the first and [`GetReqHeaders`](#getreqheaders) builds a map of the whole header block. It returns `nil` when the header is absent. :::note Empty field lines are skipped, so `len(c.GetAll(k)) > 0` answers the same question as [`HasHeader`](#hasheader). Without that, the headers fasthttp keeps in a slot of their own (`Content-Length`, `Trailer`) report one empty line when they are not present at all. ::: :::caution Returned values are only valid within the handler. Do not store any references. Make copies or use the [**`Immutable`**](./fiber.md#config) setting instead. ::: ```go title="Signature" func (c fiber.Ctx) GetAll(key string) []string func (r fiber.Req) GetAll(key string) []string ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { // X-Forwarded-For: 10.0.0.1 // X-Forwarded-For: 10.0.0.2 c.GetAll("X-Forwarded-For") // => ["10.0.0.1", "10.0.0.2"] c.Get("X-Forwarded-For") // => "10.0.0.1" // ... }) ``` #### HasBody Returns `true` if the incoming request declares a body: a `Content-Length` greater than zero, a chunked body or another `Transfer-Encoding` besides `identity`, or payload data that is already buffered. A chunked body with no data counts only when [`StreamRequestBody`](./fiber.md#streamrequestbody) is enabled. Otherwise the server reads the body before the handler runs and a chunked request that carried no data reaches the handler as `Content-Length: 0` without a `Transfer-Encoding` field, so `HasBody` returns `false`. With `StreamRequestBody` the headers are seen as sent, and every chunked request counts, even an empty one. ```go title="Signature" func (c fiber.Ctx) HasBody() bool func (r fiber.Req) HasBody() bool ``` ```go title="Example" app.Post("/", func(c fiber.Ctx) error { if !c.HasBody() { return c.SendStatus(fiber.StatusBadRequest) } return c.SendString("OK") }) ``` #### HasHeader Reports whether the request includes a header with the given key. The field name is matched case-insensitively, and a header whose lines are all empty counts as absent, so the answer agrees with [`GetAll`](#getall). ```go title="Signature" func (c fiber.Ctx) HasHeader(key string) bool func (r fiber.Req) HasHeader(key string) bool ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { c.HasHeader("X-Trace-Id") return nil }) ``` #### HasHeaderValue Reports whether the request header `key` lists `value` as one of its comma-separated members, on any of its field lines. Repeated field lines are treated as one list ([RFC 9110 Section 5.3](https://www.rfc-editor.org/rfc/rfc9110#section-5.3)) and the member is matched case-insensitively, which fits directive-style headers such as `Cache-Control` or `Connection`. A comma inside a quoted argument does not split a member, so `ext="a,no-transform,b"` does not list `no-transform`. A backslash inside quotes escapes the next byte ([RFC 9110 Section 5.6.4](https://www.rfc-editor.org/rfc/rfc9110#section-5.6.4)), so `ext="a\"", no-transform` does list it. An empty `value` is never present. ```go title="Signature" func (c fiber.Ctx) HasHeaderValue(key, value string) bool func (r fiber.Req) HasHeaderValue(key, value string) bool ``` ```go title="Example" // Cache-Control: public, max-age=60 // Cache-Control: no-transform app.Get("/", func(c fiber.Ctx) error { c.HasHeaderValue(fiber.HeaderCacheControl, "no-transform") // true c.HasHeaderValue(fiber.HeaderCacheControl, "PUBLIC") // true c.HasHeaderValue(fiber.HeaderCacheControl, "max-age") // false return nil }) ``` #### Host Returns the host derived from the [Host](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Host) HTTP header. When the request comes from a trusted proxy (see [`TrustProxy`](fiber.md#trustproxy)), the first value of the `X-Forwarded-Host` header is used instead. In a network context, [`Host`](#host) refers to the combination of a hostname and potentially a port number used for connecting, while [`Hostname`](#hostname) refers specifically to the name assigned to a device on a network, excluding any port information. ```go title="Signature" func (c fiber.Ctx) Host() string func (r fiber.Req) Host() string ``` ```go title="Example" // GET http://google.com:8080/search app.Get("/", func(c fiber.Ctx) error { c.Host() // "google.com:8080" c.Hostname() // "google.com" // ... }) ``` :::info The returned value is valid only within the handler. Do not store references. Make copies or use the [**`Immutable`**](./fiber.md#immutable) setting instead. [Read more...](../#zero-allocation) ::: #### Hostname Returns the hostname derived from the [Host](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Host) HTTP header, without the port. It is taken from [`Host`](#host), so a trusted proxy's `X-Forwarded-Host` applies here too. ```go title="Signature" func (c fiber.Ctx) Hostname() string func (r fiber.Req) Hostname() string ``` ```go title="Example" // GET http://google.com/search app.Get("/", func(c fiber.Ctx) error { c.Hostname() // "google.com" // ... }) ``` :::info The returned value is valid only within the handler. Do not store references. Make copies or use the [**`Immutable`**](./fiber.md#immutable) setting instead. [Read more...](../#zero-allocation) ::: #### IfModifiedSince Returns the time carried by the `If-Modified-Since` request header. It returns `fiber.ErrHeaderNotFound` when the header is absent or empty, and a parse error when the value is none of the three HTTP-date formats RFC 9110, Section 5.6.7 requires recipients to accept. ```go title="Signature" func (c fiber.Ctx) IfModifiedSince() (time.Time, error) func (r fiber.Req) IfModifiedSince() (time.Time, error) ``` ```go title="Example" app.Get("/report", func(c fiber.Ctx) error { since, err := c.IfModifiedSince() switch { case errors.Is(err, fiber.ErrHeaderNotFound): // Unconditional request. case err != nil: return c.Error(fiber.StatusBadRequest, "malformed If-Modified-Since") case !report.ModTime().After(since): return c.SendStatus(fiber.StatusNotModified) } return c.JSON(report) }) ``` #### IfNoneMatch Returns the entity tags listed in the `If-None-Match` request header. Repeated field lines are combined into one list (RFC 9110, Section 5.2), and a comma inside a quoted opaque-tag does not split it, so `"v1,v2"` stays one tag. A wildcard header returns a single `*` element. :::note Tags are returned verbatim, weak `W/` prefix included, and are not validated; empty list elements are dropped as RFC 9110, Section 5.6.1 requires, so a trailing comma is not an extra tag. [`Fresh`](#fresh) already applies these tags to the response `ETag`; reach for this only to implement a comparison of your own. Returned values are only valid within the handler. Do not store any references. ::: ```go title="Signature" func (c fiber.Ctx) IfNoneMatch() []string func (r fiber.Req) IfNoneMatch() []string ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { // If-None-Match: W/"a", "b,c" c.IfNoneMatch() // => [`W/"a"`, `"b,c"`] // ... }) ``` #### IP Returns the remote IP address of the request. ```go title="Signature" func (c fiber.Ctx) IP() string func (r fiber.Req) IP() string ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { c.IP() // "127.0.0.1" // ... }) ``` :::info By default, `c.IP()` returns the remote IP address from the TCP connection. When your Fiber app is behind a reverse proxy (like Nginx, Traefik, or a load balancer), you need to configure **both** [`TrustProxy`](fiber.md#trustproxy) and [`ProxyHeader`](fiber.md#proxyheader) to read the client IP from proxy headers like `X-Forwarded-For`. **Important:** You must enable `TrustProxy` and configure trusted proxy IPs to prevent header spoofing. Simply setting `ProxyHeader` alone will not work. **Note:** When using a proxy header such as `X-Forwarded-For`, `c.IP()` returns the raw header value unless [`EnableIPValidation`](fiber.md#enableipvalidation) is enabled. A header sent on several field lines is read as one comma-joined list ([RFC 9110 Section 5.2](https://www.rfc-editor.org/rfc/rfc9110#section-5.2)), so the raw value and the chain walk below see every proxy's entry whichever line carries it. Without validation, an absent or empty header gives an empty string rather than the TCP remote address. Enable it whenever `c.IP()` is used as a single client identifier, for example by the [limiter](../middleware/limiter.md) middleware or an IP allowlist, unless your proxy overwrites the header with a single address: otherwise the raw value can be a comma-separated chain whose leading entries are supplied by the client. **Chain parsing with `EnableIPValidation`:** For `X-Forwarded-For`, the raw value is a comma-separated chain that grows from left to right as the request passes through each proxy. With validation enabled, `c.IP()` walks the chain from right to left, skipping every IP that matches the configured `TrustProxyConfig` (exact IPs, CIDR ranges, loopback, private or link-local) and returns the first non-trusted IP it finds. This matches the behavior recommended by [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-For#selecting_an_ip_address) and the convention used by Nginx (`set_real_ip_from` + `real_ip_recursive`), Apache `mod_remoteip`, and Envoy (`xff_num_trusted_hops`). If every IP in the chain matches the trusted set, the leftmost IP is returned as a fallback. Trust only your proxies: blanket `Private`, `Loopback` or `LinkLocal` trust is unsafe when clients can also connect from those ranges, because such a client's own entry is skipped and the address it supplied is returned instead; prefer exact proxy addresses or ranges that exclude client networks, or have the proxy overwrite the header. When `TrustProxyConfig` trusts only `UnixSocket`, there are no proxy IPs to skip and the first valid IP from the left is returned. If the chain is empty, `c.IP()` falls back to the TCP remote address. ::: ##### Configuration for apps behind a reverse proxy ```go title="Example - Basic Configuration" app := fiber.New(fiber.Config{ // Enable proxy support TrustProxy: true, // Specify which header contains the real client IP ProxyHeader: fiber.HeaderXForwardedFor, // Resolve a single client IP from the X-Forwarded-For chain EnableIPValidation: true, // Configure which proxy IPs to trust TrustProxyConfig: fiber.TrustProxyConfig{ // Trust private IP ranges (for internal load balancers). Only safe when // clients never connect from private addresses: a client in a trusted // range can forge the entry before its own, see the note above. Private: true, // Or, preferably, specify exact proxy IPs/ranges // Proxies: []string{"10.10.0.58", "192.168.0.0/24"}, }, }) ``` ```go title="Example - Specific Proxy IPs" app := fiber.New(fiber.Config{ TrustProxy: true, ProxyHeader: fiber.HeaderXForwardedFor, EnableIPValidation: true, TrustProxyConfig: fiber.TrustProxyConfig{ // Trust only specific proxy IP addresses Proxies: []string{"10.10.0.58", "192.168.1.0/24"}, }, }) ``` See [`TrustProxy`](fiber.md#trustproxy) and [`TrustProxyConfig`](fiber.md#trustproxyconfig) for more details on security considerations and configuration options. #### IPs Returns an array of IP addresses specified in the [X-Forwarded-For](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For) request header, with repeated field lines read as one comma-joined list and each address's surrounding spaces and tabs trimmed ([RFC 9110 Section 5.6.3](https://www.rfc-editor.org/rfc/rfc9110#section-5.6.3) allows either as optional whitespace). With `EnableIPValidation`, IPv4, IPv6 and IPv4-mapped IPv6 addresses (`::ffff:203.0.113.5`, as dual-stack proxies forward IPv4 clients) are all accepted; whitespace inside an address makes it invalid. ```go title="Signature" func (c fiber.Ctx) IPs() []string func (r fiber.Req) IPs() []string ``` ```go title="Example" // X-Forwarded-For: proxy1, 127.0.0.1, proxy3 app.Get("/", func(c fiber.Ctx) error { c.IPs() // ["proxy1", "127.0.0.1", "proxy3"] // ... }) ``` :::caution Improper use of the X-Forwarded-For header can be a security risk. For details, see the [Security and privacy concerns](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For#security_and_privacy_concerns) section. ::: #### Is Returns the matching **content type**, if the incoming request’s [Content-Type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) HTTP header field matches the [MIME type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types) specified by the type parameter. :::info Only the media type of the `Content-Type` header is compared, ignoring its parameters and its case, whether or not the request has a body. A request without a `Content-Type` returns **false**. ::: ```go title="Signature" func (c fiber.Ctx) Is(extension string) bool func (r fiber.Req) Is(extension string) bool ``` ```go title="Example" // Content-Type: text/html; charset=utf-8 app.Get("/", func(c fiber.Ctx) error { c.Is("html") // true c.Is(".html") // true c.Is("json") // false // ... }) ``` #### IsForm Reports whether the `Content-Type` header is form-encoded. ```go title="Signature" func (c fiber.Ctx) IsForm() bool func (r fiber.Req) IsForm() bool ``` ```go title="Example" // Content-Type: application/x-www-form-urlencoded app.Post("/", func(c fiber.Ctx) error { c.IsForm() // true return nil }) ``` #### IsFromLocal Returns `true` if the request came from localhost, that is, if the address of the immediate peer is a loopback address. Forwarded headers are not consulted, so behind a reverse proxy on the same host every request is local. ```go title="Signature" func (c fiber.Ctx) IsFromLocal() bool func (r fiber.Req) IsFromLocal() bool ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { // If request came from localhost, return true; else return false c.IsFromLocal() // ... }) ``` #### IsFromUnixSocket Returns `true` if the request came in over a Unix domain socket. ```go title="Signature" func (c fiber.Ctx) IsFromUnixSocket() bool func (r fiber.Req) IsFromUnixSocket() bool ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { if c.IsFromUnixSocket() { return c.SendString("Connected via Unix socket") } return c.SendString("Connected via TCP") }) ``` #### IsIdempotent Reports whether the request method is idempotent, meaning repeating it has the same intended effect as making it once (RFC 9110, Section 9.2.2). Every safe method is also idempotent. ```go title="Signature" func (c fiber.Ctx) IsIdempotent() bool func (r fiber.Req) IsIdempotent() bool ``` ```go title="Example" app.Use(func(c fiber.Ctx) error { // GET, HEAD, OPTIONS, TRACE, QUERY, PUT, DELETE => true // POST, PATCH => false if c.IsIdempotent() { return retryable(c) } return c.Next() }) ``` #### IsJSON Reports whether the `Content-Type` header is JSON. ```go title="Signature" func (c fiber.Ctx) IsJSON() bool func (r fiber.Req) IsJSON() bool ``` ```go title="Example" // Content-Type: application/json; charset=utf-8 app.Post("/", func(c fiber.Ctx) error { c.IsJSON() // true return nil }) ``` #### IsMultipart Reports whether the `Content-Type` header is multipart form data. ```go title="Signature" func (c fiber.Ctx) IsMultipart() bool func (r fiber.Req) IsMultipart() bool ``` ```go title="Example" // Content-Type: multipart/form-data; boundary=abc123 app.Post("/", func(c fiber.Ctx) error { c.IsMultipart() // true return nil }) ``` #### IsPreflight Returns `true` if the request is a CORS preflight (`OPTIONS` + `Access-Control-Request-Method` + `Origin`). ```go title="Signature" func (c fiber.Ctx) IsPreflight() bool func (r fiber.Req) IsPreflight() bool ``` ```go title="Example" app.Use(func(c fiber.Ctx) error { if c.IsPreflight() { return c.SendStatus(fiber.StatusNoContent) } return c.Next() }) ``` #### IsProxyTrusted Checks the trustworthiness of the remote IP. If [`TrustProxy`](fiber.md#trustproxy) is `false`, it returns `false`. `IsProxyTrusted` can check the remote IP by proxy ranges and IP map. ```go title="Signature" func (c fiber.Ctx) IsProxyTrusted() bool func (r fiber.Req) IsProxyTrusted() bool ``` ```go title="Example" app := fiber.New(fiber.Config{ // TrustProxy enables the trusted proxy check TrustProxy: true, // TrustProxyConfig allows for configuring trusted proxies. // Proxies is a list of trusted proxy IP ranges/addresses TrustProxyConfig: fiber.TrustProxyConfig{ Proxies: []string{"0.8.0.0", "1.1.1.1/30"}, // IP address or IP address range Loopback: true, // Trust loopback addresses (127.0.0.0/8, ::1/128) UnixSocket: true, // Trust Unix domain socket connections }, }) app.Get("/", func(c fiber.Ctx) error { // If request came from trusted proxy, return true; else return false c.IsProxyTrusted() // ... }) ``` #### IsSafe Reports whether the request method is safe, meaning it is not expected to change server state (RFC 9110, Section 9.2.1). ```go title="Signature" func (c fiber.Ctx) IsSafe() bool func (r fiber.Req) IsSafe() bool ``` ```go title="Example" app.Use(func(c fiber.Ctx) error { // GET, HEAD, OPTIONS, TRACE, QUERY => true // POST, PUT, PATCH, DELETE => false if !c.IsSafe() { return requireCSRFToken(c) } return c.Next() }) ``` #### IsWebSocket Returns `true` if the request includes a WebSocket upgrade handshake. ```go title="Signature" func (c fiber.Ctx) IsWebSocket() bool func (r fiber.Req) IsWebSocket() bool ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { if c.IsWebSocket() { // handle websocket } return c.Next() }) ``` #### MediaType Returns the MIME type from the `Content-Type` header without parameters. ```go title="Signature" func (c fiber.Ctx) MediaType() string func (r fiber.Req) MediaType() string ``` ```go title="Example" // Content-Type: application/json; charset=utf-8 app.Post("/", func(c fiber.Ctx) error { c.MediaType() // "application/json" return nil }) ``` #### Method Returns a string corresponding to the HTTP method of the request: `GET`, `POST`, `PUT`, and so on. Optionally, you can override the method by passing a string. Method tokens are case-sensitive (RFC 9110): the override is first matched exactly against the methods registered in [`Config.RequestMethods`](./fiber.md#requestmethods), and only falls back to the uppercase form as a convenience for the standard methods (e.g. `"get"` → `GET`). An unregistered override is ignored. :::caution Route registration (`app.Get`, `app.Add`, …) uppercases method names before validating them, so custom methods you want to **route** must be registered in `Config.RequestMethods` in uppercase. A mixed-case entry can be set via `c.Method(...)` but cannot have routes registered for it. ::: ```go title="Signature" func (c fiber.Ctx) Method(override ...string) string func (r fiber.Req) Method(override ...string) string ``` ```go title="Example" app.Post("/override", func(c fiber.Ctx) error { c.Method() // "POST" c.Method("GET") c.Method() // "GET" // ... }) ``` #### MultipartForm To access multipart form entries, you can parse the binary with `MultipartForm()`. This returns a `*multipart.Form`, allowing you to access form values and files. Parsing is bounded by the app [BodyLimit](./fiber.md#bodylimit). :::caution On a form request this lowercases the case-insensitive parts of the request's own `Content-Type`, so a value obtained earlier from `Get(HeaderContentType)` — which aliases those bytes unless [Immutable](./fiber.md#immutable) is set — can change during the call. Copy it first if you need it to outlive one. ::: ```go title="Signature" func (c fiber.Ctx) MultipartForm() (*multipart.Form, error) func (r fiber.Req) MultipartForm() (*multipart.Form, error) ``` ```go title="Example" app.Post("/", func(c fiber.Ctx) error { // Parse the multipart form: if form, err := c.MultipartForm(); err == nil { // => *multipart.Form if token := form.Value["token"]; len(token) > 0 { // Get key value: fmt.Println(token[0]) } // Get all files from "documents" key: files := form.File["documents"] // => []*multipart.FileHeader // Loop through files: for _, file := range files { fmt.Println(file.Filename, file.Size, file.Header["Content-Type"][0]) // => "tutorial.pdf" 360641 "application/pdf" // Save the files to disk: if err := c.SaveFile(file, fmt.Sprintf("./%s", file.Filename)); err != nil { return err } } } return nil }) ``` #### Origin Returns the `Origin` request header. :::caution Returned value is only valid within the handler. Do not store any references. Make copies or use the [**`Immutable`**](./fiber.md#config) setting to use the value outside the handler. ::: ```go title="Signature" func (c fiber.Ctx) Origin() string func (r fiber.Req) Origin() string ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { // Origin: https://example.com c.Origin() // => "https://example.com" // ... }) ``` #### OriginalURL Returns the original request URL. ```go title="Signature" func (c fiber.Ctx) OriginalURL() string func (r fiber.Req) OriginalURL() string ``` ```go title="Example" // GET http://example.com/search?q=something app.Get("/", func(c fiber.Ctx) error { c.OriginalURL() // "/search?q=something" // ... }) ``` :::info The returned value is valid only within the handler. Do not store references. Make copies or use the [**`Immutable`**](./fiber.md#immutable) setting instead. [Read more...](../#zero-allocation) ::: #### Params This method can be used to get the route parameters. You can pass an optional default value that will be returned if the param key does not exist. :::info Defaults to an empty string \(`""`\) if the param **doesn't** exist. ::: ```go title="Signature" func (c fiber.Ctx) Params(key string, defaultValue ...string) string func (r fiber.Req) Params(key string, defaultValue ...string) string ``` ```go title="Example" // GET http://example.com/user/fenny app.Get("/user/:name", func(c fiber.Ctx) error { c.Params("name") // "fenny" // ... }) // GET http://example.com/user/fenny/123 app.Get("/user/*", func(c fiber.Ctx) error { c.Params("*") // "fenny/123" c.Params("*1") // "fenny/123" // ... }) ``` Unnamed route parameters \(\*, +\) can be fetched by the **character** and the **counter** in the route. ```go title="Example" // ROUTE: /v1/*/shop/* // GET: /v1/brand/4/shop/blue/xs c.Params("*1") // "brand/4" c.Params("*2") // "blue/xs" ``` For reasons of **downward compatibility**, the first parameter segment for the parameter character can also be accessed without the counter. ```go title="Example" app.Get("/v1/*/shop/*", func(c fiber.Ctx) error { c.Params("*") // outputs the value of the first wildcard segment }) ``` :::info The returned value is valid only within the handler. Do not store references. Make copies or use the [**`Immutable`**](./fiber.md#immutable) setting instead. [Read more...](../#zero-allocation) ::: In certain scenarios, it can be useful to have an alternative approach to handle different types of parameters, not just strings. This can be achieved using a generic `Params` function known as `Params[V GenericType](c fiber.Ctx, key string, defaultValue ...V) V`. This function is capable of parsing a route parameter and returning a value of a type that is assumed and specified by `V GenericType`. ```go title="Signature" func Params[V GenericType](c fiber.Ctx, key string, defaultValue ...V) V ``` ```go title="Example" // GET http://example.com/user/114 app.Get("/user/:id", func(c fiber.Ctx) error{ fiber.Params[string](c, "id") // returns "114" as string. fiber.Params[int](c, "id") // returns 114 as integer fiber.Params[string](c, "number") // returns "" (default string type) fiber.Params[int](c, "number") // returns 0 (default integer value type) }) ``` The generic `Params` function supports returning the following data types based on `V GenericType`: - Integer: `int`, `int8`, `int16`, `int32`, `int64` - Unsigned integer: `uint`, `uint8`, `uint16`, `uint32`, `uint64` - Floating-point numbers: `float32`, `float64` - Boolean: `bool` - String: `string` - Byte array: `[]byte` #### Path Contains the path part of the request URL, normalized before routing as [RFC 3986 Section 6.2.2](https://www.rfc-editor.org/rfc/rfc3986#section-6.2.2) describes: percent-encoded unreserved characters are decoded, other escapes keep their encoding with uppercase hex digits, and `.` and `..` segments are removed, so a request for `/users/../users/%6Aohn` reports `/users/john`. A target without a leading slash is rooted: the asterisk form `OPTIONS *` reports `/*`, and an absolute-form target with an empty path, such as `GET http://host?q`, reports `/`. An encoded slash stays encoded and empty segments are kept unless [`UnescapePath`](fiber.md#config) decodes every escape. A stray `%` that begins no escape is kept as sent, except where an escape decoded after it would complete a new escape with it: that `%` is written as `%25`, so `/%%370rivate` reports `/%2570rivate`. The result is not a path to percent-decode again, since an escape it keeps, such as `%2F`, is meant to stay one (see [Routing](../guide/routing.md)). Use [OriginalURL](#originalurl) for the request target as the client sent it. Optionally, you can override the path by passing a string. For internal redirects, you might want to call [RestartRouting](ctx.md#restartrouting) instead of [Next](ctx.md#next). ```go title="Signature" func (c fiber.Ctx) Path(override ...string) string func (r fiber.Req) Path(override ...string) string ``` ```go title="Example" // GET http://example.com/users?sort=desc app.Get("/users", func(c fiber.Ctx) error { c.Path() // "/users" c.Path("/john") c.Path() // "/john" // ... }) ``` #### Port Returns the remote port of the request: the port of the connection's remote address, which is the proxy's rather than the client's when the app sits behind one. It is not the port in the `Host` header, which [`Host`](#host) returns. The result is `"0"` when there is no remote address and empty for a Unix socket. ```go title="Signature" func (c fiber.Ctx) Port() string func (r fiber.Req) Port() string ``` ```go title="Example" // The client connects from 192.168.1.10:54321 to http://example.com:8080 app.Get("/", func(c fiber.Ctx) error { c.Port() // "54321" c.Host() // "example.com:8080" // ... }) ``` #### Protocol Returns the HTTP protocol version of the request: `HTTP/1.1` or `HTTP/2`. :::info To get the request scheme (`http` or `https`), use [`Scheme`](#scheme) instead. ::: ```go title="Signature" func (c fiber.Ctx) Protocol() string func (r fiber.Req) Protocol() string ``` ```go title="Example" // GET http://example.com app.Get("/", func(c fiber.Ctx) error { c.Protocol() // "HTTP/1.1" // ... }) ``` #### Queries `Queries` is a function that returns an object containing a property for each query string parameter in the route. ```go title="Signature" func (c fiber.Ctx) Queries() map[string]string func (r fiber.Req) Queries() map[string]string ``` ```go title="Example" // GET http://example.com/?name=alex&want_pizza=false&id= app.Get("/", func(c fiber.Ctx) error { m := c.Queries() m["name"] // "alex" m["want_pizza"] // "false" m["id"] // "" // ... }) ``` ```go title="Example" // GET http://example.com/?field1=value1&field1=value2&field2=value3 app.Get("/", func (c fiber.Ctx) error { m := c.Queries() m["field1"] // "value2" m["field2"] // "value3" }) ``` ```go title="Example" // GET http://example.com/?list_a=1&list_a=2&list_a=3&list_b[]=1&list_b[]=2&list_b[]=3&list_c=1,2,3 app.Get("/", func(c fiber.Ctx) error { m := c.Queries() m["list_a"] // "3" m["list_b[]"] // "3" m["list_c"] // "1,2,3" }) ``` ```go title="Example" // GET /api/posts?filters.author.name=John&filters.category.name=Technology app.Get("/", func(c fiber.Ctx) error { m := c.Queries() m["filters.author.name"] // John m["filters.category.name"] // Technology }) ``` ```go title="Example" // GET /api/posts?tags=apple,orange,banana&filters[tags]=apple,orange,banana&filters[category][name]=fruits&filters.tags=apple,orange,banana&filters.category.name=fruits app.Get("/", func(c fiber.Ctx) error { m := c.Queries() m["tags"] // apple,orange,banana m["filters[tags]"] // apple,orange,banana m["filters[category][name]"] // fruits m["filters.tags"] // apple,orange,banana m["filters.category.name"] // fruits }) ``` #### Query This method returns a string corresponding to a query string parameter by name. You can pass an optional default value that will be returned if the query key does not exist. :::info If there is **no** query string, it returns an **empty string**. ::: ```go title="Signature" func (c fiber.Ctx) Query(key string, defaultValue ...string) string func (r fiber.Req) Query(key string, defaultValue ...string) string ``` ```go title="Example" // GET http://example.com/?order=desc&brand=nike app.Get("/", func(c fiber.Ctx) error { c.Query("order") // "desc" c.Query("brand") // "nike" c.Query("empty", "nike") // "nike" // ... }) ``` :::info The returned value is valid only within the handler. Do not store references. Make copies or use the [**`Immutable`**](./fiber.md#immutable) setting instead. [Read more...](../#zero-allocation) ::: In certain scenarios, it can be useful to have an alternative approach to handle different types of query parameters, not just strings. This can be achieved using a generic `Query` function known as `Query[V GenericType](c fiber.Ctx, key string, defaultValue ...V) V`. This function is capable of parsing a query string and returning a value of a type that is assumed and specified by `V GenericType`. Here is the signature for the generic `Query` function: ```go title="Signature" func Query[V GenericType](c fiber.Ctx, key string, defaultValue ...V) V ``` ```go title="Example" // GET http://example.com/?page=1&brand=nike&new=true app.Get("/", func(c fiber.Ctx) error { fiber.Query[int](c, "page") // 1 fiber.Query[string](c, "brand") // "nike" fiber.Query[bool](c, "new") // true // ... }) ``` In this case, `Query[V GenericType](c Ctx, key string, defaultValue ...V) V` can retrieve `page` as an integer, `brand` as a string, and `new` as a boolean. The function uses the appropriate parsing function for each specified type to ensure the correct type is returned. This simplifies the retrieval process of different types of query parameters, making your controller actions cleaner. The generic `Query` function supports returning the following data types based on `V GenericType`: - Integer: `int`, `int8`, `int16`, `int32`, `int64` - Unsigned integer: `uint`, `uint8`, `uint16`, `uint32`, `uint64` - Floating-point numbers: `float32`, `float64` - Boolean: `bool` - String: `string` - Byte array: `[]byte` #### Range Returns a struct containing the type and a slice of ranges. Each `RangeSet` holds an inclusive `Start` and `End` position, with open-ended and suffix ranges resolved against `size` and `End` limited to the last byte. Only the `bytes` unit is recognized (its name is case-insensitive) and any optional whitespace around range specifiers will be ignored, as specified in RFC 9110. Empty list elements (e.g. `bytes=,0-5`) are ignored, though they still count toward [`Config.MaxRanges`](./fiber.md#maxranges). A range with a non-numeric bound or a last position smaller than the first position invalidates the whole header and `ErrRangeMalformed` (carrying a **400 Bad Request** status) is returned, per RFC 9110. So is a request without a `Range` header, or one with no `=` in it, so check [`HasHeader`](#hasheader) first when the header is optional. A range unit other than `bytes` (e.g. `pages=1-3`) returns `ErrRangeUnsupported`; RFC 9110 requires servers to **ignore** such a Range header, so treat this error as "serve the full representation", not as a failure. As a safety net the error carries a **400 Bad Request** status so that blindly propagating it does not surface as a 500. More list elements than `Config.MaxRanges` (16 by default) sets the HTTP status code to **416 Range Not Satisfiable**, sets `Content-Range: bytes */` and returns `ErrRangeTooLarge`. If the requested ranges are valid but none of them are satisfiable, the method automatically sets the HTTP status code to **416 Range Not Satisfiable** and populates the `Content-Range` header with the current representation size, and returns `ErrRequestedRangeNotSatisfiable` together with the parsed `Range`. ```go title="Signature" func (c fiber.Ctx) Range(size int64) (Range, error) func (r fiber.Req) Range(size int64) (Range, error) ``` ```go title="Example" // Range: bytes=500-700, 700-900 app.Get("/", func(c fiber.Ctx) error { r, err := c.Range(1000) if err != nil { return err } if r.Type == "bytes" { for _, rng := range r.Ranges { fmt.Println(rng) // {500 700} // {700 900} } } return nil }) ``` #### Referer Returns the `Referer` request header. ```go title="Signature" func (c fiber.Ctx) Referer() string func (r fiber.Req) Referer() string ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { c.Referer() // "https://example.com" return nil }) ``` #### RequestID Returns the request identifier from the `X-Request-ID` header: the response header when one has been set, for example by the [requestid](../middleware/requestid.md) middleware, otherwise the request header. It returns an empty string when neither is present. To get a number that needs no header, use [`ID`](#id). ```go title="Signature" func (c fiber.Ctx) RequestID() string ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { c.RequestID() // "8d7ad5e3-aaf3-450b-a241-2beb887efd54" return nil }) ``` #### SaveFile Method is used to save **any** multipart file to disk. ```go title="Signature" func (c fiber.Ctx) SaveFile(fileheader *multipart.FileHeader, path string) error ``` ```go title="Example" app.Post("/", func(c fiber.Ctx) error { // Parse the multipart form: if form, err := c.MultipartForm(); err == nil { // => *multipart.Form // Get all files from "documents" key: files := form.File["documents"] // => []*multipart.FileHeader // Loop through files: for _, file := range files { fmt.Println(file.Filename, file.Size, file.Header["Content-Type"][0]) // => "tutorial.pdf" 360641 "application/pdf" // Save the files to disk: if err := c.SaveFile(file, fmt.Sprintf("./%s", file.Filename)); err != nil { return err } } return err } }) ``` #### SaveFileToStorage Method is used to save **any** multipart file to an external storage system. The file is read into memory before it is stored, so a file larger than the app [BodyLimit](./fiber.md#bodylimit) is refused with an error that wraps `fasthttp.ErrBodyTooLarge`. ```go title="Signature" func (c fiber.Ctx) SaveFileToStorage(fileheader *multipart.FileHeader, path string, storage Storage) error ``` ```go title="Example" storage := memory.New() app.Post("/", func(c fiber.Ctx) error { // Parse the multipart form: if form, err := c.MultipartForm(); err == nil { // => *multipart.Form // Get all files from "documents" key: files := form.File["documents"] // => []*multipart.FileHeader // Loop through files: for _, file := range files { fmt.Println(file.Filename, file.Size, file.Header["Content-Type"][0]) // => "tutorial.pdf" 360641 "application/pdf" // Save the files to storage: if err := c.SaveFileToStorage(file, fmt.Sprintf("./%s", file.Filename), storage); err != nil { return err } } return err } }) ``` #### Scheme Contains the request protocol string: `http` or `https` for TLS requests. :::info Please use [`Config.TrustProxy`](fiber.md#trustproxy) to prevent header spoofing if your app is behind a proxy. ::: :::note Only `http` and `https` are ever returned. When the proxy is trusted, the forwarding headers (`X-Forwarded-Proto`, `X-Forwarded-Protocol`, `X-Forwarded-Ssl`, `X-Url-Scheme`) are read, but a value naming anything else is ignored rather than passed through — the result is spliced into [`BaseURL`](#baseurl) and compared for origin equality by CSRF and `Redirect().Back()`, so a header announcing, say, `javascript` must not become part of a URL. The first value of a comma-separated `X-Forwarded-Proto` or `X-Forwarded-Protocol` list is the one used. A proxy that terminates a different protocol should send the scheme the client used to reach it. ::: ```go title="Signature" func (c fiber.Ctx) Scheme() string func (r fiber.Req) Scheme() string ``` ```go title="Example" // GET http://example.com app.Get("/", func(c fiber.Ctx) error { c.Scheme() // "http" // ... }) ``` #### Secure A boolean property that is `true` if a **TLS** connection is established, or if a trusted proxy reports `https` (see [`Scheme`](#scheme)). ```go title="Signature" func (c fiber.Ctx) Secure() bool func (r fiber.Req) Secure() bool ``` ```go title="Example" // Secure() method is equivalent to: c.Scheme() == "https" ``` #### Stale When the client's cached response is **stale**, this method returns **true**. It is the logical complement of [`Fresh`](#fresh), which checks whether the cached representation is still valid. [https://expressjs.com/en/4x/api.html#req.stale](https://expressjs.com/en/4x/api.html#req.stale) ```go title="Signature" func (c fiber.Ctx) Stale() bool func (r fiber.Req) Stale() bool ``` #### Subdomains Returns a slice with the host’s sub-domain labels. The dot-separated parts that precede the registrable domain (`example`) and the top-level domain (ex: `com`). The `subdomain offset` (default `2`) tells Fiber how many labels, counting from the right-hand side, are always discarded. Passing an `offset` argument lets you override that value for a single call. The host is taken without its port and lower-cased, a trailing dot is ignored, and punycode labels (`xn--`) are returned decoded. An IP address has no subdomains, so it returns `[]`. ```go title="Signature" func (c fiber.Ctx) Subdomains(offset ...int) []string func (r fiber.Req) Subdomains(offset ...int) []string ``` | `offset` | Result | Meaning | | ---------------------- | --------------------------------------- | --------------------------------------------- | | *omitted* → **2** | trim 2 right-most labels | drop the registrable domain **and** the TLD | | `1` to `len(labels)-1` | trim exactly `offset` right-most labels | custom trimming of available labels | | `>= len(labels)` | **return `[]`** | offset exceeds available labels → empty slice | | `0` | **return every label** | keep the entire host unchanged | | `< 0` | **return `[]`** | negative offsets are invalid → empty slice | ##### Example ```go // Host: "tobi.ferrets.example.com" app.Get("/", func(c fiber.Ctx) error { c.Subdomains() // ["tobi", "ferrets"] c.Subdomains(1) // ["tobi", "ferrets", "example"] c.Subdomains(0) // ["tobi", "ferrets", "example", "com"] c.Subdomains(-1) // [] // ... }) ``` #### URI Returns the parsed [`*fasthttp.URI`](https://pkg.go.dev/github.com/valyala/fasthttp#URI) of the request, which gives access to every fasthttp URI method. :::caution The returned value is owned by the request and is rewritten by a [`Path`](#path) override, so it is only valid within the handler. Do not store any references. ::: ```go title="Signature" func (c fiber.Ctx) URI() *fasthttp.URI func (r fiber.Req) URI() *fasthttp.URI ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { // GET http://example.com/search?q=fiber#results uri := c.URI() uri.QueryString() // => "q=fiber" uri.Hash() // => "results" // ... }) ``` #### UserAgent Returns the `User-Agent` request header. ```go title="Signature" func (c fiber.Ctx) UserAgent() string func (r fiber.Req) UserAgent() string ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { c.UserAgent() // "Mozilla/5.0 ..." return nil }) ``` #### XHR A boolean property that is `true` if the request’s [X-Requested-With](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers) header field is [XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest), indicating that the request was issued by a client library (such as [jQuery](https://api.jquery.com/jQuery.ajax/)). ```go title="Signature" func (c fiber.Ctx) XHR() bool func (r fiber.Req) XHR() bool ``` ```go title="Example" // X-Requested-With: XMLHttpRequest app.Get("/", func(c fiber.Ctx) error { c.XHR() // true // ... }) ``` ### Response Methods which modify the response object. :::tip Use `c.Res()` to limit gopls suggestions to only these methods! ::: Each entry lists both forms it can be called in. `DefaultCtx` embeds `DefaultRes`, so the method is promoted: `c.Del(key)` and `c.Res().Del(key)` are the same call. `Res` also carries [`App`](#app), [`GetRouteURL`](#getrouteurl), [`OriginalURL`](#originalurl), [`Redirect`](#redirect), [`RequestCtx`](#requestctx), [`Response`](#response) and [`ViewBind`](#viewbind), which are documented under their `Ctx` signatures, and the header getters `Get` and `GetHeaders`, documented as [`GetRespHeader`](#getrespheader) and [`GetRespHeaders`](#getrespheaders). :::caution Five names mean different things on `Req` and `Res`: `Body`, `ContentLength`, `ContentType`, [`Get`](#get) and [`GetHeaders`](#getheaders). On `Ctx` the **request wins** for all five, so `c.Body()` reads the request body. The response counterparts of the last two are [`GetRespHeader`](#getrespheader) and [`GetRespHeaders`](#getrespheaders); the others are listed below as [`Body (Res)`](#body-res), [`ContentLength (Res)`](#contentlength-res) and [`ContentType (Res)`](#contenttype-res), and are reachable only through `c.Res()` — which is why they list a single `fiber.Res` signature. The response cookies are [`GetCookies`](#getcookies): named apart from `Req.Cookies` so that a `Ctx` still satisfies `fiber.Res`, which a same-name/different-signature pair would prevent. ::: #### Add Appends the given value to the response header field as a **new field line**, leaving any existing lines untouched. This differs from [`Append`](#append), which folds values into a single comma-separated line: headers whose values may themselves contain commas — `WWW-Authenticate`, `Link` — have to be sent as separate lines to stay unambiguous (RFC 9110, Section 5.3). :::caution `Add` does not append for most of the headers fasthttp stores in a slot of their own: `Content-Type`, `Content-Encoding`, `Content-Length`, `Connection` and `Server` are **replaced**, and `Transfer-Encoding` and `Date` are **ignored** because fasthttp writes them itself. Use [`Set`](#set) for those. `Set-Cookie` is slotted too but does repeat, so `Add` appends a line — prefer [`Cookie`](#cookie), which builds the field value for you. `Trailer` accumulates as well: each `Add` extends the set of announced trailer fields. ::: ```go title="Signature" func (c fiber.Ctx) Add(key, val string) func (r fiber.Res) Add(key, val string) ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { c.Add(fiber.HeaderWWWAuthenticate, `Basic realm="api"`) c.Add(fiber.HeaderWWWAuthenticate, `Bearer realm="api"`) // => WWW-Authenticate: Basic realm="api" // => WWW-Authenticate: Bearer realm="api" return c.SendStatus(fiber.StatusUnauthorized) }) ``` #### Append Appends the specified **value** to the HTTP response header field. :::caution If the header is **not** already set, it creates the header with the specified value. ::: Empty values are skipped, since a sender must not generate empty list elements (RFC 9110). Members are compared byte-exactly. Some lists (`Link`, `Cache-Control`) are not all field names, so case folding would be the wrong default. For `Vary` field names, use [`Vary()`](#vary), which compares case-insensitively. ```go title="Signature" func (c fiber.Ctx) Append(field string, values ...string) func (r fiber.Res) Append(field string, values ...string) ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { c.Append("Link", "http://google.com", "http://localhost") // => Link: http://google.com, http://localhost c.Append("Link", "Test") // => Link: http://google.com, http://localhost, Test // ... }) ``` #### Attachment Sets the HTTP response [Content-Disposition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) header field to `attachment`. ```go title="Signature" func (c fiber.Ctx) Attachment(filename ...string) func (r fiber.Res) Attachment(filename ...string) ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { c.Attachment() // => Content-Disposition: attachment c.Attachment("./upload/images/logo.png") // => Content-Disposition: attachment; filename="logo.png" // => Content-Type: image/png // ... }) ``` With a filename, the `Content-Type` is set from its extension the way [`Type`](#type) does it, charset included. The `filename` parameter is emitted as an RFC 9110 quoted-string: spaces and punctuation stay literal, and quotes/backslashes are escaped with a backslash (no URL encoding). Non-ASCII filenames additionally carry the `filename*` parameter as defined in [RFC 6266](https://www.rfc-editor.org/rfc/rfc6266) and [RFC 8187](https://www.rfc-editor.org/rfc/rfc8187): ```go title="Example" app.Get("/non-ascii", func(c fiber.Ctx) error { c.Attachment("./files/文件.txt") // => Content-Disposition: attachment; filename="文件.txt"; filename*=UTF-8''%E6%96%87%E4%BB%B6.txt return nil }) ``` #### AutoFormat Performs content-negotiation on the [Accept](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept) HTTP header. It uses [Accepts](ctx.md#accepts) to select a proper format. The supported content types are `text/html`, `text/plain`, `application/json`, `application/vnd.msgpack`, `application/xml`, and `application/cbor`. For `text/html` the body is converted to text, HTML-escaped and wrapped in a `

` element. `application/vnd.msgpack` and `application/cbor` need an encoder in the app config ([`MsgPackEncoder`](./fiber.md#msgpackencoder), [`CBOREncoder`](./fiber.md#cborencoder)); without one, `AutoFormat` returns an error when the client asks for them. Because the representation is selected from the Accept header, `Vary: Accept` is added to the response. For more flexible content negotiation, use [Format](ctx.md#format). :::info If the header is **not** specified or there is **no** proper format, **text/plain** is used. ::: ```go title="Signature" func (c fiber.Ctx) AutoFormat(body any) error func (r fiber.Res) AutoFormat(body any) error ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { // Accept: text/plain c.AutoFormat("Hello, World!") // => Hello, World! // Accept: text/html c.AutoFormat("Hello, World!") // =>

Hello, World!

type User struct { Name string } user := User{"John Doe"} // Accept: application/json c.AutoFormat(user) // => {"Name":"John Doe"} // Accept: application/vnd.msgpack c.AutoFormat(user) // => 81 a4 4e 61 6d 65 a8 4a 6f 68 6e 20 44 6f 65 // Accept: application/cbor c.AutoFormat(user) // => a1 64 4e 61 6d 65 68 4a 6f 68 6e 20 44 6f 65 // Accept: application/xml c.AutoFormat(user) // => John Doe // .. }) ``` #### Body (Res) Returns the response body buffered so far, which lets middleware inspect or checksum what a handler produced before it is written out. Reached through `c.Res()`: `Req` and `Res` both carry a `Body`, so `c.Body()` is the **request** body. :::note A streamed body returns `nil` rather than being drained. Reading it would pull the whole stream into memory and turn the response into a buffered one, which would hang an SSE route and defeat a large [`SendFile`](#sendfile). Use [`Written`](#written) to tell a streaming response from one that produced nothing, and the underlying `c.Response().Body()` if you really do want to materialize the stream. ::: :::caution The returned slice is the response's live buffer, not a copy — writing through it writes to the response, as it does for the request-side [`Body`](#body). It is only valid within the handler and is invalidated by the next write to the response. Do not store any references; copy it instead. ::: ```go title="Signature" func (r fiber.Res) Body() []byte ``` ```go title="Example" app.Use(func(c fiber.Ctx) error { if err := c.Next(); err != nil { return err } c.Set(fiber.HeaderETag, string(etag.Generate(c.Res().Body()))) return nil }) ``` #### CBOR CBOR converts any interface or string to CBOR encoded bytes. > **Note:** Before using any CBOR-related features, make sure to follow the [CBOR setup instructions](../guide/advance-format.md#cbor). :::info CBOR also sets the content header to the `ctype` parameter. If no `ctype` is passed in, the header is set to `application/cbor`. ::: ```go title="Signature" func (c fiber.Ctx) CBOR(data any, ctype ...string) error func (r fiber.Res) CBOR(data any, ctype ...string) error ``` ```go title="Example" type SomeStruct struct { Name string `cbor:"name"` Age uint8 `cbor:"age"` } app.Get("/cbor", func(c fiber.Ctx) error { // Create data struct: data := SomeStruct{ Name: "Grame", Age: 20, } return c.CBOR(data) // => Content-Type: application/cbor // => \xa2dnameeGramecage\x14 return c.CBOR(fiber.Map{ "name": "Grame", "age": 20, }) // => Content-Type: application/cbor // => \xa2dnameeGramecage\x14 return c.CBOR(fiber.Map{ "type": "https://example.com/probs/out-of-credit", "title": "You do not have enough credit.", "status": 403, "detail": "Your current balance is 30, but that costs 50.", "instance": "/account/12345/msgs/abc", }) // => Content-Type: application/cbor // => \xa5dtypex'https://example.com/probs/out-of-creditetitlex\x1eYou do not have enough credit.fstatus\x19\x01\x93fdetailx.Your current balance is 30, but that costs 50.hinstancew/account/12345/msgs/abc }) ``` #### ClearCookie Expires a client cookie (or all cookies if left empty). ```go title="Signature" func (c fiber.Ctx) ClearCookie(key ...string) func (r fiber.Res) ClearCookie(key ...string) ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { // Clears all cookies: c.ClearCookie() // Expire specific cookie by name: c.ClearCookie("user") // Expire multiple cookies by names: c.ClearCookie("token", "session", "track_id", "version") // ... }) ``` :::caution Web browsers and other compliant clients will only clear the cookie if the given options are identical to those when creating the cookie, excluding `Expires` and `MaxAge`. `ClearCookie` will not set these values for you - a technique similar to the one shown below should be used to ensure your cookie is deleted. ::: ```go title="Example" app.Get("/set", func(c fiber.Ctx) error { c.Cookie(&fiber.Cookie{ Name: "token", Value: "randomvalue", Expires: time.Now().Add(24 * time.Hour), HTTPOnly: true, SameSite: "Lax", }) // ... }) app.Get("/delete", func(c fiber.Ctx) error { c.Cookie(&fiber.Cookie{ Name: "token", Expires: fasthttp.CookieExpireDelete, // Use fasthttp's built-in constant HTTPOnly: true, SameSite: "Lax", }) // ... }) ``` You can also use `c.Cookie()` to expire cookies with specific `Path` or `Domain` attributes: ```go title="Example" app.Get("/logout", func(c fiber.Ctx) error { // Expire a cookie with path and domain c.Cookie(&fiber.Cookie{ Name: "token", Path: "/api", Domain: "example.com", Expires: fasthttp.CookieExpireDelete, }) return c.SendStatus(fiber.StatusOK) }) ``` #### ContentLength (Res) Returns the value of the `Content-Length` response header. Reached through `c.Res()`: `Req` and `Res` both carry a `ContentLength`, so `c.ContentLength()` is the **request** header. :::note It reports what the header **declares** — a length a handler, middleware or upstream response set, and `-1` once the body is a stream of unknown length. It is not a count of buffered bytes: fasthttp fills `Content-Length` in as it serializes the response, so this is `0` inside a handler unless something set it explicitly. Use `len(c.Res().Body())` for the bytes buffered so far. ::: ```go title="Signature" func (r fiber.Res) ContentLength() int ``` ```go title="Example" app.Use(func(c fiber.Ctx) error { if err := c.Next(); err != nil { return err } log.Printf("declared %d bytes, buffered %d", c.Res().ContentLength(), len(c.Res().Body())) return nil }) ``` #### ContentType (Res) Returns the `Content-Type` response header, parameters included. It is the read side of [`Type`](#type), and of the content type Fiber sets for you when a `JSON`, `XML`, or `SendFile` response goes out. :::note When nothing has set one it reports what would be sent: fasthttp's default, `text/plain; charset=utf-8`, or an empty string under [`DisableDefaultContentType`](./fiber.md#config). `Req` and `Res` both carry a `ContentType`, so on `Ctx` the request wins, as it does for [`Get`](#get). This section is the response side, reached through `c.Res()`. ::: ```go title="Signature" func (r fiber.Res) ContentType() string ``` ```go title="Example" app.Use(func(c fiber.Ctx) error { if err := c.Next(); err != nil { return err } if strings.HasPrefix(c.Res().ContentType(), fiber.MIMEApplicationJSON) { c.Set("X-Content-Type-Options", "nosniff") } return nil }) ``` #### Cookie Sets a cookie. ```go title="Signature" func (c fiber.Ctx) Cookie(cookie *Cookie) func (r fiber.Res) Cookie(cookie *Cookie) ``` ```go type Cookie struct { Name string `json:"name"` // The name of the cookie Value string `json:"value"` // The value of the cookie Path string `json:"path"` // Specifies a URL path which is allowed to receive the cookie Domain string `json:"domain"` // Specifies the domain which is allowed to receive the cookie MaxAge int `json:"max_age"` // The maximum age (in seconds) of the cookie Expires time.Time `json:"expires"` // The expiration date of the cookie Secure bool `json:"secure"` // Indicates that the cookie should only be transmitted over a secure HTTPS connection HTTPOnly bool `json:"http_only"` // Indicates that the cookie is accessible only through the HTTP protocol SameSite string `json:"same_site"` // Controls whether or not a cookie is sent with cross-site requests Partitioned bool `json:"partitioned"` // Indicates if the cookie is stored in a partitioned cookie jar SessionOnly bool `json:"session_only"` // Indicates if the cookie is a session-only cookie } ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { // Create cookie cookie := new(fiber.Cookie) cookie.Name = "john" cookie.Value = "doe" cookie.Expires = time.Now().Add(24 * time.Hour) // Set cookie c.Cookie(cookie) // ... }) ``` :::info Defaults and reuse `Cookie` treats its argument as read-only. The defaults below are applied to a copy, so one `*fiber.Cookie` can be kept as a template and passed on every request. - An empty `Path` is sent as `/`. - `SessionOnly` drops `MaxAge` and `Expires`. Otherwise a positive `MaxAge` sends `Max-Age`, `0` sends none, and a negative value sends `Max-Age=0`, which deletes the cookie. - `SameSite` is matched case-insensitively: `Lax`, `Strict` and `None` set the attribute, `disabled` omits it, and an empty or unrecognized value is sent as `Lax`. ::: :::caution Cookie values The grammar of [RFC 6265 §4.1.1](https://www.rfc-editor.org/rfc/rfc6265#section-4.1.1) allows visible ASCII in a cookie value, without the double quote, the comma, the semicolon, the backslash and whitespace. A `Value` that holds a character outside the grammar is not altered: the cookie is not set, as with an invalid name or `Domain`. The exceptions are the space and the comma, which many applications use: a value with either is written inside double quotes, as `net/http` does (`Value: "hello world"` is sent as `n="hello world"`), and so is a value that already starts and ends with a double quote. The quotes frame the value and are not part of it, so `GetCookie` and the `Cookies` of the request that carries the cookie back return `hello world`. Quoted spaces and commas are widely accepted but are not strictly what the grammar allows. A strict parser can drop them, so percent-encode a value that has to travel through one, for example with `url.QueryEscape`, and decode it when you read it. ::: :::info When setting a cookie with `SameSite=None`, Fiber automatically sets `Secure=true` as required by RFC 6265bis and modern browsers. This ensures compliance with the "None" SameSite policy which mandates that cookies must be sent over secure connections. For more information, see: - [Mozilla Documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#none) - [Chrome Documentation](https://developers.google.com/search/blog/2020/01/get-ready-for-new-samesitenone-secure) ::: :::info Partitioned cookies allow partitioning the cookie jar by top-level site, enhancing user privacy by preventing cookies from being shared across different sites. This feature is particularly useful in scenarios where a user interacts with embedded third-party services that should not have access to the main site's cookies. You can check out [CHIPS](https://developers.google.com/privacy-sandbox/3pcd/chips) for more information. ::: ```go title="Example" app.Get("/", func(c fiber.Ctx) error { // Create a new partitioned cookie cookie := new(fiber.Cookie) cookie.Name = "user_session" cookie.Value = "abc123" cookie.Partitioned = true // This cookie will be stored in a separate jar when it's embedded into another website // Set the cookie in the response c.Cookie(cookie) return c.SendString("Partitioned cookie set") }) ``` #### Del Removes every field line of the response header specified by `key`. Field names are case-insensitive. Deleting a header that was never set is a no-op. :::caution `Del(fiber.HeaderSetCookie)` withdraws every cookie this response was going to set, which is not what [`ClearCookie`](#clearcookie) does: `ClearCookie` adds a `Set-Cookie` that expires the cookie already in the client's jar. ::: ```go title="Signature" func (c fiber.Ctx) Del(key string) func (r fiber.Res) Del(key string) ``` ```go title="Example" app.Use(func(c fiber.Ctx) error { if err := c.Next(); err != nil { return err } c.Del("X-Powered-By") return nil }) ``` #### Download Transfers the file from the given path as an `attachment`. Typically, browsers will prompt the user to download. By default, the [Content-Disposition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) header `filename=` parameter is the base name of the file path (this typically appears in the browser dialog). Override this default with the `filename` parameter; only its base name is used as well. The file is served through [`SendFile`](#sendfile), so the same rules apply to its name: it is taken literally, and a name built from request input has to be confined by the caller. ```go title="Signature" func (c fiber.Ctx) Download(file string, filename ...string) error func (r fiber.Res) Download(file string, filename ...string) error ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { return c.Download("./files/report-12345.pdf") // => Download report-12345.pdf return c.Download("./files/report-12345.pdf", "report.pdf") // => Download report.pdf }) ``` The `filename` parameter is emitted as an RFC 9110 quoted-string (no URL encoding). For filenames containing non-ASCII characters, a `filename*` parameter is added according to [RFC 6266](https://www.rfc-editor.org/rfc/rfc6266) and [RFC 8187](https://www.rfc-editor.org/rfc/rfc8187): ```go title="Example" app.Get("/non-ascii", func(c fiber.Ctx) error { return c.Download("./files/文件.txt") // => Content-Disposition: attachment; filename="文件.txt"; filename*=UTF-8''%E6%96%87%E4%BB%B6.txt }) ``` #### Drop Terminates the client connection silently without sending any HTTP headers or response body. This can be used for scenarios where you want to block certain requests without notifying the client, such as mitigating DDoS attacks or protecting sensitive endpoints from unauthorized access. ```go title="Signature" func (c fiber.Ctx) Drop() error func (r fiber.Res) Drop() error ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { if c.IP() == "192.168.1.1" { return c.Drop() } return c.SendString("Hello World!") }) ``` #### End End immediately flushes the current response and closes the underlying connection. ```go title="Signature" func (c fiber.Ctx) End() error func (r fiber.Res) End() error ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { c.SendString("Hello World!") return c.End() }) ``` :::caution Calling `c.End()` will disallow further writes to the underlying connection. ::: :::warning `c.End()` does **not** work in streaming mode (e.g. when using `fasthttp`'s `HijackConn` or `SendStream`). In streaming mode the connection is managed asynchronously and `ctx.Conn()` may return `nil`, so `c.End()` will return `nil` without flushing or closing the connection. ::: End can be used to stop a middleware from modifying a response of a handler/other middleware down the method chain when they regain control after calling `c.Next()`. ```go title="Example" // Error Logging/Responding middleware app.Use(func(c fiber.Ctx) error { err := c.Next() // Log errors & write the error to the response if err != nil { log.Printf("Got error in middleware: %v", err) return c.SendString(fmt.Sprintf("(got error %v)", err)) } // No errors occurred return nil }) // Handler with simulated error app.Get("/", func(c fiber.Ctx) error { // Closes the connection instantly after writing from this handler // and disallow further modification of its response defer c.End() c.SendString("Hello, ... I forgot what comes next!") return errors.New("some error") }) ``` #### Format Performs content-negotiation on the [Accept](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept) HTTP header. It uses [Accepts](ctx.md#accepts) to select a proper format from the supplied offers, each a `fiber.ResFmt` that pairs a `MediaType` with the `Handler` that writes the response. A default handler can be provided by setting the `MediaType` to `"default"`. If no offers match and no default is provided, a 406 (Not Acceptable) response is sent. The Content-Type is automatically set when a handler is selected. Because the representation is selected from the Accept header, `Vary: Accept` is added to the response, and with a custom `Ctx` the handlers receive that custom type, as route handlers do. Calling `Format` with no handlers returns `fiber.ErrNoHandlers`, and a `nil` `Handler` returns an error rather than panicking. :::info If the Accept header is **not** specified, the first handler with a real media type will be used (entries with the `"default"` media type are skipped, since `"default"` is not a valid Content-Type value). If only a `"default"` handler is supplied, it is called without setting the Content-Type. ::: ```go title="Signature" func (c fiber.Ctx) Format(handlers ...ResFmt) error func (r fiber.Res) Format(handlers ...ResFmt) error ``` ```go title="Example" // Accept: application/json => {"command":"eat","subject":"fruit"} // Accept: text/plain => Eat Fruit! // Accept: application/xml => Not Acceptable app.Get("/no-default", func(c fiber.Ctx) error { return c.Format( fiber.ResFmt{MediaType: "application/json", Handler: func(c fiber.Ctx) error { return c.JSON(fiber.Map{ "command": "eat", "subject": "fruit", }) }}, fiber.ResFmt{MediaType: "text/plain", Handler: func(c fiber.Ctx) error { return c.SendString("Eat Fruit!") }}, ) }) // Accept: application/json => {"command":"eat","subject":"fruit"} // Accept: text/plain => Eat Fruit! // Accept: application/xml => Eat Fruit! app.Get("/default", func(c fiber.Ctx) error { textHandler := func(c fiber.Ctx) error { return c.SendString("Eat Fruit!") } handlers := []fiber.ResFmt{ {MediaType: "application/json", Handler: func(c fiber.Ctx) error { return c.JSON(fiber.Map{ "command": "eat", "subject": "fruit", }) }}, {MediaType: "text/plain", Handler: textHandler}, {MediaType: "default", Handler: textHandler}, } return c.Format(handlers...) }) ``` #### GetCookie Reads back a cookie this response is already set to send, so a later handler or middleware can inspect or re-emit what an earlier one wrote. The second result is `false` when no cookie of that name has been set, or when its `Set-Cookie` field value does not parse. Cookie names are case-sensitive (RFC 6265, Section 4.1.1). A name written more than once resolves to the **first** occurrence; use [`GetCookies`](#getcookies) to see them all. :::note The returned `Cookie` is a copy: changing it does not change the response. Pass it to [`Cookie`](#cookie) to write the change back. A cookie that expires immediately (`Max-Age=0`) comes back with a `MaxAge` of `-1`, which `Cookie` writes as `Max-Age=0` again. ::: :::caution A `Set-Cookie` that carried no `Path` comes back with `Path` empty, and [`Cookie`](#cookie) writes an empty `Path` as `/`. Re-emitting such a cookie therefore widens it from the browser's RFC 6265 default-path — the directory of the request URI — to the whole origin. Set `Path` explicitly before writing one back. Cookies written through [`Cookie`](#cookie) always carry a `Path`, so this only arises for a `Set-Cookie` added with [`Add`](#add) or copied from an upstream response. ::: ```go title="Signature" func (c fiber.Ctx) GetCookie(name string) (*fiber.Cookie, bool) func (r fiber.Res) GetCookie(name string) (*fiber.Cookie, bool) ``` ```go title="Example" app.Use(func(c fiber.Ctx) error { if err := c.Next(); err != nil { return err } // Encrypt a session cookie an inner handler set, keeping its attributes. if cookie, ok := c.GetCookie("session"); ok { cookie.Value = encrypt(cookie.Value) c.Cookie(cookie) } return nil }) ``` #### GetCookies Returns a copy of every cookie this response is set to send, in the order they were added. It returns `nil` when none have been set. Repeated names are kept apart, so a response that sets one name at two paths yields both. A `Set-Cookie` whose field value does not parse is skipped. Named to sit beside [`GetCookie`](#getcookie) rather than mirroring `Req.Cookies`: the same name under a different signature would stop `fiber.Ctx` satisfying `fiber.Res`. `c.Cookies(key)` reads what the **client** sent. ```go title="Signature" func (c fiber.Ctx) GetCookies() []*fiber.Cookie func (r fiber.Res) GetCookies() []*fiber.Cookie ``` ```go title="Example" app.Use(func(c fiber.Ctx) error { if err := c.Next(); err != nil { return err } for _, cookie := range c.Res().GetCookies() { log.Printf("setting %s on %s", cookie.Name, cookie.Path) } return nil }) ``` #### JSON Converts any **interface** or **string** to JSON using the [encoding/json](https://pkg.go.dev/encoding/json) package. :::info JSON also sets the content header to the `ctype` parameter. If no `ctype` is passed in, the header is set to `application/json; charset=utf-8` by default. ::: ```go title="Signature" func (c fiber.Ctx) JSON(data any, ctype ...string) error func (r fiber.Res) JSON(data any, ctype ...string) error ``` ```go title="Example" type SomeStruct struct { Name string Age uint8 } app.Get("/json", func(c fiber.Ctx) error { // Create data struct: data := SomeStruct{ Name: "Grame", Age: 20, } return c.JSON(data) // => Content-Type: application/json; charset=utf-8 // => {"Name": "Grame", "Age": 20} return c.JSON(fiber.Map{ "name": "Grame", "age": 20, }) // => Content-Type: application/json; charset=utf-8 // => {"name": "Grame", "age": 20} return c.JSON(fiber.Map{ "type": "https://example.com/probs/out-of-credit", "title": "You do not have enough credit.", "status": 403, "detail": "Your current balance is 30, but that costs 50.", "instance": "/account/12345/msgs/abc", }, "application/problem+json") // => Content-Type: application/problem+json // => "{ // => "type": "https://example.com/probs/out-of-credit", // => "title": "You do not have enough credit.", // => "status": 403, // => "detail": "Your current balance is 30, but that costs 50.", // => "instance": "/account/12345/msgs/abc", // => }" }) ``` #### JSONP Sends a JSON response with JSONP support. This method is identical to [JSON](ctx.md#json), except that it opts-in to JSONP callback support. By default, the callback name is simply `callback`. Override this by passing a **named string** in the method. Unlike `JSON`, the response is sent with `Content-Type: text/javascript; charset=utf-8` and `X-Content-Type-Options: nosniff`. The callback name is reduced to a JavaScript member expression: every character outside `[A-Za-z0-9_$.[]]` is dropped, so names like `window.cb`, `ns.cb[0]`, and `$.jsonp_1` pass through unchanged. Because the name is written straight into a same-origin `text/javascript` body — and JSONP callers normally take it from the query string — leaving it unfiltered would let a request supply arbitrary script for your own origin. If what survives is not a valid member expression, the default `callback` is used: `cb[0]` is emitted, `cb[0x]` is not. ```go title="Signature" func (c fiber.Ctx) JSONP(data any, callback ...string) error func (r fiber.Res) JSONP(data any, callback ...string) error ``` ```go title="Example" type SomeStruct struct { Name string Age uint8 } app.Get("/", func(c fiber.Ctx) error { // Create data struct: data := SomeStruct{ Name: "Grame", Age: 20, } return c.JSONP(data) // => callback({"Name": "Grame", "Age": 20}) return c.JSONP(data, "customFunc") // => customFunc({"Name": "Grame", "Age": 20}) }) ``` #### Links Joins the links followed by the property to populate the response’s [Link HTTP header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link) field. Quotes and backslashes in the `rel` value are escaped so the emitted quoted-string stays grammar-valid per RFC 9110. ```go title="Signature" func (c fiber.Ctx) Links(link ...string) func (r fiber.Res) Links(link ...string) ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { c.Links( "http://api.example.com/users?page=2", "next", "http://api.example.com/users?page=5", "last", ) // Link: ; rel="next", // ; rel="last" // ... }) ``` #### Location Sets the response [Location](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location) HTTP header to the specified path parameter. ```go title="Signature" func (c fiber.Ctx) Location(path string) func (r fiber.Res) Location(path string) ``` ```go title="Example" app.Post("/", func(c fiber.Ctx) error { c.Location("http://example.com") c.Location("/foo/bar") return nil }) ``` #### MsgPack > **Note:** Before using any MsgPack-related features, make sure to follow the [MsgPack setup instructions](../guide/advance-format.md#msgpack). A compact binary alternative to [JSON](#json) for efficient data transfer between micro-services or from server to client. MessagePack serializes faster and yields smaller payloads than plain JSON. Converts any **interface** or **string** to MsgPack with the encoder set in [`MsgPackEncoder`](./fiber.md#msgpackencoder). Fiber does not bundle one; [shamaton/msgpack](https://pkg.go.dev/github.com/shamaton/msgpack/v3) is a common choice. :::info MsgPack also sets the content header to the `ctype` parameter. If no `ctype` is passed in, the header is set to `application/vnd.msgpack`. ::: ```go title="Signature" func (c fiber.Ctx) MsgPack(data any, ctype ...string) error func (r fiber.Res) MsgPack(data any, ctype ...string) error ``` ```go title="Example" type SomeStruct struct { Name string Age uint8 } app.Get("/msgpack", func(c fiber.Ctx) error { // Create data struct: data := SomeStruct{ Name: "Grame", Age: 20, } return c.MsgPack(data) // => Content-Type: application/vnd.msgpack // => 82 A4 4E 61 6D 65 A5 47 72 61 6D 65 A3 41 67 65 14 return c.MsgPack(fiber.Map{ "name": "Grame", "age": 20, }) // => Content-Type: application/vnd.msgpack // => 82 A4 6E 61 6D 65 A5 47 72 61 6D 65 A3 61 67 65 14 return c.MsgPack(fiber.Map{ "type": "https://example.com/probs/out-of-credit", "title": "You do not have enough credit.", "status": 403, "detail": "Your current balance is 30, but that costs 50.", "instance": "/account/12345/msgs/abc", }, "application/problem+msgpack") }) // => Content-Type: application/problem+msgpack // 85 A4 74 79 70 65 D9 27 68 74 74 70 73 3A 2F 2F 65 78 61 6D 70 6C 65 2E 63 6F 6D 2F 70 72 6F 62 73 2F 6F 75 74 2D 6F 66 2D 63 72 65 64 69 74 A5 74 69 74 6C 65 BE 59 6F 75 20 64 6F 20 6E 6F 74 20 68 61 76 65 20 65 6E 6F 75 67 68 20 63 72 65 64 69 74 2E A6 73 74 61 74 75 73 CD 01 93 A6 64 65 74 61 69 6C D9 2E 59 6F 75 72 20 63 75 72 72 65 6E 74 20 62 61 6C 61 6E 63 65 20 69 73 20 33 30 2C 20 62 75 74 20 74 68 61 74 20 63 6F 73 74 73 20 35 30 2E A8 69 6E 73 74 61 6E 63 65 B7 2F 61 63 63 6F 75 6E 74 2F 31 32 33 34 35 2F 6D 73 67 73 2F 61 62 63 ``` #### NoContent Replies `204 No Content`. [`SendStatus`](#sendstatus) already discards the body for every status that disallows one; this drops the `Content-Type` as well, since RFC 9110, Section 6.4.1 gives a `204` no content to describe. :::note `c.SendStatus(fiber.StatusNoContent)` leaves a `Content-Type` a handler had set, so the two are not interchangeable — this is the one that sends nothing about content. ::: ```go title="Signature" func (c fiber.Ctx) NoContent() error func (r fiber.Res) NoContent() error ``` ```go title="Example" app.Delete("/item/:id", func(c fiber.Ctx) error { if err := deleteItem(c.Params("id")); err != nil { return err } return c.NoContent() // => 204, no body }) ``` #### Render Renders a view with data and sends a `text/html` response. By default, `Render` uses the default [**Go Template engine**](https://pkg.go.dev/html/template/). If you want to use another view engine, please take a look at our [**Template middleware**](https://docs.gofiber.io/template). ```go title="Signature" func (c fiber.Ctx) Render(name string, bind any, layouts ...string) error func (r fiber.Res) Render(name string, bind any, layouts ...string) error ``` #### ResetBody Discards the response body, keeping the status and headers. Use it before replacing a partially written body — an error page over a half-rendered view, a cached body over a fresh one. ```go title="Signature" func (c fiber.Ctx) ResetBody() func (r fiber.Res) ResetBody() ``` ```go title="Example" app.Use(func(c fiber.Ctx) error { if err := c.Next(); err == nil { return nil } c.ResetBody() // Drop whatever the handler managed to write. return c.Status(fiber.StatusInternalServerError).SendString("something went wrong") }) ``` #### Send Sets the HTTP response body. The slice is used as it is, without a copy, so it must not be changed after the call. ```go title="Signature" func (c fiber.Ctx) Send(body []byte) error func (r fiber.Res) Send(body []byte) error ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { return c.Send([]byte("Hello, World!")) // => "Hello, World!" }) ``` Fiber also provides `SendString` and `SendStream` methods for raw inputs. :::tip Use this if you **don't need** type assertion, recommended for **faster** performance. ::: ```go title="Signature" func (c fiber.Ctx) SendString(body string) error func (c fiber.Ctx) SendStream(stream io.Reader, size ...int) error func (r fiber.Res) SendString(body string) error func (r fiber.Res) SendStream(stream io.Reader, size ...int) error ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { return c.SendString("Hello, World!") // => "Hello, World!" return c.SendStream(bytes.NewReader([]byte("Hello, World!"))) // => "Hello, World!" }) ``` #### SendEarlyHints Sends an informational `103 Early Hints` response with one or more [`Link` headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Link) before the final response. This allows the browser to start preloading resources while the server prepares the full response. :::caution Browsers use Early Hints most reliably over HTTP/2 or newer. Some legacy HTTP/1.1 clients may not support `SendEarlyHints`. Early Hints (`103` responses) are best supported in HTTP/2 and newer. Older HTTP/1.1 clients may ignore these interim responses or misbehave when receiving them. See [Enabling HTTP/2](../guide/reverse-proxy#enabling-http2) for instructions on how to use a reverse proxy (e.g. Nginx or Traefik) to enable HTTP/2 support. ::: For requests that are not HTTP/1.1 (e.g. HTTP/1.0), no interim `103` response is sent — RFC 9110 forbids sending 1xx responses to HTTP/1.0 clients — but the `Link` headers are still included in the final response. :::caution Interim responses need Fiber's own server. When the app is mounted into `net/http` via the `adaptor` middleware, there is no client connection for interim responses: the `103` is silently skipped and the `Link` headers are still delivered on the final response. ::: ```go title="Signature" func (c fiber.Ctx) SendEarlyHints(hints []string) error func (r fiber.Res) SendEarlyHints(hints []string) error ``` ```go title="Example" hints := []string{"; rel=preload; as=script"} app.Get("/early", func(c fiber.Ctx) error { if err := c.SendEarlyHints(hints); err != nil { return err } return c.SendString("done") }) ``` #### SendFile Transfers the file from the given path. Sets the [Content-Type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) response HTTP header field based on the **file** extension or format. ```go title="Config" title="Config" // SendFile defines configuration options when to transfer file with SendFile. type SendFile struct { // FS is the file system to serve the static files from. // You can use interfaces compatible with fs.FS like embed.FS, os.DirFS etc. // // Optional. Default: nil FS fs.FS // When set to true, the server tries minimizing CPU usage by caching compressed files. // This works differently than the github.com/gofiber/compression middleware. // You have to set Content-Encoding header to compress the file. // Available compression methods are gzip, br, and zstd. // The request's Accept-Encoding header is left untouched either way, so the // compress middleware can still compress the response when this is false. // // Optional. Default: false Compress bool `json:"compress"` // When set to true, enables byte range requests. A request for several // ranges, or whose If-Range does not match, gets the whole file. // // Optional. Default: false ByteRange bool `json:"byte_range"` // When set to true, enables direct download. // // Optional. Default: false Download bool `json:"download"` // Expiration duration for inactive file handlers. // Use a negative time.Duration to disable it. // // Optional. Default: 10 * time.Second CacheDuration time.Duration `json:"cache_duration"` // The value for the Cache-Control HTTP-header // that is set on the file response. MaxAge is defined in seconds. // // Optional. Default: 0 MaxAge int `json:"max_age"` } ``` ```go title="Signature" title="Signature" func (c fiber.Ctx) SendFile(file string, config ...SendFile) error func (r fiber.Res) SendFile(file string, config ...SendFile) error ``` ```go title="Example" app.Get("/not-found", func(c fiber.Ctx) error { return c.SendFile("./public/404.html") // Serve pre-compressed copies, caching them to save CPU. This is the file // server's own compression, not the compress middleware, which keeps // compressing the response either way. return c.SendFile("./static/index.html", fiber.SendFile{ Compress: true, }) }) ``` :::info The name is taken as it is, the way `os.Open` takes it: a `%`, `?` or `#` in it is part of the name, so `c.SendFile("hash_sign_#.txt")` serves that file and nothing needs escaping. The exceptions are a name holding an ASCII control byte, which is not found, and a directory named without a trailing slash, which is not found either; with the slash, its `index.html` is served. A name built from request input is served from wherever it points, so confine it yourself. Lexical cleaning such as `filepath.Join(root, filepath.Clean("/"+name))` keeps the name under `root` but does not stop a symbolic link inside `root` from pointing outside it, and neither does `os.DirFS`. To confine the file itself, open the directory with `os.OpenRoot` and serve through the `FS` option with `root.FS()`, which refuses any link that escapes the root. An encoded separator that a route parameter still holds, as in `..%2Fsecret.txt`, stays one name and is not found rather than decoded into a step out of the directory. ::: :::caution Route parameters keep their escapes With [`UnescapePath`](fiber.md#config) off, which is the default, a route parameter keeps every escape the router keeps, and `SendFile` no longer decodes it. For `/files/my%20file.txt`, `c.SendFile(filepath.Join(dir, c.Params("name")))` looks for a file named `my%20file.txt` rather than `my file.txt`, and `/files/100%25.txt` names `100%25.txt` rather than `100%.txt`. Either enable `UnescapePath`, which gives decoded parameters, or decode the parameter yourself and confine the result, since a decoded `..%2F` is a step out of the directory: ```go root, err := os.OpenRoot("./files") if err != nil { log.Fatal(err) } app.Get("/files/:name", func(c fiber.Ctx) error { name, err := url.PathUnescape(c.Params("name")) if err != nil { return fiber.ErrBadRequest } // root.FS() refuses a name that leaves ./files, such as "../secret.txt" return c.SendFile(name, fiber.SendFile{FS: root.FS()}) }) ``` ::: :::info Byte ranges With `ByteRange` on, the file server answers one range of a `GET` request: `Range: bytes=0-499`, `bytes=500-` or `bytes=-500`, with `206 Partial Content`, `Content-Range` and `Accept-Ranges: bytes`. [RFC 9110 §13.1.5 and §14.2](https://www.rfc-editor.org/rfc/rfc9110#section-14.2) say what else a server does with the field, and Fiber applies it around that single range: - The unit is case-insensitive, so `Range: Bytes=0-499` is a range. - A request for **several ranges** (`bytes=0-499,1000-1499`), with a **unit the server does not know**, or with a method other than `GET` (`HEAD` included) is answered with the whole file, as if it carried no `Range`. Several ranges used to be refused with `416`, a status that is for ranges that cannot be satisfied. - **`If-Range`** makes the range conditional on the file being the one the client holds. The range is served only when the date it carries is exactly the file's `Last-Modified`, and that time is at least two seconds old (the header has whole-second resolution, and a younger file may still change within the second it names); when the file has changed, the whole file is sent instead of a piece that would be spliced onto the client's stale copy. An entity-tag matches only an `ETag` your handler set before calling `SendFile`: the file server sends none. A date cannot tell apart two versions written within the same second, so a file that is replaced in place while clients resume it should carry a strong `ETag`. - A range that cannot be satisfied is answered with `416 Range Not Satisfiable`, and with `Content-Range: bytes */` for `SendFile`. ::: :::info You can set the `CacheDuration` config property to `-1` to disable caching. ::: ```go title="Example" app.Get("/file", func(c fiber.Ctx) error { return c.SendFile("style.css", fiber.SendFile{ CacheDuration: -1, }) }) ``` :::info You can use multiple `SendFile` calls with different configurations in a single route. Fiber creates different filesystem handlers per config. A file system whose type is a function (a `func` type with an `Open` method) cannot be told apart from another one, so Fiber keeps no handler for it: a new handler is built, without the file cache, on every call. That keeps the handler cache from growing, but each call costs more than with a struct or pointer type. ::: ```go title="Example" app.Get("/file", func(c fiber.Ctx) error { switch c.Query("config") { case "filesystem": return c.SendFile("style.css", fiber.SendFile{ FS: os.DirFS("."), }) case "filesystem-compress": return c.SendFile("style.css", fiber.SendFile{ FS: os.DirFS("."), Compress: true, }) case "compress": return c.SendFile("style.css", fiber.SendFile{ Compress: true, }) default: return c.SendFile("style.css") } return nil }) ``` :::info For sending multiple files from an embedded file system, [this functionality](../middleware/static.md#serving-files-using-embedfs) can be used. ::: #### SendStatus Sets the status code and the correct status message in the body if the response body is **empty**. A status that cannot carry a body (`1xx`, `204`, `205` and `304`) empties the response body instead, and drops a `Content-Length` header the handler had set. [RFC 9110 Section 8.6](https://www.rfc-editor.org/rfc/rfc9110#section-8.6) requires that for `1xx` and `204`; Fiber also does it for `205` and `304`. A `205` still goes out with `Content-Length: 0`: it has no content ([Section 15.3.6](https://www.rfc-editor.org/rfc/rfc9110#section-15.3.6)), but unlike `1xx`, `204` and `304` it still has to be framed ([RFC 9112 Section 6.3](https://www.rfc-editor.org/rfc/rfc9112#section-6.3)). :::tip You can find all used status codes and messages [in the Fiber source code](https://github.com/gofiber/fiber/blob/dffab20bcdf4f3597d2c74633a7705a517d2c8c2/utils.go#L183-L244). ::: ```go title="Signature" func (c fiber.Ctx) SendStatus(status int) error func (r fiber.Res) SendStatus(status int) error ``` ```go title="Example" app.Get("/not-found", func(c fiber.Ctx) error { return c.SendStatus(415) // => 415 "Unsupported Media Type" c.SendString("Hello, World!") return c.SendStatus(415) // => 415 "Hello, World!" }) ``` #### SendStream Sets the response body to a stream of data and adds an optional body size. ```go title="Signature" func (c fiber.Ctx) SendStream(stream io.Reader, size ...int) error func (r fiber.Res) SendStream(stream io.Reader, size ...int) error ``` :::info `SendStream` operates asynchronously. The handler returns immediately after setting up the stream, but the actual reading and sending of data happens **after** the handler completes. This is handled by the underlying `fasthttp` library. If the provided stream implements `io.Closer`, it will be automatically closed by `fasthttp` after the response is fully sent or if an error occurs. ::: :::caution HTTP/1.0 clients A stream of unknown size is sent with chunked framing, which a server must not do in response to a request older than HTTP/1.1 ([RFC 9112 §6.1](https://www.rfc-editor.org/rfc/rfc9112#section-6.1)). Fiber does it on purpose: the RFC's alternative, ending the body by closing the connection ([§6.3](https://www.rfc-editor.org/rfc/rfc9112#section-6.3)), is not something fasthttp offers, and reading the stream to its end before replying would hold the handler for as long as the stream lasts, with no way to tell that the client left. Proxies that speak HTTP/1.0 to their upstream, such as nginx before 1.29.7 unless `proxy_http_version 1.1` is set, are the usual source of such requests. Pass the size when you know it: a stream of known size is sent with a `Content-Length` as it is read, whatever the HTTP version. ::: :::caution When passing `fiber.Ctx` as a `context.Context` to libraries that spawn goroutines (e.g., for streaming operations), those goroutines may attempt to access the context after the handler returns. Since `fiber.Ctx` is recycled and released after the handler completes, this can cause issues. **Recommended approach**: Use `c.Context()` or `c.RequestCtx()` instead of passing `c` directly to such libraries. See the [Context Guide](../guide/context.md) for more details. ::: ```go title="Example" app.Get("/", func(c fiber.Ctx) error { return c.SendStream(bytes.NewReader([]byte("Hello, World!"))) // => "Hello, World!" }) ``` ```go title="Example with file streaming" app.Get("/download", func(c fiber.Ctx) error { file, err := os.Open("large-file.zip") if err != nil { return err } // File will be automatically closed by fasthttp after streaming completes stat, err := file.Stat() if err != nil { file.Close() return err } return c.SendStream(file, int(stat.Size())) }) ``` #### SendStreamWriter Sets the response body stream writer. :::note The argument `streamWriter` represents a function that populates the response body using a buffered stream writer. ::: ```go title="Signature" func (c fiber.Ctx) SendStreamWriter(streamWriter func(*bufio.Writer)) error func (r fiber.Res) SendStreamWriter(streamWriter func(*bufio.Writer)) error ``` ```go title="Example" app.Get("/", func (c fiber.Ctx) error { return c.SendStreamWriter(func(w *bufio.Writer) { fmt.Fprintf(w, "Hello, World!\n") }) // => "Hello, World!" }) ``` :::info To send data before `streamWriter` returns, you can call `w.Flush()` on the provided writer. Otherwise, the buffered stream flushes after `streamWriter` returns. ::: :::caution HTTP/1.0 clients The response is chunked, even for a request older than HTTP/1.1, for the reasons given under [`SendStream`](#sendstream). A client that cannot read a chunked response cannot be sent a stream of unknown size. ::: :::note `w.Flush()` will return an error if the client disconnects before `streamWriter` finishes writing a response. ::: ```go title="Example" app.Get("/wait", func(c fiber.Ctx) error { return c.SendStreamWriter(func(w *bufio.Writer) { // Begin Work fmt.Fprintf(w, "Please wait for 10 seconds\n") if err := w.Flush(); err != nil { log.Print("Client disconnected!") return } // Send progress over time time.Sleep(time.Second) for i := 0; i < 9; i++ { fmt.Fprintf(w, "Still waiting...\n") if err := w.Flush(); err != nil { // If client disconnected, cancel work and finish log.Print("Client disconnected!") return } time.Sleep(time.Second) } // Finish fmt.Fprintf(w, "Done!\n") }) }) ``` #### SendString Sets the response body to a string. ```go title="Signature" func (c fiber.Ctx) SendString(body string) error func (r fiber.Res) SendString(body string) error ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { return c.SendString("Hello, World!") // => "Hello, World!" }) ``` #### Set Sets the response’s HTTP header field to the specified `key`, `value`. ```go title="Signature" func (c fiber.Ctx) Set(key, val string) func (r fiber.Res) Set(key, val string) ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { c.Set("Content-Type", "text/plain") // => "Content-Type: text/plain" // ... }) ``` #### Status Sets the HTTP status for the response. :::info This method is **chainable**. ::: :::caution Status codes outside 100 to 999 A status line carries three digits ([RFC 9112 §4](https://www.rfc-editor.org/rfc/rfc9112#section-4)), so a code below `100` or above `999` cannot be sent: `99` and `1000` would be malformed status lines that clients refuse, and `0` or a negative number would go out as `200 OK`, reporting a failure as a success. `Status` answers `500 Internal Server Error` for such a code and logs it, which also covers [`SendStatus`](#sendstatus), `Redirect().Status` and an error created with `fiber.NewError`. Codes from `600` to `999` are sent as given, although [RFC 9110 §15](https://www.rfc-editor.org/rfc/rfc9110#section-15) defines only `100` to `599` and clients treat the rest as a `5xx`. ::: ```go title="Signature" func (c fiber.Ctx) Status(status int) fiber.Ctx func (r fiber.Res) Status(status int) fiber.Ctx ``` ```go title="Example" app.Get("/fiber", func(c fiber.Ctx) error { c.Status(fiber.StatusOK) return nil }) app.Get("/hello", func(c fiber.Ctx) error { return c.Status(fiber.StatusBadRequest).SendString("Bad Request") }) app.Get("/world", func(c fiber.Ctx) error { return c.Status(fiber.StatusNotFound).SendFile("./public/gopher.png") }) ``` #### StatusCode Returns the status code currently set on the response. It is the read side of [`Status`](#status), and reports `200` until something sets another code. Called after [`Next`](#next) it is the status the chain settled on, which is what logging, metrics, and caching middleware key off. ```go title="Signature" func (c fiber.Ctx) StatusCode() int func (r fiber.Res) StatusCode() int ``` ```go title="Example" app.Use(func(c fiber.Ctx) error { err := c.Next() if c.StatusCode() >= fiber.StatusInternalServerError { alert(c.Route().Path, c.StatusCode()) } return err }) ``` #### Type Sets the [Content-Type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) HTTP header to the MIME type listed [in the Nginx MIME types configuration](https://github.com/nginx/nginx/blob/master/conf/mime.types) specified by the file **extension**. A text type gets `; charset=utf-8` unless you pass another `charset`: every `text/*` type, `application/json`, `application/javascript`, `application/xml` and any `+json` or `+xml` type. Other types, such as `image/png`, get none. An extension missing from that list is looked up in the standard library's MIME tables, and the parameters of its answer are dropped: `csv` gives `text/csv`, which takes `charset=utf-8` by the rule above. The header never holds two charsets. Pass `charset` to send another one. :::info This method is **chainable**. ::: ```go title="Signature" func (c fiber.Ctx) Type(extension string, charset ...string) fiber.Ctx func (r fiber.Res) Type(extension string, charset ...string) fiber.Ctx ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { c.Type(".html") // => "text/html; charset=utf-8" c.Type("html") // => "text/html; charset=utf-8" c.Type("png") // => "image/png" c.Type("json", "utf-8") // => "application/json; charset=utf-8" c.Type("html", "iso-8859-1") // => "text/html; charset=iso-8859-1" // ... }) ``` #### Vary Adds the given header field to the [Vary](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary) response header. This will append the header if not already listed; otherwise, it leaves it listed in the current location. Field names are compared case-insensitively (RFC 9110 Section 5.1). The first spelling is kept, so `Vary("accept")` followed by `Vary("Accept")` stays `Vary: accept`. :::info Multiple fields are **allowed**. Per RFC 9110, the wildcard `"*"` is only meaningful as the sole member of the field: adding `"*"` collapses the header to a single `*`, and once `*` is present no further fields are appended. ::: ```go title="Signature" func (c fiber.Ctx) Vary(fields ...string) func (r fiber.Res) Vary(fields ...string) ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { c.Vary("Origin") // => Vary: Origin c.Vary("User-Agent") // => Vary: Origin, User-Agent // No duplicates c.Vary("Origin") // => Vary: Origin, User-Agent c.Vary("Accept-Encoding", "Accept") // => Vary: Origin, User-Agent, Accept-Encoding, Accept c.Vary("*") // => Vary: * // ... }) ``` #### Write Adopts the `Writer` interface. ```go title="Signature" func (c fiber.Ctx) Write(p []byte) (n int, err error) func (r fiber.Res) Write(p []byte) (n int, err error) ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { c.Write([]byte("Hello, World!")) // => "Hello, World!" fmt.Fprintf(c, "%s\n", "Hello, World!") // => "Hello, World!" return nil }) ``` #### Writef Writes a formatted string using a format specifier. ```go title="Signature" func (c fiber.Ctx) Writef(format string, a ...any) (n int, err error) func (r fiber.Res) Writef(format string, a ...any) (n int, err error) ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { world := "World!" c.Writef("Hello, %s", world) // => "Hello, World!" fmt.Fprintf(c, "%s\n", "Hello, World!") // => "Hello, World!" return nil }) ``` #### WriteString Writes a string to the response body. ```go title="Signature" func (c fiber.Ctx) WriteString(s string) (n int, err error) func (r fiber.Res) WriteString(s string) (n int, err error) ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { _, err := c.WriteString("Hello, World!") // => "Hello, World!" return err }) ``` #### Written Reports whether anything has been written to the response body yet, so middleware can tell a handler that produced a response from one that left it untouched. A streamed body counts as written without draining the stream, which is the case [`Body`](#body-res) deliberately answers `nil` for. :::note Status and headers are not body writes: a handler that only called [`Status`](#status) leaves this `false`. ::: ```go title="Signature" func (c fiber.Ctx) Written() bool func (r fiber.Res) Written() bool ``` ```go title="Example" app.Use(func(c fiber.Ctx) error { if err := c.Next(); err != nil { return err } if !c.Written() { return c.NoContent() } return nil }) ``` #### XML Converts any **interface** or **string** to XML using the standard `encoding/xml` package. :::info XML also sets the content header to `application/xml; charset=utf-8`. ::: ```go title="Signature" func (c fiber.Ctx) XML(data any) error func (r fiber.Res) XML(data any) error ``` ```go title="Example" type SomeStruct struct { XMLName xml.Name `xml:"Fiber"` Name string `xml:"Name"` Age uint8 `xml:"Age"` } app.Get("/", func(c fiber.Ctx) error { // Create data struct: data := SomeStruct{ Name: "Grame", Age: 20, } return c.XML(data) // // Grame // 20 // }) ``` --- ## 📦 Fiber ### Server start #### New This method creates a new **App** named instance. You can pass optional [config](#config) when creating a new instance. ```go title="Signature" func New(config ...Config) *App ``` ```go title="Example" // Default config app := fiber.New() // ... ``` #### Config You can pass an optional Config when creating a new Fiber instance. ```go title="Example" // Custom config app := fiber.New(fiber.Config{ CaseSensitive: true, StrictRouting: true, ServerHeader: "Fiber", AppName: "Test App v1.0.1", }) // ... ``` ##### Config fields | Property | Type | Description | Default | |---------------------------------------------------------------------------------------|-----------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------| | AppName | `string` | Sets the application name, which is shown in the startup message, passed to the [`OnListen`](./hooks.md#onlisten) hooks as `ListenData.AppName`, and used in the default [`SharedState`](./state.md#sharedstate-prefork-safe) key prefix. It does not set the `Server` header; use `ServerHeader` for that. | `""` | | BodyLimit | `int` | Sets the maximum allowed size for a request body. Zero or negative values fall back to the default limit, `fiber.DefaultBodyLimit`. If the size exceeds the configured limit, it sends `413 - Request Entity Too Large` response. This limit also applies when running Fiber through the adaptor middleware from `net/http`, when decoding compressed request bodies via [`Ctx.Body()`](./ctx.md#body), when parsing multipart form data via [`Ctx.MultipartForm()`](./ctx.md#multipartform), and when copying an uploaded file with [`Ctx.SaveFileToStorage()`](./ctx.md#savefiletostorage). | `4 * 1024 * 1024` | | CaseSensitive | `bool` | When enabled, `/Foo` and `/foo` are different routes. When disabled, `/Foo` and `/foo` are treated the same. A file server opens the name the router matched, so on a case-insensitive filesystem middleware mounted on `/private` does not cover `/PRIVATE` when this is enabled; see [Static](../middleware/static.md). | `false` | | CBORDecoder | `utils.CBORUnmarshal` | Allowing for flexibility in using another cbor library for decoding. | `binder.UnimplementedCborUnmarshal` | | CBOREncoder | `utils.CBORMarshal` | Allowing for flexibility in using another cbor library for encoding. | `binder.UnimplementedCborMarshal` | | ColorScheme | [`Colors`](https://github.com/gofiber/fiber/blob/main/color.go) | You can define custom color scheme. They'll be used for startup message, route list and some middlewares. The fields are `Black`, `Red`, `Green`, `Yellow`, `Blue`, `Magenta`, `Cyan`, `White` and `Reset`, each an ANSI escape sequence; a field left empty falls back to its `DefaultColors` value. | [`DefaultColors`](https://github.com/gofiber/fiber/blob/main/color.go) | | CompressedFileSuffixes | `map[string]string` | Adds a suffix to the original file name and tries saving the resulting compressed file under the new file name. | `{"gzip": ".fiber.gz", "br": ".fiber.br", "zstd": ".fiber.zst"}` | | Concurrency | `int` | Maximum number of concurrent connections. Zero or negative values fall back to the default, `fiber.DefaultConcurrency`. | `256 * 1024` | | DisableDefaultContentType | `bool` | When true, omits the default Content-Type header from the response. | `false` | | DisableDefaultDate | `bool` | When true, omits the Date header from the response. | `false` | | DisableFlashMessages | `bool` | Disables redirect flash messages: `Redirect().With` and `WithInput` set no cookie, `Messages` and `OldInput` report nothing, and the scan of every request's headers for an incoming `fiber_flash` cookie is skipped. Flash messages travel in that cookie, so only clients that keep cookies across a redirect (browsers, typically) ever receive them; deployments whose clients do not can turn them off. | `false` | | DisableHeadAutoRegister | `bool` | Prevents Fiber from automatically registering `HEAD` routes for each `GET` route so you can supply custom `HEAD` handlers; manual `HEAD` routes still override the generated ones. A mounted sub-app that sets this option, or that leaves `HEAD` out of its `RequestMethods`, gets no generated `HEAD` routes for its routes, even when the app it is mounted on generates them for its own. | `false` | | DisableHeaderNormalizing | `bool` | By default all header names are normalized: conteNT-tYPE -> Content-Type | `false` | | DisableKeepalive | `bool` | Disables keep-alive connections so the server closes each connection after the first response. | `false` | | DisablePreParseMultipartForm | `bool` | Will not pre parse Multipart Form data if set to true. This option is useful for servers that desire to treat multipart form data as a binary blob, or choose when to parse the data. | `false` | | EnableIPValidation | `bool` | If set to true, `c.IP()` and `c.IPs()` will validate IP addresses before returning them. Also, `c.IP()` will return a single validated IP rather than the raw header value, which for `X-Forwarded-For` may be a comma-separated chain: when `TrustProxyConfig` names proxy IPs or ranges (`Proxies`, `Loopback`, `Private` or `LinkLocal`), the chain is walked from right to left and those proxies are skipped; otherwise, including when only `UnixSocket` is trusted, the first valid IP from the left is returned (see [`Ctx.IP`](ctx.md#ip)). Enable it whenever `c.IP()` is used as a single client identifier and the header can carry a chain, for example by the [limiter](../middleware/limiter.md) middleware or an IP allowlist. **WARNING:** There is a small performance cost to doing this validation. It is not needed when your reverse proxy overwrites this header with a single client IP or you use a single-IP header such as `X-Real-IP`; keep it disabled there if speed is your main concern. | `false` | | EnableSplittingOnParsers | `bool` | Splits query, body, and header parameters on commas when enabled. For example, `/api?foo=bar,baz` becomes `foo[]=bar&foo[]=baz`. | `false` | | ErrorHandler | `ErrorHandler` | ErrorHandler is executed when an error is returned from fiber.Handler, and for a request fasthttp refuses before routing. Mounted fiber error handlers are retained by the top-level app and applied on requests under a literal mount prefix; see [`App.ErrorHandler`](./app.md#errorhandler). | `DefaultErrorHandler` | | GETOnly | `bool` | Rejects every method except GET and HEAD if set to true. This option is useful as anti-DoS protection for servers accepting only GET requests. The request size is limited by ReadBufferSize if GETOnly is set. A rejected request is answered `405 Method Not Allowed` with `Allow: GET, HEAD`. | `false` | | IdleTimeout | `time.Duration` | The maximum amount of time to wait for the next request when keep-alive is enabled. If IdleTimeout is zero, the value of ReadTimeout is used. | `0` | | Immutable | `bool` | When enabled, all values returned by context methods are immutable. By default, they are valid until you return from the handler; see issue [\#185](https://github.com/gofiber/fiber/issues/185). | `false` | | JSONDecoder | `utils.JSONUnmarshal` | Allowing for flexibility in using another json library for decoding. | `json.Unmarshal` | | JSONEncoder | `utils.JSONMarshal` | Allowing for flexibility in using another json library for encoding. | `json.Marshal` | | MaxRanges | `int` | Sets the maximum number of ranges parsed from a `Range` header. Zero or negative values fall back to the default limit, `fiber.DefaultMaxRanges`. If the limit is exceeded, the request is rejected with `416 - Requested Range Not Satisfiable` and `Content-Range: bytes */`. | `16` | | MsgPackDecoder | `utils.MsgPackUnmarshal` | Allowing for flexibility in using another msgpack library for decoding. | `binder.UnimplementedMsgpackUnmarshal` | | MsgPackEncoder | `utils.MsgPackMarshal` | Allowing for flexibility in using another msgpack library for encoding. | `binder.UnimplementedMsgpackMarshal` | | PassLocalsToContext | `bool` | Controls whether `StoreInContext` also propagates values into the request `context.Context` for Fiber-backed contexts. `StoreInContext` always writes to `c.Locals()`. `ValueFromContext` for Fiber-backed contexts always reads from `c.Locals()`. | `false` | | PassLocalsToViews | `bool` | PassLocalsToViews Enables passing of the locals set on a fiber.Ctx to the template engine. See our **Template Middleware** for supported engines. | `false` | | ProxyHeader | `string` | Specifies the header name to read the client's real IP address from when behind a reverse proxy. Common values: `fiber.HeaderXForwardedFor`, `"X-Real-IP"`, `"CF-Connecting-IP"` (Cloudflare). **Important:** This setting **requires** `TrustProxy` to be enabled; `TrustProxyConfig` controls which proxy IPs are trusted for reading this header. Without `TrustProxy`, this setting has no effect and `c.IP()` will always return the remote IP from the TCP connection. **Behavior note:** `X-Forwarded-For` often contains a comma-separated chain of IP addresses. With the default `EnableIPValidation = false`, `c.IP()` will return the raw header value (the whole chain) rather than a single parsed client IP. With `EnableIPValidation = true`, `c.IP()` walks the chain from **right to left**, skips every IP that matches the proxy IPs or ranges in `TrustProxyConfig`, and returns the first non-trusted IP; with only `UnixSocket` trusted there are no proxy IPs to skip and the first valid IP from the left is returned (see [`Ctx.IP`](ctx.md#ip)). Enable it whenever `c.IP()` is used as a single client identifier and the header can carry a chain, for example by the [limiter](../middleware/limiter.md) middleware or an IP allowlist. Your reverse proxy should still overwrite or sanitize this header based on the real client connection, or provide a single-IP header such as `"X-Real-IP"` or a provider-specific header like `"CF-Connecting-IP"`; with a single address in the header, validation is not required to get a usable client IP. **Security Warning:** Headers can be easily spoofed. Always configure `TrustProxyConfig` to validate the proxy IP address, otherwise malicious clients can forge headers to bypass IP-based access controls. | `""` | | ReadBufferSize | `int` | per-connection buffer size for requests' reading. This also limits the maximum header size: a request whose headers do not fit is answered `431 Request Header Fields Too Large`. Increase this buffer if your clients send multi-KB RequestURIs and/or multi-KB headers \(for example, BIG cookies\). Zero or negative values fall back to the default, `fiber.DefaultReadBufferSize`. | `8192` | | ReadTimeout | `time.Duration` | The amount of time allowed to read the full request, including the body. The default timeout is unlimited. | `0` | | ReduceMemoryUsage | `bool` | Aggressively reduces memory usage at the cost of higher CPU usage if set to true. | `false` | | RegexHandler | `any` | Configures the compiler used for `regex()` route constraints. Assign `regexp.MustCompile` or `coregex.MustCompile` directly to switch engines. It must be a function of the form `func(string) T`, where `T` has a `MatchString(string) bool` method; `fiber.New` panics for anything else. Fiber reuses the compiled matcher across requests, so the returned value must be safe for concurrent use. Fiber may invoke `RegexHandler` more than once per route while parsing raw and normalized route patterns during registration. | `regexp.MustCompile` | | RequestMethods | `[]string` | RequestMethods provides customizability for HTTP methods. You can add/remove methods as you wish. The order is the one [`App.Stack`](./app.md#stack) reports. `DefaultMethods` holds GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH and QUERY. Registering a route for a method that is not listed panics, and a request with such a method is answered `501 Not Implemented`. | `DefaultMethods` | | ServerHeader | `string` | Enables the `Server` HTTP header with the given value. | `""` | | Services | `[]Service` | Services that Fiber starts, in order, while `fiber.New` creates the app, and terminates in reverse order after shutdown. `fiber.New` panics when a service is `nil`, shares the name its `String()` method returns with another service, or fails to start. See [Services](./services.md). | `nil` | | ServicesShutdownContextProvider | `func() context.Context` | Returns the context that Fiber passes to the `Terminate` method of each service when it shuts the services down. | A provider that returns `context.Background()` | | ServicesStartupContextProvider | `func() context.Context` | Returns the context that Fiber passes to the `Start` method of each service, and to its `State` method when it prints the startup message. | A provider that returns `context.Background()` | | SharedStatePrefix | `string` | Prefix of the keys that [`SharedState`](./state.md#sharedstate-prefork-safe) writes to `SharedStorage`. When it is empty, Fiber uses `gofiber-shared-state-`, followed by the `AppName` and a hyphen if `AppName` is set. | `""` | | SharedStorage | `Storage` | Storage behind [`SharedState`](./state.md#sharedstate-prefork-safe), which prefork workers and other processes can share when the storage itself is shared. The `SharedState` methods return `ErrSharedStorageNotConfigured` while it is `nil`. Fiber closes it on shutdown, after the services are terminated. | `nil` | | SkipUnmatchedRoutes | `bool` | When enabled, requests whose path and method match no registered route are answered with `404` (or `405` when the path exists for other methods) before the middleware chain runs, avoiding work on requests to unregistered paths (bots, scanners, bad URLs). Warning: middleware never runs for skipped requests, so Use-based responders on unregistered paths (catch-all 404 pages, static, proxy, healthcheck, rewrite/redirect middleware) and logger/metrics visibility stop working for them. Rate limiters are in the same position: requests to unregistered paths are neither counted nor throttled, and leave no trace in the access log. CORS preflight requests are exempt so cors middleware keeps working. Customize the responses via `ErrorHandler`. With more than 64 entries in `RequestMethods` the option has no effect and requests go through the normal router. | `false` | | StreamRequestBody | `bool` | StreamRequestBody enables request body streaming, and calls the handler sooner when given body is larger than the current limit. | `false` | | StrictRouting | `bool` | When enabled, the router treats `/foo` and `/foo/` as different, for parametric routes such as `/users/:id/` as well. Otherwise, the router treats `/foo` and `/foo/` as the same. | `false` | | StructValidator | `StructValidator` | If you want to validate header/form/query... automatically when to bind, you can define struct validator. Fiber doesn't have default validator, so it'll skip validator step if you don't use any validator. | `nil` | | TrustProxy | `bool` | Enables trust of reverse proxy headers. When enabled, Fiber will check if the request is coming from a trusted proxy (configured in `TrustProxyConfig`) before reading values from proxy headers. **Required for**: Using `ProxyHeader` to read client IP from headers like `X-Forwarded-For`. **Behavior when enabled:** If the remote IP is trusted (matches `TrustProxyConfig`), then `c.IP()` reads from `ProxyHeader` (when configured; otherwise it uses `RemoteIP()`), `c.Scheme()` first checks standard proxy scheme headers (`X-Forwarded-Proto`, `X-Forwarded-Protocol`, `X-Forwarded-Ssl`, `X-Url-Scheme`) and falls back to the actual connection scheme if none are set, and `c.Hostname()` prefers `X-Forwarded-Host` but falls back to the request Host header when the proxy header is not present. If the remote IP is NOT trusted, these methods ignore proxy headers and use the actual connection values instead. **Security:** This prevents header spoofing by validating the proxy's IP address. Always configure `TrustProxyConfig` when enabling this option and set `ProxyHeader` if you want `c.IP()` to use a specific header. | `false` | | TrustProxyConfig | `TrustProxyConfig` | Configures which proxy IP addresses or ranges to trust. Only effective when `TrustProxy` is enabled. **Fields:** • `Proxies` - List of trusted proxy IPs or CIDR ranges (e.g., `[]string{"10.10.0.58", "192.168.0.0/24"}`); `fiber.New` logs a warning for an entry it cannot parse and does not trust it • `Loopback` - Trust loopback addresses (127.0.0.0/8, ::1/128) • `Private` - Trust all private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fc00::/7) • `LinkLocal` - Trust link-local addresses (169.254.0.0/16, fe80::/10) • `UnixSocket` - Trust Unix domain socket connections **Example:** For an app behind Nginx at 10.10.0.58, use `TrustProxyConfig{Proxies: []string{"10.10.0.58"}}` or `TrustProxyConfig{Private: true}` if using private network IPs. | `{}` (`DefaultTrustProxyConfig`) | | UnescapePath | `bool` | The router always normalizes the request path before matching, as [RFC 3986 Section 6.2.2](https://www.rfc-editor.org/rfc/rfc3986#section-6.2.2) describes: percent-encoded unreserved characters (letters, digits, `-`, `.`, `_`, `~`) are decoded, every other escape keeps its encoding with uppercase hex digits, and `.` and `..` segments are removed, so `/%70rivate` and `/x/../private` both match `/private` while `/a%2Fb` and `//private` stay as sent. When enabled, every escape is decoded, so that routing, `ctx.Path()` and `ctx.Params()` work with the fully decoded path. Only `%XX` escapes are decoded: a `+` stays a `+`, as it is an ordinary path character, and a malformed escape is left as it is | `false` | | Views | `Views` | Views is the interface that wraps the Render function. See our **Template Middleware** for supported engines. A nil value, a typed nil pointer included, means that no view engine is configured. | `nil` | | ViewsLayout | `string` | Views Layout is the global layout for all template render until override on Render function. See our **Template Middleware** for supported engines. | `""` | | WriteBufferSize | `int` | Per-connection buffer size for responses' writing. Zero or negative values fall back to the default, `fiber.DefaultWriteBufferSize`. | `8192` | | WriteTimeout | `time.Duration` | The maximum duration before timing out writes of the response. The default timeout is unlimited. | `0` | | XMLDecoder | `utils.XMLUnmarshal` | Allowing for flexibility in using another XML library for decoding. | `xml.Unmarshal` | | XMLEncoder | `utils.XMLMarshal` | Allowing for flexibility in using another XML library for encoding. | `xml.Marshal` | ### Server listening #### Config You can pass an optional ListenConfig when calling the [`Listen`](#listen) or [`Listener`](#listener) method. ```go title="Example" // Custom config app.Listen(":8080", fiber.ListenConfig{ EnablePrefork: true, DisableStartupMessage: true, }) ``` ##### Config fields | Property | Type | Description | Default | |-------------------------------------------------------------------------|-------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------| | BeforeServeFunc | `func(app *App) error` | Allows customizing and accessing fiber app before serving the app. If it returns an error, `Listen` and `Listener` return that error without serving, and `Listen` closes the listener it created. It is not called when `EnablePrefork` is enabled. | `nil` | | CertClientFile | `string` | Path of the CA bundle used to verify client certificates, for mTLS on the `CertFile`/`CertKeyFile` path. Ignored when `TLSConfig` is set: that config is served as supplied and this field does not modify it, so a client certificate is required only if its `ClientAuth` is a Require mode, verified against its `ClientCAs`. Fiber logs a warning when both are supplied. | `""` | | CertFile | `string` | Path of the certificate file. If you want to use TLS, you must enter this field together with `CertKeyFile`; setting only one of them makes `Listen` return `fiber.ErrCertFileAndKeyRequired` instead of silently serving plaintext HTTP. | `""` | | CertKeyFile | `string` | Path of the certificate's private key. If you want to use TLS, you must enter this field together with `CertFile`. | `""` | | DisableStartupMessage | `bool` | When set to true, it will not print out the «Fiber» ASCII art and listening address. | `false` | | EnablePrefork | `bool` | When set to true, this will spawn multiple Go processes listening on the same port. | `false` | | EnablePrintRoutes | `bool` | If set to true, will print all routes with their method, path, name, and handlers. | `false` | | GracefulContext | `context.Context` | Field to shutdown Fiber by given context gracefully. A context that is already done, or is canceled while `Listen` / `Listener` is still starting up, shuts the app down too; see [Server Shutdown](#server-shutdown). With `EnablePrefork` it stops only the process it is canceled in; see [Prefork](#prefork). | `nil` | | ShutdownTimeout | `time.Duration` | Specifies the maximum duration to wait for the server to gracefully shutdown. When the timeout is reached, the shutdown stops waiting, without closing the connections that are still active, and the `context.DeadlineExceeded` error is passed to the `OnPostShutdown` callback. A zero value applies the default; set a negative value to disable the timeout and wait indefinitely. | `10 * time.Second` | | ListenerAddrFunc | `func(addr net.Addr)` | Called with the `net.Addr` of the listener that `Listen` creates, for example to read the port the OS picked for `:0`; with prefork, once in each child process. `Listener` does not call it. | `nil` | | ListenerNetwork | `string` | Known networks are "tcp" (`fiber.NetworkTCP`), "tcp4" (`fiber.NetworkTCP4`, IPv4-only), "tcp6" (`fiber.NetworkTCP6`, IPv6-only), "unix" (`fiber.NetworkUnix`, Unix Domain Sockets). WARNING: When prefork is set to true, only "tcp4" and "tcp6" can be chosen. | `tcp4` | | PreforkRecoverInterval | `time.Duration` | Delays the respawn of a crashed child process by this duration. Only applies when prefork is enabled. | `0` (respawn immediately) | | PreforkRecoverThreshold | `int` | Defines the maximum number of child process restarts after crashes before the prefork master exits with an error. Only applies when prefork is enabled. | `max(1, runtime.GOMAXPROCS(0) / 2)` | | PreforkShutdownGracePeriod | `time.Duration` | How long the prefork master waits for child processes to exit after SIGTERM before sending SIGKILL during shutdown. On Windows children are always killed immediately. Only applies when prefork is enabled. | `5 * time.Second` | | PreforkLogger | `PreforkLogger` | Sets a custom logger for the prefork process manager. It is any value with a `Printf(format string, args ...any)` method, such as a `*log.Logger`. Only applies when prefork is enabled. | Fiber logger | | UnixSocketFileMode | `os.FileMode` | FileMode to set for Unix Domain Socket (ListenerNetwork must be "unix") | `0770` | | TLSConfigFunc | `func(tlsConfig *tls.Config)` | Allows customizing `tls.Config` as you want. Ignored when `TLSConfig` is set. | `nil` | | TLSConfig | `*tls.Config` | Recommended base TLS configuration (cloned). Use for external certificate providers via `GetCertificate`. When set, other TLS fields are ignored. | `nil` | | AutoCertManager | `*autocert.Manager` | Manages TLS certificates automatically using the ACME protocol. Enables integration with Let's Encrypt or other ACME-compatible providers. Combining it with `CertFile` or `CertKeyFile` makes `Listen` return `fiber.ErrAutoCertWithCertFile`, unless `TLSConfig` is set, which makes Fiber ignore the other TLS fields. | `nil` | | TLSMinVersion | `uint16` | Allows customizing the TLS minimum version. Only `tls.VersionTLS12` and `tls.VersionTLS13` are accepted; `Listen` panics for any other value. Ignored when `TLSConfig` is set. | `tls.VersionTLS12` | #### Listen Listen serves HTTP requests from the given address. ```go title="Signature" func (app *App) Listen(addr string, config ...ListenConfig) error ``` ```go title="Basic Listen usage" // Listen on port :8080 app.Listen(":8080") // Listen on port :8080 with Prefork app.Listen(":8080", fiber.ListenConfig{EnablePrefork: true}) // Custom host app.Listen("127.0.0.1:8080") ``` ##### Prefork Prefork is a feature that allows you to spawn multiple Go processes listening on the same port. This can be useful for scaling across multiple CPU cores. ```go title="Prefork listener" app.Listen(":8080", fiber.ListenConfig{EnablePrefork: true}) ``` Depending on the operating system, prefork can distribute incoming connections between the spawned processes and allow more requests to be handled simultaneously. On Linux, prefork typically relies on the `SO_REUSEPORT` socket option for kernel-assisted load distribution across workers. On Windows, Fiber falls back to `SO_REUSEADDR`; this is not a functional equivalent to Linux `SO_REUSEPORT` as it lacks native load balancing and may allow other processes to bind to the same port. Operators should validate this behavior against their security and availability requirements. ###### Shutdown Each prefork process handles shutdown on its own. A `GracefulContext` canceled in a child, even while that child is still starting up, drains that child's connections, honoring `ShutdownTimeout`. Canceled in the master, it only runs the master's shutdown hooks: the master never serves requests itself and keeps supervising the children until it exits, and a child whose master is gone exits immediately, without draining. The master binds no listener, so for a context that is already done it runs those hooks at once, even if starting the children then fails. To stop a prefork deployment gracefully, deliver the signal to every process, for example to the whole process group as `systemd` does by default, so that each child's `GracefulContext` observes it. Explicit `Shutdown*` calls behave the same way: they only affect the process they run in. ###### Security Considerations Prefork changes the port-ownership model from strict single-owner binding to an intentional multi-listener setup. In shared hosts, a local co-resident attacker with sufficient privileges may be able to race for shared binds or receive a portion of traffic, depending on platform behavior and user boundaries. - Run prefork only within a trusted boundary (same deployment unit / same trust domain). - Use a dedicated service account for Fiber workers; avoid broad shared-user deployments. - Prefer container or VM isolation and avoid shared host namespaces for unrelated workloads. - If strict single-owner port semantics are required, run Fiber without prefork. ##### TLS Prefer `TLSConfig` for TLS configuration so you can fully control certificates and settings. When `TLSConfig` is set, Fiber ignores `CertFile`, `CertKeyFile`, `CertClientFile`, `TLSMinVersion`, `AutoCertManager`, and `TLSConfigFunc`. TLS serves HTTPs requests from the given address using certFile and keyFile paths as TLS certificate and key file. ```go title="TLS with cert and key files" app.Listen(":443", fiber.ListenConfig{CertFile: "./cert.pem", CertKeyFile: "./cert.key"}) ``` ##### TLS with client CA certificate `CertClientFile` only configures the client CA for mTLS when using `CertFile`/`CertKeyFile`. If `TLSConfig` is set, `CertClientFile` is ignored, so configure client CAs in the provided `tls.Config` instead. ```go title="TLS with client CA certificate" app.Listen(":443", fiber.ListenConfig{ CertFile: "./cert.pem", CertKeyFile: "./cert.key", CertClientFile: "./ca-chain-cert.pem", }) ``` ##### TLS AutoCert support (ACME / Let's Encrypt) Provides automatic access to certificates management from Let's Encrypt and any other ACME-based providers. ```go title="AutoCert (ACME) configuration" // Certificate manager certManager := &autocert.Manager{ Prompt: autocert.AcceptTOS, // Replace with your domain name HostPolicy: autocert.HostWhitelist("example.com"), // Folder to store the certificates Cache: autocert.DirCache("./certs"), } app.Listen(":444", fiber.ListenConfig{ AutoCertManager: certManager, }) ``` ##### Precedence and conflicts - `TLSConfig` is preferred and ignores `CertFile`/`CertKeyFile`, `CertClientFile`, `AutoCertManager`, `TLSMinVersion`, and `TLSConfigFunc`. - `AutoCertManager` cannot be combined with `CertFile`/`CertKeyFile`: `Listen` returns `fiber.ErrAutoCertWithCertFile`. ##### TLS with external certificate provider Use `TLSConfig` to supply a base `tls.Config` that can fetch certificates at runtime. `TLSConfig` is cloned and used as-is. ```go title="TLSConfig with dynamic certificate provider" app.Listen(":443", fiber.ListenConfig{ TLSConfig: &tls.Config{ GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) { return myProvider.Certificate(info.ServerName) }, }, }) ``` ##### Mutual TLS with TLSConfig Use `TLSConfig` to configure mutual TLS by setting `ClientAuth` to a Require mode and `ClientCAs` to the roots it verifies against. This replaces `CertClientFile` when you manage TLS configuration directly. ```go title="TLSConfig with client CA pool" certPEM := []byte(certPEMString) keyPEM := []byte(keyPEMString) caPEM := []byte(caPEMString) cert, err := tls.X509KeyPair(certPEM, keyPEM) if err != nil { log.Fatal(err) } clientCAs := x509.NewCertPool() if ok := clientCAs.AppendCertsFromPEM(caPEM); !ok { log.Fatal("failed to append client CA") } app.Listen(":443", fiber.ListenConfig{ TLSConfig: &tls.Config{ Certificates: []tls.Certificate{cert}, ClientAuth: tls.RequireAndVerifyClientCert, ClientCAs: clientCAs, }, }) ``` Load certificates from memory or environment variables and provide them via `TLSConfig`. ```go title="TLSConfig with in-memory certificate" certPEM := []byte(certPEMString) keyPEM := []byte(keyPEMString) cert, err := tls.X509KeyPair(certPEM, keyPEM) if err != nil { log.Fatal(err) } app.Listen(":443", fiber.ListenConfig{ TLSConfig: &tls.Config{ Certificates: []tls.Certificate{cert}, }, }) ``` ```go title="TLSConfig with certificate from environment" certPEM := []byte(os.Getenv("TLS_CERT_PEM")) keyPEM := []byte(os.Getenv("TLS_KEY_PEM")) cert, err := tls.X509KeyPair(certPEM, keyPEM) if err != nil { log.Fatal(err) } app.Listen(":443", fiber.ListenConfig{ TLSConfig: &tls.Config{ Certificates: []tls.Certificate{cert}, }, }) ``` #### Listener You can pass your own [`net.Listener`](https://pkg.go.dev/net/#Listener) using the `Listener` method. This method can be used to enable **TLS/HTTPS** with a custom tls.Config. ```go title="Signature" func (app *App) Listener(ln net.Listener, config ...ListenConfig) error ``` ```go title="Examples" ln, _ := net.Listen("tcp", ":3000") cer, _:= tls.LoadX509KeyPair("server.crt", "server.key") ln = tls.NewListener(ln, &tls.Config{Certificates: []tls.Certificate{cer}}) app.Listener(ln) ``` `Listener` serves the listener exactly as supplied. It ignores the TLS fields of the `ListenConfig` (`CertFile`, `CertKeyFile`, `CertClientFile`, `TLSConfig`, `TLSConfigFunc`, `AutoCertManager` and `TLSMinVersion`), `EnablePrefork` and `ListenerAddrFunc`, and it logs a warning when it is given a TLS field or `EnablePrefork`. To serve TLS, wrap the listener with `tls.NewListener` as above; to require client certificates, give that `tls.Config` a `ClientAuth` Require mode and the `ClientCAs` to verify against. `GracefulContext`, `ShutdownTimeout`, `BeforeServeFunc`, `DisableStartupMessage` and `EnablePrintRoutes` work as they do for `Listen`. ### Server Server returns the underlying [fasthttp server](https://godoc.org/github.com/valyala/fasthttp#Server). Fiber wraps its `HeaderReceived` callback when it starts serving, and keeps calling the one you set, including one set in `BeforeServeFunc`. ```go title="Signature" func (app *App) Server() *fasthttp.Server ``` ```go title="Examples" func main() { app := fiber.New() app.Server().MaxConnsPerIP = 1 // ... } ``` ### Server Shutdown Shutdown gracefully shuts down the server without interrupting any active connections. Shutdown works by first closing all open listeners and then waits indefinitely for all connections to return to idle before shutting down. ShutdownWithTimeout stops waiting once the timeout expires and returns `context.DeadlineExceeded`; it does not close connections that are still active. ShutdownWithContext does the same, bounded by the context instead of a timeout. Shutdown hooks will still be executed, even if an error occurs during the shutdown process, as they are deferred to ensure cleanup happens regardless of errors. ```go func (app *App) Shutdown() error func (app *App) ShutdownWithTimeout(timeout time.Duration) error func (app *App) ShutdownWithContext(ctx context.Context) error ``` :::info How long does shutdown wait? `Shutdown` / `ShutdownWithTimeout` / `ShutdownWithContext` wait for **in-flight handlers** (including streaming responses written with `SendStream` / `SendStreamWriter`) until they return or the timeout fires. The timeout is measured from the start of shutdown; if a handler finishes before the deadline, shutdown returns without waiting for the full timeout. Call `Shutdown*` from a separate goroutine while `Listen` / `Listener` runs, and keep the process alive until `Shutdown*` returns. Set a non-zero `ReadTimeout`: a connection that has not sent its first request yet, such as a browser preconnect, otherwise holds the shutdown until the timeout, or forever with `Shutdown`. Long-lived streams should end once `c.RequestCtx().Done()` is closed. `ListenConfig.ShutdownTimeout` is applied only when Fiber runs its **built-in** graceful shutdown path (for example with `GracefulContext`). If you call `app.ShutdownWithTimeout` yourself, that argument is the one that applies. On that built-in path `Listen` / `Listener` returns only once the shutdown has finished, so the caller may exit as soon as it returns (a prefork master is the exception: it keeps supervising its children, see [Prefork](#prefork)); an explicit `Shutdown*` call still needs the process kept alive until it returns. That wait includes the `OnPreShutdown` and `OnPostShutdown` hooks, which `ShutdownTimeout` does not bound. The `OnPreShutdown` and `OnPostShutdown` hooks run exactly once per shutdown, whether it was started by the context or by an explicit `Shutdown*` call, and not at all when `Listen` cannot bind its address, even if the `GracefulContext` is already done. When `Listen` fails after that, for example because `BeforeServeFunc` returned an error, they run only if a `GracefulContext` has already started the shutdown. Outside the prefork master, a `GracefulContext` can also be done before the server serves: canceled while the app is still starting up, or already done when `Listen` / `Listener` is called. The shutdown starts as soon as the app has its listener (`Listen` has bound its address, or `Listener` was handed one) and `Listen` / `Listener` returns once it has finished, so a signal that arrives during startup cannot leave the process serving. The startup still runs to completion, so the `OnListen` hooks, the startup message and `BeforeServeFunc` can run after the shutdown hooks; do not count on services or `SharedStorage` being open in them. As with a server that already serves, connections are accepted until the `OnPreShutdown` hooks are done; a server that starts to serve after that point accepts none. With prefork enabled, `Shutdown*` and `GracefulContext` act only on the process they run in; see [Prefork](#prefork). ::: ### Helper functions #### NewError NewError creates a new `*fiber.Error` with an optional message. Without one, the message is the standard text of the status code. ```go title="Signature" func NewError(code int, message ...string) *Error ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { return fiber.NewError(782, "Custom error message") }) ``` #### NewErrorf NewErrorf creates a new `*fiber.Error` with an optional formatted message. With two or more arguments, the first is the format string. ```go title="Signature" func NewErrorf(code int, message ...any) *Error ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { return fiber.NewErrorf(782, "Custom error %s", "message") }) ``` #### IsChild IsChild determines if the current process is a result of Prefork. ```go title="Signature" func IsChild() bool ``` ```go title="Example" // Config app app := fiber.New() app.Get("/", func(c fiber.Ctx) error { if !fiber.IsChild() { fmt.Println("I'm the parent process") } else { fmt.Println("I'm a child process") } return c.SendString("Hello, World!") }) // ... // With prefork enabled, the parent process will spawn child processes app.Listen(":8080", fiber.ListenConfig{EnablePrefork: true}) ``` --- ## 🎣 Hooks Fiber lets you run custom callbacks at specific points in the routing lifecycle. Available hooks include: - [OnRoute](#onroute) - [OnName](#onname) - [OnGroup](#ongroup) - [OnGroupName](#ongroupname) - [OnListen](#onlisten) - [OnPreStartupMessage/OnPostStartupMessage](#onprestartupmessageonpoststartupmessage) - [ListenData](#listendata) - [OnFork](#onfork) - [OnPreShutdown](#onpreshutdown) - [OnPostShutdown](#onpostshutdown) - [OnMount](#onmount) Hooks of one kind run in the order you registered them. At startup, `OnListen` runs first, then `OnPreStartupMessage`, the banner and `OnPostStartupMessage`. At shutdown, `OnPreShutdown` runs before the server drains its connections and `OnPostShutdown` after. A `GracefulContext` that is done while the app is still starting up starts the shutdown as soon as the app has its listener, so the two sequences can overlap: the shutdown hooks can run while the startup hooks are still running, also for a context that was already done when `Listen` was called (see [Server Shutdown](./fiber.md#server-shutdown)). What a returned error does depends on the hook; see [Errors returned by hooks](#errors-returned-by-hooks). ### Constants ```go // Handlers define functions to create hooks for Fiber. type OnRouteHandler = func(Route) error type OnNameHandler = OnRouteHandler type OnGroupHandler = func(Group) error type OnGroupNameHandler = OnGroupHandler type OnListenHandler = func(ListenData) error type OnForkHandler = func(int) error type OnPreStartupMessageHandler = func(*PreStartupMessageData) error type OnPostStartupMessageHandler = func(*PostStartupMessageData) error type OnPreShutdownHandler = func() error type OnPostShutdownHandler = func(error) error type OnMountHandler = func(*App) error ``` ### OnRoute Runs after each route is registered. The callback receives the route so you can inspect its properties. A registration that covers several methods runs the hook once per method: `Add` runs it once for each method it lists, and `Use` and `All` once for each entry of `RequestMethods`. Fiber also runs it for every `HEAD` route it generates for a `GET` route, which happens when the app starts or when [`RebuildTree`](./app.md#rebuildtree) runs. ```go title="Signature" func (h *Hooks) OnRoute(handler ...OnRouteHandler) ``` ### OnName Runs when a route is named. The callback receives the route. :::caution `OnName` only works with named routes, not groups. ::: ```go title="Signature" func (h *Hooks) OnName(handler ...OnNameHandler) ``` **OnName Example** ```go package main import ( "fmt" "github.com/gofiber/fiber/v3" ) func main() { app := fiber.New() app.Get("/", func(c fiber.Ctx) error { return c.SendString(c.Route().Name) }).Name("index") app.Hooks().OnName(func(r fiber.Route) error { fmt.Print("Name: " + r.Name + ", ") return nil }) app.Hooks().OnName(func(r fiber.Route) error { fmt.Print("Method: " + r.Method + "\n") return nil }) app.Get("/add/user", func(c fiber.Ctx) error { return c.SendString(c.Route().Name) }).Name("addUser") app.Delete("/destroy/user", func(c fiber.Ctx) error { return c.SendString(c.Route().Name) }).Name("destroyUser") app.Listen(":5000") } // Results: // Name: addUser, Method: GET // Name: destroyUser, Method: DELETE ``` ### OnGroup Runs after each group is registered. The callback receives the group. ```go title="Signature" func (h *Hooks) OnGroup(handler ...OnGroupHandler) ``` ### OnGroupName Runs when a group is named. The callback receives the group. :::caution `OnGroupName` only works with named groups, not routes. ::: ```go title="Signature" func (h *Hooks) OnGroupName(handler ...OnGroupNameHandler) ``` ### OnListen Runs when the app starts listening via `Listen` or `Listener`, before the startup message is printed. With prefork enabled it runs once, in the master process after the child processes have started; the children do not run it. ```go title="Signature" func (h *Hooks) OnListen(handler ...OnListenHandler) ``` **OnListen Example** ```go package main import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/log" ) func main() { app := fiber.New() app.Hooks().OnListen(func(listenData fiber.ListenData) error { scheme := "http" if listenData.TLS { scheme = "https" } log.Info(scheme + "://" + listenData.Host + ":" + listenData.Port) return nil }) app.Listen(":5000", fiber.ListenConfig{DisableStartupMessage: true}) } ``` ### OnPreStartupMessage/OnPostStartupMessage Use `OnPreStartupMessage` to tweak the banner before Fiber prints it, and `OnPostStartupMessage` to run logic after the banner is printed (or skipped). You can use some helper functions to customize the banner inside the `OnPreStartupMessage` hook. Both hooks run even when `ListenConfig.DisableStartupMessage` is set, and with prefork they run in the master process only. ```go title="Signatures" // AddInfo adds an informational entry to the startup message with "INFO" label. func (sm *PreStartupMessageData) AddInfo(key, title, value string, priority ...int) // AddWarning adds a warning entry to the startup message with "WARN" label. func (sm *PreStartupMessageData) AddWarning(key, title, value string, priority ...int) // AddError adds an error entry to the startup message with "ERROR" label. func (sm *PreStartupMessageData) AddError(key, title, value string, priority ...int) // EntryKeys returns all entry keys currently present in the startup message. func (sm *PreStartupMessageData) EntryKeys() []string // ResetEntries removes all existing entries from the startup message. func (sm *PreStartupMessageData) ResetEntries() // DeleteEntry removes a specific entry from the startup message by its key. func (sm *PreStartupMessageData) DeleteEntry(key string) ``` - Assign `sm.BannerHeader` to override the ASCII art banner. Leave it empty to use the default banner provided by Fiber. - Set `sm.PreventDefault = true` to suppress the built-in banner without affecting other hooks. - `PostStartupMessageData` reports whether the banner was skipped via the `Disabled`, `IsChild`, and `Prevented` flags. - Entries print from the highest `priority` to the lowest. An entry added without a priority gets `-1`, so it prints after the built-in ones. - Adding an entry with a `key` that already exists replaces that entry, so you can change a built-in one. The built-in entries use these keys and priorities: | Key | Entry | Priority | | --- | --- | --- | | `server_address` | Server started on | `10` | | `app_name` | Application name, only when `AppName` is set | `9` | | `total_handlers` | Total handlers | `8` | | `prefork` | Prefork | `7` when enabled, `6` when disabled | | `pid` | PID | `5` | | `process_count` | Total process count | `4` | #### Startup Message Customization ```go title="Customize the startup message" package main import ( "fmt" "os" "github.com/gofiber/fiber/v3" ) func main() { app := fiber.New() app.Hooks().OnPreStartupMessage(func(sm *fiber.PreStartupMessageData) error { sm.BannerHeader = "FOOBER " + sm.Version + "\n-------" // Optional: you can also remove old entries // sm.ResetEntries() sm.AddInfo("git-hash", "Git hash", os.Getenv("GIT_HASH")) sm.AddInfo("prefork", "Prefork", fmt.Sprintf("%v", sm.Prefork), 15) return nil }) app.Hooks().OnPostStartupMessage(func(sm *fiber.PostStartupMessageData) error { if !sm.Disabled && !sm.IsChild && !sm.Prevented { fmt.Println("startup completed") } return nil }) app.Listen(":5000") } ``` #### ListenData `ListenData` exposes runtime metadata about the listener: | Field | Type | Description | | --- | --- | --- | | `Host` | `string` | Resolved hostname or IP address. | | `Port` | `string` | The bound port. | | `TLS` | `bool` | Indicates whether TLS is enabled. | | `Version` | `string` | Fiber version reported in the startup banner. | | `AppName` | `string` | Application name from the configuration. | | `HandlerCount` | `int` | Total registered handler count. | | `ProcessCount` | `int` | Number of processes Fiber will use. | | `PID` | `int` | Current process identifier. | | `Prefork` | `bool` | Whether prefork is enabled. | | `ChildPIDs` | `[]int` | Child process identifiers when preforking. | | `ColorScheme` | [`Colors`](https://github.com/gofiber/fiber/blob/main/color.go) | Active color scheme for the startup message. | ### OnFork Runs in the prefork master process each time it starts a child process, including a child it starts again after a crash. The callback receives the PID of that child. The children themselves do not run it. ```go title="Signature" func (h *Hooks) OnFork(handler ...OnForkHandler) ``` ### OnPreShutdown Runs when a shutdown begins, before the server stops accepting connections. It runs once per shutdown, whether `Shutdown`, `ShutdownWithTimeout`, `ShutdownWithContext` or a `GracefulContext` started it. ```go title="Signature" func (h *Hooks) OnPreShutdown(handler ...OnPreShutdownHandler) ``` ### OnPostShutdown Runs once the shutdown has finished, even when it failed, and exactly once per shutdown. The callback receives the error the shutdown returned: `nil` after a clean shutdown, or for example `context.DeadlineExceeded` when the timeout expired while connections were still active. ```go title="Signature" func (h *Hooks) OnPostShutdown(handler ...OnPostShutdownHandler) ``` :::note Fiber registers its own `OnPostShutdown` hook before yours. It terminates the configured [services](./services.md) and then closes the `SharedStorage`, so both are already gone when your hooks run. ::: ### OnMount Fires after a sub-app is mounted on a parent. Register it on the sub-app: the parent app is passed to the callback, and it works for app, group and `Domain` mounts. ```go title="Signature" func (h *Hooks) OnMount(handler ...OnMountHandler) ``` **OnMount Example** ```go package main import ( "fmt" "github.com/gofiber/fiber/v3" ) func main() { app := fiber.New() app.Get("/", testSimpleHandler).Name("x") subApp := fiber.New() subApp.Get("/test", testSimpleHandler) subApp.Hooks().OnMount(func(parent *fiber.App) error { fmt.Print("Mount path of sub-app: " + subApp.MountPath()) // Additional custom logic... return nil }) app.Use("/sub", subApp) } func testSimpleHandler(c fiber.Ctx) error { return c.SendString("Hello, Fiber!") } // Result: // Mount path of sub-app: /sub ``` :::caution OnName, OnRoute, OnGroup, and OnGroupName are mount-sensitive. When you mount a sub-app that registers these hooks, the route and group paths it reports include the mount prefix, but only for routes and groups registered after the mount. The parent app's hooks do not run for the routes of its sub-apps. ::: ### Errors returned by hooks Every hook handler returns an `error`. What Fiber does with it depends on the hook: | Hooks | When a handler returns an error | | --- | --- | | `OnRoute`, `OnName`, `OnGroup`, `OnGroupName`, `OnMount`, `OnListen` | Fiber panics with the error, so the route registration, the mount or the start fails loudly. | | `OnPreStartupMessage`, `OnPostStartupMessage` | Fiber logs the error and skips the remaining hooks of that kind. The app keeps starting. | | `OnPreShutdown`, `OnPostShutdown`, `OnFork` | Fiber logs the error and still runs the remaining hooks. | --- ## 📃 Log Logs help you observe program behavior, diagnose issues, and trigger alerts. Structured logs improve searchability and speed up troubleshooting. Fiber logs to standard error by default and exposes global helpers such as `log.Info`, `log.Errorf`, and `log.Warnw`. ### Log Levels ```go const ( LevelTrace Level = iota LevelDebug LevelInfo LevelWarn LevelError LevelFatal LevelPanic ) ``` ### Custom Log Fiber provides the generic `AllLogger[T]` interface for adapting various log libraries. A logger passed to `log.SetLogger` must implement every method of these interfaces. ```go type Logger interface { Trace(v ...any) Debug(v ...any) Info(v ...any) Warn(v ...any) Error(v ...any) Fatal(v ...any) Panic(v ...any) } type FormatLogger interface { Tracef(format string, v ...any) Debugf(format string, v ...any) Infof(format string, v ...any) Warnf(format string, v ...any) Errorf(format string, v ...any) Fatalf(format string, v ...any) Panicf(format string, v ...any) } type WithLogger interface { Tracew(msg string, keysAndValues ...any) Debugw(msg string, keysAndValues ...any) Infow(msg string, keysAndValues ...any) Warnw(msg string, keysAndValues ...any) Errorw(msg string, keysAndValues ...any) Fatalw(msg string, keysAndValues ...any) Panicw(msg string, keysAndValues ...any) } type CommonLogger interface { Logger FormatLogger WithLogger } type ConfigurableLogger[T any] interface { // SetLevel sets logging level. SetLevel(level Level) // SetOutput sets the logger output. SetOutput(w io.Writer) // Logger returns the logger instance. Logger() T } type AllLogger[T any] interface { CommonLogger ConfigurableLogger[T] // WithContext returns a new logger with the given context. WithContext(ctx any) CommonLogger } ``` ### Print Log **Note:** The Fatal level methods terminate the program after printing the log message, and they do so even when `SetLevel` filters the message out. The Panic level methods print the message and then panic. Please use them with caution. #### Basic Logging Call level-specific methods directly. The default logger writes the standard `log` header (date, time with microseconds, and `file:line`), then the level and the message, for example `2026/10/12 14:30:44.616405 main.go:46: [Info] Hello, World!`. ```go log.Trace("Entering the handler.") log.Info("Hello, World!") log.Debug("Are you OK?") log.Info("42 is the answer to life, the universe, and everything") log.Warn("We are under attack!") log.Error("Houston, we have a problem.") log.Fatal("So Long, and Thanks for All the Fish.") log.Panic("The system is down.") ``` #### Formatted Logging Append `f` to format the message. ```go log.Tracef("Entering %s", "the handler") log.Debugf("Hello %s", "boy") log.Infof("%d is the answer to life, the universe, and everything", 42) log.Warnf("We are under attack, %s!", "boss") log.Errorf("%s, we have a problem.", "John Smith") log.Fatalf("So Long, and Thanks for All the %s.", "fish") log.Panicf("The system is %s.", "down") ``` #### Key-Value Logging Key-value helpers log structured fields after the message, as `key=value` pairs; a key without a value is logged as `key=KEYVALS UNPAIRED`. ```go log.Tracew("", "step", "init") log.Debugw("", "greeting", "Hello", "target", "boy") log.Infow("", "number", 42) log.Warnw("", "job", "boss") log.Errorw("", "name", "John Smith") log.Fatalw("", "fruit", "fish") log.Panicw("", "system", "down") ``` ### Global Log Fiber also exposes a global logger for quick messages. ```go import "github.com/gofiber/fiber/v3/log" log.Info("info") log.Warn("warn") ``` The example uses `log.DefaultLogger`, which writes to stderr. The [contrib](https://github.com/gofiber/contrib) repo offers adapters like `fiberzap` and `fiberzerolog`, or you can register your own with `log.SetLogger`. `SetLogger` is not concurrent safe, so call it once at startup, before anything else logs. Here's an example using a custom logger: ```go import ( "log" "os" fiberlog "github.com/gofiber/fiber/v3/log" ) var _ fiberlog.AllLogger[*log.Logger] = (*customLogger)(nil) type customLogger struct { stdlog *log.Logger } // Implement required methods for the AllLogger interface... // Inject your custom logger fiberlog.SetLogger[*log.Logger](&customLogger{ stdlog: log.New(os.Stdout, "CUSTOM ", log.LstdFlags), }) // Retrieve the underlying *log.Logger for direct use std := fiberlog.DefaultLogger[*log.Logger]().Logger() std.Println("custom logging") ``` ### Set Level `log.SetLevel` sets the minimum level that will be output. The default is `LevelTrace`. **Note:** This method is not concurrent safe. ```go import "github.com/gofiber/fiber/v3/log" log.SetLevel(log.LevelInfo) ``` Setting the log level allows you to control the verbosity of the logs, filtering out messages below the specified level. ### Set Output `log.SetOutput` sets where logs are written. By default, they go to standard error. #### Writing Logs to Stderr `log.SetOutput(os.Stderr)` redirects the default logger to standard error. ```go import ( "os" fiberlog "github.com/gofiber/fiber/v3/log" ) fiberlog.SetOutput(os.Stderr) ``` This lets you route logs to a file, service, or any destination. #### Writing Logs to a File To write to a file such as `test.log`: ```go // Output to ./test.log file f, err := os.OpenFile("test.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) if err != nil { log.Fatal("Failed to open log file:", err) } log.SetOutput(f) ``` #### Writing Logs to Both Console and File Write to both `test.log` and `stdout`: ```go // Output to ./test.log file file, err := os.OpenFile("test.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) if err != nil { log.Fatal("Failed to open log file:", err) } iw := io.MultiWriter(os.Stdout, file) log.SetOutput(iw) ``` ### Bind Context Bind a logger to a context with `log.WithContext`, which returns a `CommonLogger` tied to that context. ```go commonLogger := log.WithContext(ctx) commonLogger.Info("info") ``` Context binding can render request-specific data for easier tracing. The default context format is `log.DefaultFormat` (the empty string), so `log.WithContext(ctx)` adds no fields until you configure a format with `log.SetContextTemplate` (or its `MustSetContextTemplate` panic-on-error variant). Passing `nil` to `log.WithContext` binds no context, so the lines it logs carry no contextual fields at all. `SetContextTemplate` configures Fiber's built-in default logger. Custom loggers registered with `SetLogger` keep full control over their own `WithContext` behavior and should implement equivalent enrichment themselves when needed. ```go import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/log" "github.com/gofiber/fiber/v3/middleware/requestid" ) app.Use(requestid.New()) log.MustSetContextTemplate(log.ContextConfig{Format: log.RequestIDFormat}) app.Get("/", func(c fiber.Ctx) error { log.WithContext(c).Info("start") return c.SendString("Hello, World!") }) ``` The `requestid`, `basicauth`, `keyauth`, `csrf` and `session` middleware register their log context tags automatically, the first time their `New` function is called. For example, `requestid.New()` fills in `${requestid}` and `${request-id}`. Other middleware that stores request values, such as `paginate`, registers no tag. Fiber pre-registers the built-in tag names (`requestid`, `request-id`, `username`, `api-key`, `csrf-token`, and `session-id`) as renderers that write nothing, so a format that references `${requestid}` still compiles, and renders an empty string, when `requestid.New()` is not in the chain. Registering a tag also updates the active format, so it does not matter whether `SetContextTemplate` or the middleware's `New` runs first. Use `log.WithContext(c)` inside handlers when you want tags to read values stored by Fiber middleware. Passing `c.Context()` only exposes values propagated into the standard request context, which `StoreInContext` does only when `Config.PassLocalsToContext` is enabled. :::tip Cross-reference: the access-log integration uses the same tag names — see [middleware/logger](../middleware/logger.md#config) for the request-time format. ::: #### Glossary - **Format** — the string with `${...}` placeholders, e.g. `"[${requestid}] "`. - **Template** — the compiled, reusable form of a format produced by `SetContextTemplate`. - **Tag** — a single `${name}` (bare) or `${name:param}` (parametric) placeholder inside a format. #### Signatures ```go // Configure the active context template (or pass log.ContextConfig{} to disable). // It returns an error when the format cannot be compiled, and ErrContextTagReserved // when CustomTags tries to override TagContextValue. func SetContextTemplate(config ContextConfig) error func MustSetContextTemplate(config ContextConfig) // Register a tag globally. The new renderer applies to the active format and to // subsequent SetContextTemplate calls. It returns ErrContextTagInvalid for an // empty name or a nil function, and ErrContextTagReserved for the reserved // TagContextValue ("value:") name. The Must variant panics with the same errors. func RegisterContextTag(tag string, fn ContextTagFunc) error func MustRegisterContextTag(tag string, fn ContextTagFunc) // Bind a context to the default logger. ctx accepts fiber.Ctx, // *fasthttp.RequestCtx, context.Context, or any value exposing // Value(key any)/UserValue(key any). func WithContext(ctx any) CommonLogger // Public types referenced by the API above. type ContextConfig struct { CustomTags map[string]ContextTagFunc Format string } type ContextData struct{} type Buffer = logtemplate.Buffer // ContextTagFunc has the shape // func(output Buffer, ctx any, data *ContextData, extraParam string) (int, error) type ContextTagFunc = logtemplate.Func[any, ContextData] // Format constants. const ( DefaultFormat = "" RequestIDFormat = "[${requestid}] " KeyValueFormat = "request-id=${request-id} username=${username} api-key=${api-key} csrf-token=${csrf-token} session-id=${session-id} " ) // Tag names. The first six are the tags that Fiber's middleware register. const ( TagRequestID = "requestid" TagRequestIDDashed = "request-id" TagUsername = "username" TagAPIKey = "api-key" TagCSRFToken = "csrf-token" TagSessionID = "session-id" TagContextValue = "value:" ) var ( // Returned by RegisterContextTag for an empty tag name or a nil function. ErrContextTagInvalid = errors.New("log: context tag name and function are required") // Returned by RegisterContextTag and SetContextTemplate for the reserved TagContextValue tag. ErrContextTagReserved = errors.New("log: context tag is reserved") ) ``` #### Context Formats | Format Constant | Format String | Description | | :-- | :-- | :-- | | `DefaultFormat` | `""` | Disables contextual fields. | | `RequestIDFormat` | `"[${requestid}] "` | Prepends the request ID when the requestid middleware is used. | | `KeyValueFormat` | `"request-id=${request-id} username=${username} api-key=${api-key} csrf-token=${csrf-token} session-id=${session-id} "` | Prepends common middleware context values as key/value fields. Sensitive values are redacted by the registering middleware. | #### Context Tags | Tag | Constant | Source | | :-- | :-- | :-- | | `${requestid}` | `TagRequestID` | `requestid` middleware | | `${request-id}` | `TagRequestIDDashed` | `requestid` middleware | | `${username}` | `TagUsername` | `basicauth` middleware — written **in clear text** for audit-log use cases. Avoid this tag if your usernames are PII. | | `${api-key}` | `TagAPIKey` | `keyauth` middleware, redacted | | `${csrf-token}` | `TagCSRFToken` | `csrf` middleware, redacted | | `${session-id}` | `TagSessionID` | `session` middleware, redacted | | `${value:key}` | `TagContextValue` | Any bound value with `Value(key)` or `UserValue(key)` lookup methods. `key` is passed as a `string`, so values stored under a custom key type need a [custom tag](#custom-context-tags). | Redacted values keep their first 4 bytes followed by `****`; a non-empty value shorter than 8 bytes is replaced by `****` entirely, and an empty value stays empty. :::caution `${value:KEY}` looks up arbitrary context values. CR, LF, NUL, and other ASCII control bytes (except tab) are replaced with spaces before they reach the log line, so attacker-controlled values cannot forge log lines via header smuggling. The lookup still writes the value verbatim apart from that scrub — strip or hash sensitive fields before storing them on the context if you do not want them in operator logs. ::: #### Custom Context Tags Register custom tags with `log.RegisterContextTag`, then reference them from a format passed to `SetContextTemplate`. The built-in `${value:key}` tag is reserved for context-value lookups and cannot be overridden. A tag function receives the buffer to write to, the value passed to `WithContext`, a `*log.ContextData` (reserved, currently empty), and the text after the colon for a parametric tag such as `${value:key}`. It returns the number of bytes written, as `output.WriteString` does. Registering a name again replaces its renderer, and registration is global to the process. Tags passed in `ContextConfig.CustomTags` to `SetContextTemplate` are registered the same way and stay registered for later calls. `RegisterContextTag` returns `ErrContextTagInvalid` for an empty name or a `nil` function, and both `RegisterContextTag` and `SetContextTemplate` return `ErrContextTagReserved` for `value:`. ```go type tenantContextKey struct{} var tenantKey tenantContextKey log.MustRegisterContextTag("tenant", func(output log.Buffer, ctx any, _ *log.ContextData, _ string) (int, error) { tenant, _ := fiber.ValueFromContext[string](ctx, tenantKey) return output.WriteString(tenant) }) log.MustSetContextTemplate(log.ContextConfig{Format: "[${tenant}] "}) app.Use(func(c fiber.Ctx) error { fiber.StoreInContext(c, tenantKey, "acme") return c.Next() }) ``` :::note Register tags **before** referencing them in `SetContextTemplate`. Compiling a format that references an unregistered name returns an error whose message reads `logtemplate: unknown tag: "tenant"` (or panics from `MustSetContextTemplate`). Writing `${value}` without a key fails the same way, with a hint to use `${value:PARAM}`. ::: If a tag function returns an error while a line is logged, the line carries a `[ctx-render-error: ]` marker after the level, in place of the context fields, with control bytes in the error replaced by spaces, and the message is still logged. ### Logger Use `Logger` to access the underlying logger and call its native methods: ```go logger := fiberlog.DefaultLogger[*log.Logger]() // Get the default logger instance stdlogger := logger.Logger() // stdlogger is *log.Logger stdlogger.SetFlags(0) // Hide timestamp by setting flags to 0 ``` `DefaultLogger` returns `nil` when the active logger does not implement `AllLogger[T]` for the requested `T`, so check the result before calling `Logger()` if you replaced the logger with `SetLogger`. --- ## 🔄 Redirect Redirect helpers send the client to another URL or route. ### Redirect Methods #### To Redirects to a URL built from the given path. Optionally set an HTTP [status](#status). :::info If unspecified, status defaults to **303 See Other**. ::: ```go title="Signature" func (r *Redirect) To(location string) error ``` ```go title="Example" app.Get("/coffee", func(c fiber.Ctx) error { // => HTTP - GET 301 /teapot return c.Redirect().Status(fiber.StatusMovedPermanently).To("/teapot") }) app.Get("/teapot", func(c fiber.Ctx) error { return c.Status(fiber.StatusTeapot).SendString("🍵 short and stout 🍵") }) ``` ```go title="More examples" app.Get("/", func(c fiber.Ctx) error { // => HTTP - GET 303 /foo/bar return c.Redirect().To("/foo/bar") // => HTTP - GET 303 ../login return c.Redirect().To("../login") // => HTTP - GET 303 http://example.com return c.Redirect().To("http://example.com") // => HTTP - GET 301 http://example.com return c.Redirect().Status(301).To("http://example.com") }) ``` #### Route Redirects to a named route with parameters and queries. :::info To send params and queries to a route, use the [`RedirectConfig`](#redirectconfig) struct. ::: ```go title="Signature" func (r *Redirect) Route(name string, config ...RedirectConfig) error ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { // /user/fiber return c.Redirect().Route("user", fiber.RedirectConfig{ Params: fiber.Map{ "name": "fiber", }, }) }) app.Get("/with-queries", func(c fiber.Ctx) error { // /user/fiber?data[0][name]=john&data[0][age]=10&test=doe return c.Redirect().Route("user", fiber.RedirectConfig{ Params: fiber.Map{ "name": "fiber", }, Queries: map[string]string{ "data[0][name]": "john", "data[0][age]": "10", "test": "doe", }, }) }) app.Get("/user/:name", func(c fiber.Ctx) error { return c.SendString(c.Params("name")) }).Name("user") ``` `Route` returns `fiber.ErrNotFound` when no route has the given name. Values in `Queries` are percent-encoded and keys are written as given. The order of the query parameters is not specified. :::note Each `Params` value is automatically percent-encoded with URL path-segment rules. Greedy parameters (`*` and `+`) preserve `/` inside the matched path tail; delimiters such as `?` and `#` remain parameter data instead of adding a query or fragment, and cannot absorb or discard `Queries`. [`Route.URL`](./app.md#getroute) and [`GetRouteURL`](./ctx.md#getrouteurl) apply the same encoding. These take the value as data, not as URL text. Representable values use the same encoding under any configuration. With `UnescapePath` off (the default) `c.Params` returns the value still percent-encoded, so forwarding it straight back encodes the `%` a second time. Turn `UnescapePath` on, or decode with [`url.PathUnescape`](https://pkg.go.dev/net/url#PathUnescape) first. An app with `UnescapePath` enabled decodes `%2F` before route matching, so an ordinary single-segment parameter holding a `/` cannot round-trip and returns `ErrRouteNotRepresentable`. Greedy (`*` or `+`), adjacent single-byte parameters and required parameters with a single-byte non-slash terminator retain their existing slash-matching rules. An optional parameter with such a terminator does not match a `/`, so a decoded `/` in its value returns `ErrRouteNotRepresentable`. Dot-containing values are also rejected when the composed path has a `.` or `..` segment that would be removed during normalization. Surrounding route constants are considered: `:name.txt` with `name="."` remains representable. Pass a different value or use a route that can represent it; do not pre-encode it. ::: #### Back Redirects to the referer. If it's missing, fall back to the provided URL. You can also set the status code. :::info If unspecified, status defaults to **303 See Other**. ::: :::note The referer is only used when it is same-origin with the current request, and the value sent in `Location` is a normalized form of it, not the raw header. The check reads the referer the way a browser does — folding backslashes, dropping ASCII tab, CR and LF, and collapsing a leading run of slashes — so a value like `/\evil.com`, which a browser resolves to `//evil.com`, is not mistaken for a local path. A referer that is missing, cross-origin, or unparsable falls back to the provided URL. If there is no fallback either, `Back` returns `fiber.ErrRedirectBackNoFallback`, an error with status 500. ::: ```go title="Signature" func (r *Redirect) Back(fallback ...string) error ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { return c.SendString("Home page") }) app.Get("/test", func(c fiber.Ctx) error { c.Set("Content-Type", "text/html") return c.SendString(`Back`) }) app.Get("/back", func(c fiber.Ctx) error { return c.Redirect().Back("/") }) ``` ### Controls :::info Methods are **chainable**. ::: #### Status Sets the HTTP status code for the redirect. :::info It is used in conjunction with [**To**](#to), [**Route**](#route), and [**Back**](#back) methods. ::: ```go title="Signature" func (r *Redirect) Status(status int) *Redirect ``` ```go title="Example" app.Get("/coffee", func(c fiber.Ctx) error { // => HTTP - GET 301 /teapot return c.Redirect().Status(fiber.StatusMovedPermanently).To("/teapot") }) ``` #### RedirectConfig Sets the configuration for the redirect. :::info It is used in conjunction with the [**Route**](#route) method. ::: ```go title="Definition" // RedirectConfig is a config to use with Redirect().Route() type RedirectConfig struct { Params fiber.Map // Route parameters Queries map[string]string // Query map } ``` #### Flash Message Similar to [Laravel](https://laravel.com/docs/11.x/redirects#redirecting-with-flashed-session-data), we can flash a message and retrieve it in the next request. :::note Flash messages travel in the `fiber_flash` cookie (`fiber.FlashCookieName`), so only a client that keeps cookies across the redirect (a browser, typically) receives them, and every request is scanned for the cookie. Deployments whose clients keep no cookies can turn the feature off with `fiber.Config{DisableFlashMessages: true}`: `With` and `WithInput` then set no cookie, `Messages` and `OldInput` report nothing, and the scan is skipped. ::: The next request that carries the cookie can read the data, and Fiber expires the cookie on that request, so it is delivered once. The accessors return these types: ```go title="Definition" // FlashMessage is returned by Messages and Message. type FlashMessage struct { Key string Value string Level uint8 } // OldInputData is returned by OldInputs and OldInput. type OldInputData struct { Key string Value string } ``` ##### Messages Retrieve all flash messages. Reading removes them from the request, so a second call returns none. See [With](#with) for details. ```go title="Signature" func (r *Redirect) Messages() []FlashMessage ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { messages := c.Redirect().Messages() return c.JSON(messages) }) ``` ##### Message Get a flash message by key; see [With](#with). It returns an empty `FlashMessage` when the key is not found and, like `Messages`, removes the message it returns. ```go title="Signature" func (r *Redirect) Message(key string) FlashMessage ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { message := c.Redirect().Message("status") return c.SendString(message.Value) }) ``` ##### OldInputs Retrieve stored input data. See [WithInput](#withinput). ```go title="Signature" func (r *Redirect) OldInputs() []OldInputData ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { oldInputs := c.Redirect().OldInputs() return c.JSON(oldInputs) }) ``` ##### OldInput Get stored input data by key; see [WithInput](#withinput). It returns an empty `OldInputData` when the key is not found. ```go title="Signature" func (r *Redirect) OldInput(key string) OldInputData ``` ```go title="Example" app.Get("/name", func(c fiber.Ctx) error { oldInput := c.Redirect().OldInput("name") return c.SendString(oldInput.Value) }) ``` ##### With Send flash messages with `With`. The optional `level` sets the message's `Level` and defaults to `0`. Calling `With` again with the same key replaces the earlier message. ```go title="Signature" func (r *Redirect) With(key, value string, level ...uint8) *Redirect ``` ```go title="Example" app.Get("/login", func(c fiber.Ctx) error { return c.Redirect().With("status", "Logged in successfully").To("/") }) app.Get("/", func(c fiber.Ctx) error { // => Logged in successfully return c.SendString(c.Redirect().Message("status").Value) }) ``` ##### WithInput Send input data with `WithInput`, which stores them in a cookie. It captures form, multipart, or query data depending on the request content type. :::caution `WithInput` copies the whole submitted body into the `fiber_flash` cookie — a rejected login carries the password the user just typed. The payload is hex-encoded, not encrypted or signed, so anything sensitive in the request goes back to the browser as a cookie value. Prefer `With` for the specific fields you need to carry across the redirect. The cookie is `HttpOnly`, and `Secure` when the request that produced it arrived over TLS — so script cannot read it and it is not replayed in the clear. Neither attribute protects the payload from whoever is at the browser. Browsers typically drop a cookie larger than about 4 KB, and Fiber sets no flash cookie at all when a redirect carries more than 256 entries (messages plus input fields), so a large form does not make the round trip. ::: ```go title="Signature" func (r *Redirect) WithInput() *Redirect ``` ```go title="Example" // curl -X POST http://localhost:3000/login -d "name=John" app.Post("/login", func(c fiber.Ctx) error { return c.Redirect().WithInput().Route("name") }) app.Get("/name", func(c fiber.Ctx) error { // => John return c.SendString(c.Redirect().OldInput("name").Value) }).Name("name") ``` --- ## 🧩 Services Services wrap external dependencies. List them in the application's configuration, and Fiber starts and stops them automatically—useful during development and testing. Fiber starts the configured services while `fiber.New` runs and stops them during shutdown. Retrieve a started service from state with `GetService` or `MustGetService`, using the name its `String` method returns (see [State Management](./state)). ### Service Interface The `Service` interface defines methods a service must implement. #### Definition ```go type Service interface { // Start starts the service, returning an error if it fails. Start(ctx context.Context) error // String returns a string representation of the service. // It is used to print a human-readable name of the service in the startup message. String() string // State returns the current state of the service. State(ctx context.Context) (string, error) // Terminate terminates the service, returning an error if it fails. Terminate(ctx context.Context) error } ``` ### Service Methods #### Start Starts the service. Fiber calls this while `fiber.New` runs, with the context returned by `ServicesStartupContextProvider`. See [Startup](#startup) for what happens when it returns an error. ```go func (s *SomeService) Start(ctx context.Context) error ``` #### String Returns a string representation of the service, used to print the service in the startup message and as the key the service is registered under in the application state. Names must be unique across the configured services: `fiber.New` panics when two services report the same name, since one would otherwise overwrite the other and never be terminated. ```go func (s *SomeService) String() string ``` #### State Reports the current state of the service for the startup message. Fiber calls it with the startup context when it prints the message, which shows the returned value in upper case, or `ERROR` when the call returns an error. ```go func (s *SomeService) State(ctx context.Context) (string, error) ``` #### Terminate Stops the service after the application shuts down using a post-shutdown hook, with the context returned by `ServicesShutdownContextProvider`. Services are terminated in reverse start order, so a service can rely on the ones started before it still being available while it shuts down. See [Shutdown](#shutdown) for what happens when it returns an error. ```go func (s *SomeService) Terminate(ctx context.Context) error ``` ### Configuration Services are configured with these `fiber.Config` fields: | Property | Type | Description | Default | | --- | --- | --- | --- | | `Services` | `[]fiber.Service` | Services that `fiber.New` starts and that Fiber terminates on shutdown. | `nil` | | `ServicesStartupContextProvider` | `func() context.Context` | Returns the context passed to `Start`. Fiber also passes it to `State` when it prints the startup message. | `context.Background()` | | `ServicesShutdownContextProvider` | `func() context.Context` | Returns the context passed to `Terminate`. | `context.Background()` | Fiber calls a provider each time it needs a context, so build a context that carries a deadline inside the provider. A context created ahead of time may already be expired when the application shuts down, and `Terminate` would then never run. ### Lifecycle #### Startup `fiber.New` starts the services one after the other, in the order of `Config.Services`, and registers each started service in the application's [state](./state). It panics in these cases: - The slice holds a `nil` entry or two services whose `String` method returns the same name. Nothing is started. - A `Start` call returns an error. Fiber still tries the remaining services, then panics with all the collected errors, unless the error wraps `context.Canceled` or `context.DeadlineExceeded`: then startup stops at once and the panic carries only that error. - The startup context is canceled or reaches its deadline. Startup stops at once and the panic carries the context error. :::warning With `ListenConfig.EnablePrefork`, the master process and every child process run your `main` function, and with it `fiber.New`, so each process starts its own copy of every service. ::: #### Shutdown Fiber registers a post-shutdown hook while `fiber.New` runs, so `Shutdown`, `ShutdownWithTimeout`, `ShutdownWithContext`, and a graceful shutdown started through `ListenConfig.GracefulContext` all terminate the services. The hook runs before any `OnPostShutdown` hook you register afterwards. When `Config.SharedStorage` is set, the hook closes it once the services are done (see [SharedState](./state#sharedstate-prefork-safe)). Services are terminated in reverse start order, and a terminated service is removed from the state. Termination is best effort: - If `Terminate` returns an error, Fiber logs it (`Shutdown` does not return it) and goes on with the remaining services. The failed service stays registered, so a later shutdown retries it. - Once the shutdown context is done, the remaining services are not terminated, and the logged error carries the context error for each of them. - Each started service is terminated once, even when shutdown calls overlap. ### Comprehensive Examples #### Example: Adding a Service This example demonstrates how to add a Redis store as a service to the application, backed by the Testcontainers Redis Go module. ```go package main import ( "context" "fmt" "log" "time" "github.com/gofiber/fiber/v3" "github.com/redis/go-redis/v9" tcredis "github.com/testcontainers/testcontainers-go/modules/redis" ) const redisServiceName = "redis-store" type redisService struct { ctr *tcredis.RedisContainer } // Start initializes and starts the service. It implements the [fiber.Service] interface. func (s *redisService) Start(ctx context.Context) error { // start the service c, err := tcredis.Run(ctx, "redis:latest") if err != nil { return err } s.ctr = c return nil } // String returns a string representation of the service. // It is used to print a human-readable name of the service in the startup message. // It implements the [fiber.Service] interface. func (s *redisService) String() string { return redisServiceName } // State returns the current state of the service. // It implements the [fiber.Service] interface. func (s *redisService) State(ctx context.Context) (string, error) { state, err := s.ctr.State(ctx) if err != nil { return "", fmt.Errorf("container state: %w", err) } // Status is a named string type (container.ContainerState) since testcontainers-go v0.42.0. return string(state.Status), nil } // Terminate stops and removes the service. It implements the [fiber.Service] interface. func (s *redisService) Terminate(ctx context.Context) error { // stop the service return s.ctr.Terminate(ctx) } func main() { cfg := &fiber.Config{} // Initialize service. cfg.Services = append(cfg.Services, &redisService{}) // Define a context provider for the services startup. // This is useful to cancel the startup of the services if the context is canceled. // Fiber calls the provider to start the services and again when it prints the startup // message, so create the context inside it to give each call a fresh timeout. // Default is context.Background(). cfg.ServicesStartupContextProvider = func() context.Context { startupCtx, cancelStartup := context.WithTimeout(context.Background(), 10*time.Second) context.AfterFunc(startupCtx, cancelStartup) // the deadline releases the timer; this also keeps go vet from reporting the unused cancel function return startupCtx } // Define a context provider for the services shutdown. // This is useful to cancel the shutdown of the services if the context is canceled. // Fiber calls the provider from the post-shutdown hook, once the server has stopped, so // create the context inside it: a context created here would already be expired if the // app ran longer than the timeout. // Default is context.Background(). cfg.ServicesShutdownContextProvider = func() context.Context { shutdownCtx, cancelShutdown := context.WithTimeout(context.Background(), 10*time.Second) context.AfterFunc(shutdownCtx, cancelShutdown) // the deadline releases the timer; this also keeps go vet from reporting the unused cancel function return shutdownCtx } app := fiber.New(*cfg) ctx := context.Background() // Obtain the Redis service from the application's State. redisSrv, ok := fiber.GetService[*redisService](app.State(), redisServiceName) if !ok || redisSrv == nil { log.Printf("Redis service not found") return } // Obtain the connection string from the service. connString, err := redisSrv.ctr.ConnectionString(ctx) if err != nil { log.Printf("Could not get connection string: %v", err) return } // Parse the connection string to create a Redis client. options, err := redis.ParseURL(connString) if err != nil { log.Printf("failed to parse connection string: %s", err) return } // Initialize the Redis client. rdb := redis.NewClient(options) // Check the Redis connection. if err := rdb.Ping(ctx).Err(); err != nil { log.Fatalf("Could not connect to Redis: %v", err) } app.Listen(":3000") } ``` #### Example: Add a service with the Store middleware This example shows how to use services with the Store middleware for dependency injection. It uses a Redis store backed by the Testcontainers Redis module. ```go package main import ( "context" "encoding/json" "fmt" "log" "time" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/logger" redisStore "github.com/gofiber/storage/redis/v3" tcredis "github.com/testcontainers/testcontainers-go/modules/redis" ) const ( redisServiceName = "redis-store" ) type User struct { ID int `json:"id"` Name string `json:"name"` Email string `json:"email"` } type redisService struct { ctr *tcredis.RedisContainer } // Start initializes and starts the service. It implements the [fiber.Service] interface. func (s *redisService) Start(ctx context.Context) error { // start the service c, err := tcredis.Run(ctx, "redis:latest") if err != nil { return err } s.ctr = c return nil } // String returns a string representation of the service. // It is used to print a human-readable name of the service in the startup message. // It implements the [fiber.Service] interface. func (s *redisService) String() string { return redisServiceName } // State returns the current state of the service. // It implements the [fiber.Service] interface. func (s *redisService) State(ctx context.Context) (string, error) { state, err := s.ctr.State(ctx) if err != nil { return "", fmt.Errorf("container state: %w", err) } // Status is a named string type (container.ContainerState) since testcontainers-go v0.42.0. return string(state.Status), nil } // Terminate stops and removes the service. It implements the [fiber.Service] interface. func (s *redisService) Terminate(ctx context.Context) error { // stop the service return s.ctr.Terminate(ctx) } func main() { cfg := &fiber.Config{} // Initialize service. cfg.Services = append(cfg.Services, &redisService{}) // Define a context provider for the services startup. // This is useful to cancel the startup of the services if the context is canceled. // Fiber calls the provider to start the services and again when it prints the startup // message, so create the context inside it to give each call a fresh timeout. // Default is context.Background(). cfg.ServicesStartupContextProvider = func() context.Context { startupCtx, cancelStartup := context.WithTimeout(context.Background(), 10*time.Second) context.AfterFunc(startupCtx, cancelStartup) // the deadline releases the timer; this also keeps go vet from reporting the unused cancel function return startupCtx } // Define a context provider for the services shutdown. // This is useful to cancel the shutdown of the services if the context is canceled. // Fiber calls the provider from the post-shutdown hook, once the server has stopped, so // create the context inside it: a context created here would already be expired if the // app ran longer than the timeout. // Default is context.Background(). cfg.ServicesShutdownContextProvider = func() context.Context { shutdownCtx, cancelShutdown := context.WithTimeout(context.Background(), 10*time.Second) context.AfterFunc(shutdownCtx, cancelShutdown) // the deadline releases the timer; this also keeps go vet from reporting the unused cancel function return shutdownCtx } app := fiber.New(*cfg) // Initialize default config app.Use(logger.New()) ctx := context.Background() // Obtain the Redis service from the application's State. redisSrv, ok := fiber.GetService[*redisService](app.State(), redisServiceName) if !ok || redisSrv == nil { log.Printf("Redis service not found") return } // Obtain the connection string from the service. connString, err := redisSrv.ctr.ConnectionString(ctx) if err != nil { log.Printf("Could not get connection string: %v", err) return } // define a GoFiber session store, backed by the Redis service store := redisStore.New(redisStore.Config{ URL: connString, }) app.Post("/user/create", func(c fiber.Ctx) error { var user User if err := c.Bind().JSON(&user); err != nil { return c.Status(fiber.StatusBadRequest).SendString(err.Error()) } json, err := json.Marshal(user) if err != nil { return c.Status(fiber.StatusInternalServerError).SendString(err.Error()) } // Save the user to the database. err = store.Set(user.Email, json, time.Hour*24) if err != nil { return c.Status(fiber.StatusInternalServerError).SendString(err.Error()) } return c.JSON(user) }) app.Get("/user/:id", func(c fiber.Ctx) error { id := c.Params("id") user, err := store.Get(id) if err != nil { return c.Status(fiber.StatusInternalServerError).SendString(err.Error()) } // The storage returns nil, nil when the key does not exist. if user == nil { return c.Status(fiber.StatusNotFound).SendString("User not found") } return c.JSON(string(user)) }) app.Listen(":3000") } ``` --- ## 🗂️ State Management State management provides a global key–value store for application dependencies and runtime data. The store is shared across the entire application and persists between requests. It's commonly used to store [Services](../api/services), which you can retrieve with the `GetService` or `MustGetService` functions. :::warning When prefork is enabled, each worker process has an independent state store, meaning state is not shared between them. ::: Use the index to jump straight to any state method; filter by name or by category: ### SharedState (Prefork-safe) For data that must be shared across prefork workers or multiple app processes, use `app.SharedState()` backed by `fiber.Storage`. Configure storage in `fiber.Config`: ```go app := fiber.New(fiber.Config{ AppName: "billing-api", SharedStorage: redisStorage, // any implementation of fiber.Storage SharedStatePrefix: "billing-shared-", // optional }) ``` `SharedStorage` accepts any implementation of the `fiber.Storage` interface, such as the drivers in the [storage](https://github.com/gofiber/storage) package. See [Storage Interface](#storage-interface) for the methods it must provide. If `SharedStatePrefix` is empty, Fiber derives a default namespace and includes `AppName` (when set) to reduce collisions between apps/services. The default is `gofiber-shared-state-`, followed by the `AppName` and a dash when `AppName` is set (for example `gofiber-shared-state-billing-api-`). Fiber stores each key in the backend as the prefix followed by the hex encoding of the key you pass. Without `SharedStorage`, `app.SharedState()` still returns a value, but every method returns `fiber.ErrSharedStorageNotConfigured`. The JSON and XML helpers use `Config.JSONEncoder`/`JSONDecoder` and `Config.XMLEncoder`/`XMLDecoder`, which default to `encoding/json` and `encoding/xml`. MsgPack and CBOR helpers require the corresponding `Config` encoders/decoders to be configured. If they are unavailable, the helper methods return an error instead of panicking. An empty key is ignored: the setters and `Delete` do nothing and return `nil`, the getters report the key as not found, and `Has` returns `false`. A `ttl` of `0` stores the value without an expiration. :::warning Memory storage caveat `SharedState` is only cross-worker / cross-process when the configured `SharedStorage` backend is shared. If you use an in-memory backend (for example memory storage), data remains process-local. In prefork mode, each worker process has its own independent in-memory store. ::: #### SharedState Methods ```go title="Signature" func (app *App) SharedState() *SharedState func (s *SharedState) Set(key string, val []byte, ttl time.Duration) error func (s *SharedState) SetWithContext(ctx context.Context, key string, val []byte, ttl time.Duration) error func (s *SharedState) Get(key string) (val []byte, found bool, err error) func (s *SharedState) GetWithContext(ctx context.Context, key string) (val []byte, found bool, err error) func (s *SharedState) SetJSON(key string, v any, ttl time.Duration) error func (s *SharedState) SetJSONWithContext(ctx context.Context, key string, v any, ttl time.Duration) error func (s *SharedState) GetJSON(key string, out any) (raw []byte, found bool, err error) func (s *SharedState) GetJSONWithContext(ctx context.Context, key string, out any) (raw []byte, found bool, err error) func (s *SharedState) SetMsgPack(key string, v any, ttl time.Duration) error func (s *SharedState) SetMsgPackWithContext(ctx context.Context, key string, v any, ttl time.Duration) error func (s *SharedState) GetMsgPack(key string, out any) (raw []byte, found bool, err error) func (s *SharedState) GetMsgPackWithContext(ctx context.Context, key string, out any) (raw []byte, found bool, err error) func (s *SharedState) SetCBOR(key string, v any, ttl time.Duration) error func (s *SharedState) SetCBORWithContext(ctx context.Context, key string, v any, ttl time.Duration) error func (s *SharedState) GetCBOR(key string, out any) (raw []byte, found bool, err error) func (s *SharedState) GetCBORWithContext(ctx context.Context, key string, out any) (raw []byte, found bool, err error) func (s *SharedState) SetXML(key string, v any, ttl time.Duration) error func (s *SharedState) SetXMLWithContext(ctx context.Context, key string, v any, ttl time.Duration) error func (s *SharedState) GetXML(key string, out any) (raw []byte, found bool, err error) func (s *SharedState) GetXMLWithContext(ctx context.Context, key string, out any) (raw []byte, found bool, err error) func (s *SharedState) Delete(key string) error func (s *SharedState) DeleteWithContext(ctx context.Context, key string) error func (s *SharedState) Has(key string) (bool, error) func (s *SharedState) HasWithContext(ctx context.Context, key string) (bool, error) func (s *SharedState) Reset() error func (s *SharedState) ResetWithContext(ctx context.Context) error func (s *SharedState) Close() error ``` :::caution `Reset` resets the whole configured storage, not only the keys written through `SharedState`, and `Close` closes it. Fiber also calls `Close` when the application shuts down, after it has terminated the [services](./services.md), so everything else that uses the same storage instance loses it at that point. ::: #### SharedState Example ```go type SessionSnapshot struct { UserID string `json:"user_id"` UpdatedAt time.Time `json:"updated_at"` } app.Post("/sessions/:id", func(c fiber.Ctx) error { key := "session:" + c.Params("id") value := SessionSnapshot{ UserID: c.Params("id"), UpdatedAt: time.Now().UTC(), } if err := app.SharedState().SetJSON(key, value, 30*time.Minute); err != nil { return err } return c.SendStatus(fiber.StatusAccepted) }) app.Get("/sessions/:id", func(c fiber.Ctx) error { key := "session:" + c.Params("id") var snapshot SessionSnapshot _, found, err := app.SharedState().GetJSON(key, &snapshot) if err != nil { return err } if !found { return c.SendStatus(fiber.StatusNotFound) } return c.JSON(snapshot) }) ``` #### SharedState with Context (timeouts/cancellation) ```go ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) defer cancel() err := app.SharedState().SetJSONWithContext(ctx, "job:42", fiber.Map{ "status": "queued", }, 2*time.Minute) if err != nil { // timeout, cancellation, storage error, or JSON serialization error } ``` #### Storage Interface `SharedStorage` takes a `fiber.Storage`. A missing key is reported as `nil, nil` rather than as an error, and an expiration of `0` means no expiration. ```go title="Definition" type Storage interface { // Get returns the value for the given key, or nil, nil when the key does not exist. Get(key string) ([]byte, error) GetWithContext(ctx context.Context, key string) ([]byte, error) // Set stores the value for the given key with an expiration, where 0 means no expiration. // An empty key or value is ignored without an error. Set(key string, val []byte, exp time.Duration) error SetWithContext(ctx context.Context, key string, val []byte, exp time.Duration) error // Delete removes the given key. It returns no error when the key does not exist. Delete(key string) error DeleteWithContext(ctx context.Context, key string) error // Reset deletes all keys. Reset() error ResetWithContext(ctx context.Context) error // Close closes the storage and stops any running garbage collector and open connection. Close() error } ``` ### State Type `State` is a key–value store built on top of `sync.Map` to ensure safe concurrent access. It allows storage and retrieval of dependencies and configurations in a Fiber application as well as thread–safe access to runtime data. #### Definition ```go // State is a key–value store for Fiber's app, used as a global storage for the app's dependencies. // It is a thread–safe implementation of a map[string]any, using sync.Map. type State struct { dependencies sync.Map } ``` ### Methods on State #### Set Set adds or updates a key–value pair in the State. ```go // Set adds or updates a key–value pair in the State. func (s *State) Set(key string, value any) ``` **Usage Example:** ```go app.State().Set("appName", "My Fiber App") ``` #### Get Get retrieves a value from the State. ```go title="Signature" func (s *State) Get(key string) (any, bool) ``` **Usage Example:** ```go value, ok := app.State().Get("appName") if ok { fmt.Println("App Name:", value) } ``` #### MustGet MustGet retrieves a value from the State and panics if the key is not found. ```go title="Signature" func (s *State) MustGet(key string) any ``` **Usage Example:** ```go appName := app.State().MustGet("appName") fmt.Println("App Name:", appName) ``` #### Has Has checks if a key exists in the State. ```go title="Signature" func (s *State) Has(key string) bool ``` **Usage Example:** ```go if app.State().Has("appName") { fmt.Println("App Name is set.") } ``` #### Delete Delete removes a key–value pair from the State. ```go title="Signature" func (s *State) Delete(key string) ``` **Usage Example:** ```go app.State().Delete("obsoleteKey") ``` #### Reset Reset removes all keys from the State, including those related to Services. `GetService` no longer finds the services afterwards, but they are not stopped: Fiber still terminates them at shutdown. ```go title="Signature" func (s *State) Reset() ``` **Usage Example:** ```go app.State().Reset() ``` #### Keys Keys returns a slice containing all keys present in the State. Services that Fiber started are stored under internal keys, which are included. ```go title="Signature" func (s *State) Keys() []string ``` **Usage Example:** ```go keys := app.State().Keys() fmt.Println("State Keys:", keys) ``` #### Len Len returns the number of keys in the State, including the entries of the services that Fiber started. ```go // Len returns the number of keys in the State. func (s *State) Len() int ``` **Usage Example:** ```go fmt.Printf("Total State Entries: %d\n", app.State().Len()) ``` #### Services Services returns a map with the [services](./services.md) that Fiber started and registered in the State. The map keys are internal identifiers, not the names that the services return from `String`; use [GetService](#getservice) to look one up by name. The map is a copy, so changing it does not affect the State. ```go title="Signature" func (s *State) Services() map[string]Service ``` **Usage Example:** ```go for _, srv := range app.State().Services() { fmt.Println("Service:", srv.String()) } ``` #### ServicesLen ServicesLen returns the number of services registered in the State. [Len](#len) counts them too. ```go title="Signature" func (s *State) ServicesLen() int ``` **Usage Example:** ```go fmt.Printf("Registered services: %d\n", app.State().ServicesLen()) ``` #### GetString GetString retrieves a string value from the State. It returns the string and a boolean indicating a successful type assertion. ```go title="Signature" func (s *State) GetString(key string) (string, bool) ``` **Usage Example:** ```go if appName, ok := app.State().GetString("appName"); ok { fmt.Println("App Name:", appName) } ``` #### GetInt GetInt retrieves an integer value from the State. It returns the int and a boolean indicating a successful type assertion. ```go title="Signature" func (s *State) GetInt(key string) (int, bool) ``` **Usage Example:** ```go if count, ok := app.State().GetInt("userCount"); ok { fmt.Printf("User Count: %d\n", count) } ``` #### GetBool GetBool retrieves a boolean value from the State. It returns the bool and a boolean indicating a successful type assertion. ```go title="Signature" func (s *State) GetBool(key string) (value, ok bool) ``` **Usage Example:** ```go if debug, ok := app.State().GetBool("debugMode"); ok { fmt.Printf("Debug Mode: %v\n", debug) } ``` #### GetFloat64 GetFloat64 retrieves a float64 value from the State. It returns the float64 and a boolean indicating a successful type assertion. ```go title="Signature" func (s *State) GetFloat64(key string) (float64, bool) ``` **Usage Example:** ```go if ratio, ok := app.State().GetFloat64("scalingFactor"); ok { fmt.Printf("Scaling Factor: %f\n", ratio) } ``` #### GetUint GetUint retrieves a `uint` value from the State. ```go title="Signature" func (s *State) GetUint(key string) (uint, bool) ``` **Usage Example:** ```go if val, ok := app.State().GetUint("maxConnections"); ok { fmt.Printf("Max Connections: %d\n", val) } ``` #### GetInt8 GetInt8 retrieves an `int8` value from the State. ```go title="Signature" func (s *State) GetInt8(key string) (int8, bool) ``` **Usage Example:** ```go if val, ok := app.State().GetInt8("threshold"); ok { fmt.Printf("Threshold: %d\n", val) } ``` #### GetInt16 GetInt16 retrieves an `int16` value from the State. ```go title="Signature" func (s *State) GetInt16(key string) (int16, bool) ``` **Usage Example:** ```go if val, ok := app.State().GetInt16("minValue"); ok { fmt.Printf("Minimum Value: %d\n", val) } ``` #### GetInt32 GetInt32 retrieves an `int32` value from the State. ```go title="Signature" func (s *State) GetInt32(key string) (int32, bool) ``` **Usage Example:** ```go if val, ok := app.State().GetInt32("portNumber"); ok { fmt.Printf("Port Number: %d\n", val) } ``` #### GetInt64 GetInt64 retrieves an `int64` value from the State. ```go title="Signature" func (s *State) GetInt64(key string) (int64, bool) ``` **Usage Example:** ```go if val, ok := app.State().GetInt64("fileSize"); ok { fmt.Printf("File Size: %d\n", val) } ``` #### GetUint8 GetUint8 retrieves a `uint8` value from the State. ```go title="Signature" func (s *State) GetUint8(key string) (uint8, bool) ``` **Usage Example:** ```go if val, ok := app.State().GetUint8("byteValue"); ok { fmt.Printf("Byte Value: %d\n", val) } ``` #### GetUint16 GetUint16 retrieves a `uint16` value from the State. ```go title="Signature" func (s *State) GetUint16(key string) (uint16, bool) ``` **Usage Example:** ```go if val, ok := app.State().GetUint16("limit"); ok { fmt.Printf("Limit: %d\n", val) } ``` #### GetUint32 GetUint32 retrieves a `uint32` value from the State. ```go title="Signature" func (s *State) GetUint32(key string) (uint32, bool) ``` **Usage Example:** ```go if val, ok := app.State().GetUint32("timeout"); ok { fmt.Printf("Timeout: %d\n", val) } ``` #### GetUint64 GetUint64 retrieves a `uint64` value from the State. ```go title="Signature" func (s *State) GetUint64(key string) (uint64, bool) ``` **Usage Example:** ```go if val, ok := app.State().GetUint64("maxSize"); ok { fmt.Printf("Max Size: %d\n", val) } ``` #### GetUintptr GetUintptr retrieves a `uintptr` value from the State. ```go title="Signature" func (s *State) GetUintptr(key string) (uintptr, bool) ``` **Usage Example:** ```go if val, ok := app.State().GetUintptr("pointerValue"); ok { fmt.Printf("Pointer Value: %d\n", val) } ``` #### GetFloat32 GetFloat32 retrieves a `float32` value from the State. ```go title="Signature" func (s *State) GetFloat32(key string) (float32, bool) ``` **Usage Example:** ```go if val, ok := app.State().GetFloat32("scalingFactor32"); ok { fmt.Printf("Scaling Factor (float32): %f\n", val) } ``` #### GetComplex64 GetComplex64 retrieves a `complex64` value from the State. ```go title="Signature" func (s *State) GetComplex64(key string) (complex64, bool) ``` **Usage Example:** ```go if val, ok := app.State().GetComplex64("complexVal"); ok { fmt.Printf("Complex Value (complex64): %v\n", val) } ``` #### GetComplex128 GetComplex128 retrieves a `complex128` value from the State. ```go title="Signature" func (s *State) GetComplex128(key string) (complex128, bool) ``` **Usage Example:** ```go if val, ok := app.State().GetComplex128("complexVal128"); ok { fmt.Printf("Complex Value (complex128): %v\n", val) } ``` ### Generic Functions Fiber provides generic functions to retrieve state values with type safety and fallback options. #### GetState GetState retrieves a value from the State and casts it to the desired type. It returns the cast value and a boolean indicating if the cast was successful. ```go title="Signature" func GetState[T any](s *State, key string) (T, bool) ``` **Usage Example:** ```go // Retrieve an integer value safely. userCount, ok := fiber.GetState[int](app.State(), "userCount") if ok { fmt.Printf("User Count: %d\n", userCount) } ``` #### MustGetState MustGetState retrieves a value from the State and casts it to the desired type. It panics if the key is not found or if the type assertion fails. ```go title="Signature" func MustGetState[T any](s *State, key string) T ``` **Usage Example:** ```go // Retrieve the value or panic if it is not present. config := fiber.MustGetState[string](app.State(), "configFile") fmt.Println("Config File:", config) ``` #### GetStateWithDefault GetStateWithDefault retrieves a value from the State, casting it to the desired type. If the key is not present, or the stored value is not of that type, it returns the provided default value. ```go title="Signature" func GetStateWithDefault[T any](s *State, key string, defaultVal T) T ``` **Usage Example:** ```go // Retrieve a value with a fallback. requestCount := fiber.GetStateWithDefault[int](app.State(), "requestCount", 0) fmt.Printf("Request Count: %d\n", requestCount) ``` #### GetService GetService retrieves a started Service from the State by the name its `String` method returns and casts it to the desired type. It returns the cast value and a boolean that is `false` when no service has that name or the service is not of that type. ```go title="Signature" func GetService[T Service](s *State, key string) (T, bool) ``` **Usage Example:** ```go if srv, ok := fiber.GetService[*redisService](app.State(), "someService"); ok { fmt.Printf("Some Service: %s\n", srv.String()) } ``` #### MustGetService MustGetService retrieves a started Service from the State by name and casts it to the desired type. It panics if no service has that name or if the type assertion fails. ```go title="Signature" func MustGetService[T Service](s *State, key string) T ``` **Usage Example:** ```go srv := fiber.MustGetService[*SomeService](app.State(), "someService") ``` ### Comprehensive Examples #### Example: Request Counter This example demonstrates how to track the number of requests using the State. ```go package main import ( "fmt" "github.com/gofiber/fiber/v3" ) func main() { app := fiber.New() // Initialize state with a counter. app.State().Set("requestCount", 0) // Middleware: Increase counter for every request. app.Use(func(c fiber.Ctx) error { count, _ := c.App().State().GetInt("requestCount") app.State().Set("requestCount", count+1) return c.Next() }) app.Get("/", func(c fiber.Ctx) error { return c.SendString("Hello World!") }) app.Get("/stats", func(c fiber.Ctx) error { count, _ := c.App().State().Get("requestCount") return c.SendString(fmt.Sprintf("Total requests: %d", count)) }) app.Listen(":3000") } ``` #### Example: Environment–Specific Configuration This example shows how to configure different settings based on the environment. ```go package main import ( "os" "github.com/gofiber/fiber/v3" ) func main() { app := fiber.New() // Determine environment. environment := os.Getenv("ENV") if environment == "" { environment = "development" } app.State().Set("environment", environment) // Set environment-specific configurations. if environment == "development" { app.State().Set("apiUrl", "http://localhost:8080/api") app.State().Set("debug", true) } else { app.State().Set("apiUrl", "https://api.production.com") app.State().Set("debug", false) } app.Get("/config", func(c fiber.Ctx) error { config := map[string]any{ "environment": environment, "apiUrl": fiber.GetStateWithDefault(c.App().State(), "apiUrl", ""), "debug": fiber.GetStateWithDefault(c.App().State(), "debug", false), } return c.JSON(config) }) app.Listen(":3000") } ``` #### Example: Dependency Injection with State Management This example demonstrates how to use the State for dependency injection in a Fiber application. ```go package main import ( "context" "fmt" "log" "github.com/gofiber/fiber/v3" "github.com/redis/go-redis/v9" ) type User struct { ID int `query:"id"` Name string `query:"name"` Email string `query:"email"` } func main() { app := fiber.New() ctx := context.Background() // Initialize Redis client. rdb := redis.NewClient(&redis.Options{ Addr: "localhost:6379", Password: "", DB: 0, }) // Check the Redis connection. if err := rdb.Ping(ctx).Err(); err != nil { log.Fatalf("Could not connect to Redis: %v", err) } // Inject the Redis client into Fiber's State for dependency injection. app.State().Set("redis", rdb) app.Get("/user/create", func(c fiber.Ctx) error { var user User if err := c.Bind().Query(&user); err != nil { return c.Status(fiber.StatusBadRequest).SendString(err.Error()) } // Retrieve the Redis client from the global state. rdb, ok := fiber.GetState[*redis.Client](c.App().State(), "redis") if !ok { return c.Status(fiber.StatusInternalServerError).SendString("Redis client not found") } // Save the user to the database. key := fmt.Sprintf("user:%d", user.ID) err := rdb.HSet(ctx, key, "name", user.Name, "email", user.Email).Err() if err != nil { return c.Status(fiber.StatusInternalServerError).SendString(err.Error()) } return c.JSON(user) }) app.Get("/user/:id", func(c fiber.Ctx) error { id := c.Params("id") rdb, ok := fiber.GetState[*redis.Client](c.App().State(), "redis") if !ok { return c.Status(fiber.StatusInternalServerError).SendString("Redis client not found") } key := fmt.Sprintf("user:%s", id) user, err := rdb.HGetAll(ctx, key).Result() if err == redis.Nil { return c.Status(fiber.StatusNotFound).SendString("User not found") } else if err != nil { return c.Status(fiber.StatusInternalServerError).SendString(err.Error()) } return c.JSON(user) }) app.Listen(":3000") } ``` --- ## 🍳 Examples ### Basic Auth Clients send credentials via the `Authorization` header, while the server stores hashed passwords as shown in the middleware example. **Client** ```go package main import ( "encoding/base64" "fmt" "github.com/gofiber/fiber/v3/client" ) func main() { cc := client.New() out := base64.StdEncoding.EncodeToString([]byte("john:doe")) resp, err := cc.Get("http://localhost:3000", client.Config{ Header: map[string]string{ "Authorization": "Basic " + out, }, }) if err != nil { panic(err) } fmt.Print(string(resp.Body())) } ``` **Server** ```go package main import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/basicauth" ) func main() { app := fiber.New() app.Use( basicauth.New(basicauth.Config{ Users: map[string]string{ // "doe" hashed using SHA-256 "john": "{SHA256}eZ75KhGvkY4/t0HfQpNPO1aO0tk6wd908bjUGieTKm8=", }, }), ) app.Get("/", func(c fiber.Ctx) error { return c.SendString("Hello, World!") }) app.Listen(":3000") } ``` ### TLS **Client** ```go package main import ( "crypto/tls" "crypto/x509" "fmt" "os" "github.com/gofiber/fiber/v3/client" ) func main() { cc := client.New() certPool, err := x509.SystemCertPool() if err != nil { panic(err) } cert, err := os.ReadFile("ssl.cert") if err != nil { panic(err) } certPool.AppendCertsFromPEM(cert) cc.SetTLSConfig(&tls.Config{ RootCAs: certPool, }) resp, err := cc.Get("https://localhost:3000") if err != nil { panic(err) } fmt.Print(string(resp.Body())) } ``` **Server** ```go package main import ( "github.com/gofiber/fiber/v3" ) func main() { app := fiber.New() app.Get("/", func(c fiber.Ctx) error { return c.SendString("Hello, World!") }) err := app.Listen(":3000", fiber.ListenConfig{ CertFile: "ssl.cert", CertKeyFile: "ssl.key", }) if err != nil { panic(err) } } ``` ### Reusing fasthttp transports The Fiber client can wrap existing `fasthttp` clients so that you can reuse connection pools, custom dialers, or load-balancing logic that is already tuned for your infrastructure. #### HostClient ```go package main import ( "log" "time" "github.com/gofiber/fiber/v3/client" "github.com/valyala/fasthttp" ) func main() { hc := &fasthttp.HostClient{ Addr: "api.internal:443", IsTLS: true, MaxConnDuration: 30 * time.Second, MaxIdleConnDuration: 10 * time.Second, } cc := client.NewWithHostClient(hc) resp, err := cc.Get("https://api.internal:443/status") if err != nil { log.Fatal(err) } log.Printf("status=%d body=%s", resp.StatusCode(), resp.Body()) } ``` #### LBClient ```go package main import ( "log" "time" "github.com/gofiber/fiber/v3/client" "github.com/valyala/fasthttp" ) func main() { lb := &fasthttp.LBClient{ Timeout: 2 * time.Second, Clients: []fasthttp.BalancingClient{ &fasthttp.HostClient{Addr: "edge-1.internal:8080"}, &fasthttp.HostClient{Addr: "edge-2.internal:8080"}, }, } cc := client.NewWithLBClient(lb) // Per-request overrides such as redirects, retries, TLS, and proxy dialers // are shared across every host client managed by the load balancer. resp, err := cc.Get("http://service.internal/api") if err != nil { log.Fatal(err) } log.Printf("status=%d body=%s", resp.StatusCode(), resp.Body()) } ``` ### Cookie jar The client can store and reuse cookies between requests by attaching a cookie jar. The jar follows RFC 6265 for storage and retrieval: - A `Set-Cookie` without a `Path` attribute is scoped to the **directory** of the request that set it, not to the whole host. A cookie set by a response to `/api/login` defaults to `Path=/api` and is not sent to `/`. Send an explicit `Path=/` to scope it host-wide. - Cookies are identified by the triple (name, domain, path), so the same name can be stored at several paths at once. When more than one applies to a request, the one with the longest path wins. - Storage is bounded: at most 1024 storage keys, and at most 64 cookies per key. A host-only cookie and a `Domain=` cookie are stored under different keys, so a single host can occupy more than one. When a key is full the jar drops expired entries first, then the least recently written — a session cookie the server re-sends on each response is not evicted by a flood of one-off cookies. A single request carries at most 64 cookies, the most specific first, so a host cannot inflate the `Cookie` header by spreading cookies across the `Domain=` keys of its parent labels. - Cookies are attached once per request, before any redirect is followed, so a redirect chain carries the cookies selected for the original URL. A hop to an unrelated host drops them, but a hop to a subdomain keeps them — matching `net/http`, which permits `Cookie` from `foo.com` to `sub.foo.com`. That is wider than the jar's own rule: a cookie set without a `Domain=` attribute is host-only and the jar would not send it to `sub.foo.com`, yet a redirect there carries it. This only arises once redirects are being followed, which is not the default: leave `MaxRedirects` unset — or set `0`, via `Request.SetMaxRedirects` or `Config{MaxRedirects: 0}` — and issue each hop yourself, so the jar decides for each one. #### Request ```go func main() { jar := client.AcquireCookieJar() defer client.ReleaseCookieJar(jar) cc := client.New() cc.SetCookieJar(jar) jar.SetKeyValueBytes("httpbin.org", []byte("john"), []byte("doe")) resp, err := cc.Get("https://httpbin.org/cookies") if err != nil { panic(err) } fmt.Println(string(resp.Body())) } ```
Click here to see the result ```json { "cookies": { "john": "doe" } } ```
#### Response Read cookies set by the server directly from the jar. ```go func main() { jar := client.AcquireCookieJar() defer client.ReleaseCookieJar(jar) cc := client.New() cc.SetCookieJar(jar) _, err := cc.Get("https://httpbin.org/cookies/set/john/doe") if err != nil { panic(err) } uri := fasthttp.AcquireURI() defer fasthttp.ReleaseURI(uri) uri.SetHost("httpbin.org") uri.SetPath("/cookies") fmt.Println(jar.Get(uri)) } ```
Click here to see the result ```plaintext [john=doe; domain=httpbin.org; path=/] ```
#### Response (follow-up request) ```go func main() { jar := client.AcquireCookieJar() defer client.ReleaseCookieJar(jar) cc := client.New() cc.SetCookieJar(jar) _, err := cc.Get("https://httpbin.org/cookies/set/john/doe") if err != nil { panic(err) } resp, err := cc.Get("https://httpbin.org/cookies") if err != nil { panic(err) } fmt.Println(resp.String()) } ```
Click here to see the result ```json { "cookies": { "john": "doe" } } ```
--- ## 🎣 Hooks (Client) Hooks let you intercept and modify the request or response flow of the Fiber client. They are useful for: - Changing request parameters (e.g., URL, headers) before sending the request. - Logging request and response details. - Integrating complex tracing or monitoring tools. - Handling authentication, retries, or other custom logic. :::caution A client is shared by every request it sends, and hooks run once per request, possibly in several goroutines at the same time. Change the `Request` or `Response` that a hook receives, not the `Client`: a change to the client alters the configuration that other in-flight requests read. ::: There are two kinds of hooks: ### Request Hooks **Request hooks** are functions executed before the HTTP request is sent. They follow the signature: ```go type RequestHook func(*Client, *Request) error ``` A request hook receives both the `Client` and the `Request` objects, allowing you to modify the request before it leaves your application. For example, you could: - Change the host URL. - Log request details (method, URL, headers). - Add or modify headers or query parameters. - Intercept and apply custom authentication logic. **Example:** ```go type Repository struct { Name string `json:"name"` FullName string `json:"full_name"` Description string `json:"description"` Homepage string `json:"homepage"` Owner struct { Login string `json:"login"` } `json:"owner"` } func main() { cc := client.New() // Add a request hook that modifies the request URL before sending. cc.AddRequestHook(func(c *client.Client, r *client.Request) error { r.SetURL("https://api.github.com/" + r.URL()) return nil }) resp, err := cc.Get("repos/gofiber/fiber") if err != nil { panic(err) } var repo Repository if err := resp.JSON(&repo); err != nil { panic(err) } fmt.Printf("Status code: %d\n", resp.StatusCode()) fmt.Printf("Repository: %s\n", repo.FullName) fmt.Printf("Description: %s\n", repo.Description) fmt.Printf("Homepage: %s\n", repo.Homepage) fmt.Printf("Owner: %s\n", repo.Owner.Login) fmt.Printf("Name: %s\n", repo.Name) fmt.Printf("Full Name: %s\n", repo.FullName) } ```
Click here to see the result ```plaintext Status code: 200 Repository: gofiber/fiber Description: ⚡️ Express inspired web framework written in Go Homepage: https://gofiber.io Owner: gofiber Name: fiber Full Name: gofiber/fiber ```
#### Built-in Request Hooks Fiber includes built-in request hooks: - **parserRequestURL**: Normalizes and customizes the URL based on path and query parameters. Required for `PathParam` and `QueryParam` methods. - **parserRequestHeader**: Sets request headers, cookies, content type, referer, and user agent based on client and request properties. Client headers are applied first and request headers with the same key replace them. The user agent defaults to `fiber`, and the user agent and referer come from `SetUserAgent` and `SetReferer`, so a `User-Agent` or `Referer` header set any other way is replaced. No `Referer` header is sent unless a referer is set. - **parserRequestBody**: Automatically serializes the request body (JSON, XML, form, file uploads, etc.). :::info If a request hook returns an error, Fiber stops the request and returns the error immediately. ::: **Example with Multiple Hooks:** ```go func main() { cc := client.New() cc.AddRequestHook(func(c *client.Client, r *client.Request) error { fmt.Println("Hook 1") return errors.New("error") }) cc.AddRequestHook(func(c *client.Client, r *client.Request) error { fmt.Println("Hook 2") return nil }) _, err := cc.Get("https://example.com/") if err != nil { panic(err) } } ```
Click here to see the result ```shell Hook 1 panic: error goroutine 1 [running]: main.main() main.go:25 +0xaa exit status 2 ```
#### Final Request Hooks Hooks added with `AddRequestHook` run before Fiber's built-in hooks, so they can configure high-level request fields such as URL parameters, headers, and body. Hooks added with `AddFinalRequestHook` run after the built-in hooks and immediately before the request is sent, when `RawRequest` holds the resolved URL, the merged headers and cookies, and the serialized body. This is the place for request signing: ```go cc.AddFinalRequestHook(func(_ *client.Client, req *client.Request) error { raw := req.RawRequest if raw.IsBodyStream() { return errors.New("cannot sign a streamed body") } signature := sign(raw.URI().RequestURI(), raw.URI().Host(), raw.Body()) raw.Header.Set("X-Signature", signature) return nil }) ``` :::caution fasthttp writes the request line, `Host` and `Content-Length` when it sends the request, which is after this hook. Take the target and the host from `RawRequest.URI()`: `Header.Header()` still carries the absolute URL without its query string, and `Header.Host()` is empty. The hook runs once per call, not once per attempt: retries resend the request it signed, and a redirect is followed below it, carrying a signature bound to the previous URL. Sign with a nonce or a timestamp only when retries are off. Hence the `IsBodyStream` guard above. Reading `RawRequest.Body()` drains a body stream into memory, and a stream that fails mid-read leaves fasthttp's error text in the body: that text is then sent as the payload, signed as if it were genuine, while `Send` reports no error. Buffer such a body before attaching it. ::: ### Response Hooks **Response hooks** are functions executed after the HTTP response is received. They follow the signature: ```go type ResponseHook func(*Client, *Response, *Request) error ``` A response hook receives the `Client`, `Response`, and `Request` objects, allowing you to inspect and modify the response or perform additional actions such as logging, tracing, or processing response data. **Example:** ```go func main() { cc := client.New() cc.AddResponseHook(func(c *client.Client, resp *client.Response, req *client.Request) error { fmt.Printf("Response Status Code: %d\n", resp.StatusCode()) fmt.Printf("HTTP protocol: %s\n\n", resp.Protocol()) fmt.Println("Response Headers:") for key, value := range resp.RawResponse.Header.All() { fmt.Printf("%s: %s\n", key, value) } return nil }) _, err := cc.Get("https://example.com/") if err != nil { panic(err) } } ```
Click here to see the result ```plaintext Response Status Code: 200 HTTP protocol: HTTP/1.1 Response Headers: Content-Length: 1256 Content-Type: text/html; charset=UTF-8 Server: ECAcc (dcd/7D5A) Age: 216114 Cache-Control: max-age=604800 Date: Fri, 10 May 2024 10:49:10 GMT Etag: "3147526947+gzip+ident" Expires: Fri, 17 May 2024 10:49:10 GMT Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT Vary: Accept-Encoding X-Cache: HIT ```
#### Built-in Response Hooks Fiber includes built-in response hooks: - **parserResponseCookie**: Parses cookies from the response and stores them in the response object and cookie jar if available. An attribute that cannot be parsed is ignored on its own, in the response and in the jar alike; the cookie keeps its name, its value and its other attributes. - **logger**: Logs information about the raw request and response. It uses the `log.CommonLogger` interface. Only the headers are logged for a streamed body, and nothing is logged when the client has no logger. :::info If a response hook returns an error, Fiber skips the remaining hooks and returns that error. ::: **Example with Multiple Response Hooks:** ```go func main() { cc := client.New() cc.AddResponseHook(func(c *client.Client, r1 *client.Response, r2 *client.Request) error { fmt.Println("Hook 1") return nil }) cc.AddResponseHook(func(c *client.Client, r1 *client.Response, r2 *client.Request) error { fmt.Println("Hook 2") return errors.New("error") }) cc.AddResponseHook(func(c *client.Client, r1 *client.Response, r2 *client.Request) error { fmt.Println("Hook 3") return nil }) _, err := cc.Get("https://example.com/") if err != nil { panic(err) } } ```
Click here to see the result ```shell Hook 1 Hook 2 panic: error goroutine 1 [running]: main.main() main.go:30 +0xd6 exit status 2 ```
### Hook Execution Order Each hook group runs in FIFO order. The complete request pipeline is regular request hooks, built-in request hooks, final request hooks, transport, built-in response hooks, and regular response hooks. **Example:** ```go func main() { cc := client.New() cc.AddRequestHook(func(c *client.Client, r *client.Request) error { fmt.Println("Hook 1") return nil }) cc.AddRequestHook(func(c *client.Client, r *client.Request) error { fmt.Println("Hook 2") return nil }) _, err := cc.Get("https://example.com/") if err != nil { panic(err) } } ```
Click here to see the result ```plaintext Hook 1 Hook 2 ```
--- ## 📤 Request The `Request` struct in Fiber's HTTP client represents an HTTP request. It encapsulates the data required to send a request, including: - **URL**: The endpoint to which the request is sent. - **Method**: The HTTP method (GET, POST, PUT, DELETE, etc.). - **Headers**: Key-value pairs that provide additional information about the request or guide how the response should be processed. - **Body**: The data sent with the request, commonly used with methods like POST and PUT. - **Query Parameters**: Parameters appended to the URL to pass additional data or modify the request's behavior. This structure is designed to be both flexible and efficient, allowing you to easily build and modify HTTP requests as needed. ```go type Request struct { ctx context.Context body any header Header params QueryParam cookies Cookie path PathParam client *Client formData FormData RawRequest *fasthttp.Request url string method string userAgent string boundary string referer string files []*File timeout time.Duration maxRedirects int bodyType bodyType isPathNormalizingDisabled bool clientOwned bool } ``` Use the index to jump straight to any request method; filter by name: ### REST Methods #### Get **Get** sends a GET request to the specified URL. It sets the URL and HTTP method, then dispatches the request to the server. ```go title="Signature" func (r *Request) Get(url string) (*Response, error) ``` #### Post **Post** sends a POST request. It sets the URL and method to POST, then sends the request. ```go title="Signature" func (r *Request) Post(url string) (*Response, error) ``` #### Put **Put** sends a PUT request. It sets the URL and method to PUT, then sends the request. ```go title="Signature" func (r *Request) Put(url string) (*Response, error) ``` #### Patch **Patch** sends a PATCH request. It sets the URL and method to PATCH, then sends the request. ```go title="Signature" func (r *Request) Patch(url string) (*Response, error) ``` #### Delete **Delete** sends a DELETE request. It sets the URL and method to DELETE, then sends the request. ```go title="Signature" func (r *Request) Delete(url string) (*Response, error) ``` #### Head **Head** sends a HEAD request. It sets the URL and method to HEAD, then sends the request. ```go title="Signature" func (r *Request) Head(url string) (*Response, error) ``` #### Options **Options** sends an OPTIONS request. It sets the URL and method to OPTIONS, then sends the request. ```go title="Signature" func (r *Request) Options(url string) (*Response, error) ``` #### Query **Query** sends a QUERY request. It sets the URL and method to QUERY, then sends the request. ```go title="Signature" func (r *Request) Query(url string) (*Response, error) ``` #### Custom **Custom** sends a request using a custom HTTP method. For example, you can use this to send a TRACE or CONNECT request. ```go title="Signature" func (r *Request) Custom(url, method string) (*Response, error) ``` ### AcquireRequest **AcquireRequest** returns a new pooled `Request`. Call `ReleaseRequest` when you're finished to return it to the pool and limit allocations. A request acquired this way stays yours: `Response.Close` releases only the response for it. Requests that the client's REST helpers (`Client.Get`, `Client.Post`, ...) create on your behalf are released by `Response.Close`. ```go title="Signature" func AcquireRequest() *Request ``` ### ReleaseRequest **ReleaseRequest** returns the `Request` to the pool. Do not use it after releasing; doing so may cause data races. ```go title="Signature" func ReleaseRequest(req *Request) ``` ### Method **Method** returns the current HTTP method set for the request. ```go title="Signature" func (r *Request) Method() string ``` ### SetMethod **SetMethod** sets the HTTP method for the `Request` object. Typically, you should use the specialized request methods (`Get`, `Post`, etc.) instead of calling `SetMethod` directly. ```go title="Signature" func (r *Request) SetMethod(method string) *Request ``` ### URL **URL** returns the current URL set in the `Request`. ```go title="Signature" func (r *Request) URL() string ``` ### SetURL **SetURL** sets the URL for the `Request` object. When the request is sent, the URL must start with `http://` or `https://`, after the client's base URL is prepended to one that does not; otherwise the request fails with `ErrURLFormat`. ```go title="Signature" func (r *Request) SetURL(url string) *Request ``` ### Client **Client** retrieves the `Client` instance associated with the `Request`. ```go title="Signature" func (r *Request) Client() *Client ``` ### SetClient **SetClient** assigns a `Client` to the `Request`. If the provided client is `nil`, it will panic with `ErrClientNil`. ```go title="Signature" func (r *Request) SetClient(c *Client) *Request ``` ### Context **Context** returns the `context.Context` of the request, or `context.Background()` if none is set. ```go title="Signature" func (r *Request) Context() context.Context ``` ### SetContext **SetContext** sets the `context.Context` for the request, allowing you to cancel or time out the request. See the [Go blog](https://blog.golang.org/context) and [context](https://pkg.go.dev/context) docs for more details. ```go title="Signature" func (r *Request) SetContext(ctx context.Context) *Request ``` ### Header **Header** returns all values for the specified header key. It searches all header fields stored in the request. ```go title="Signature" func (r *Request) Header(key string) []string ``` :::info When the request is sent, the client's headers are applied first and the request's own headers with the same key replace them rather than being appended. Each send clears those keys before applying them again, so sending the same request twice does not accumulate values. ::: #### Headers **Headers** returns an iterator over all headers in the request. Use `maps.Collect()` to transform them into a map if needed. The returned values are valid only until the request is released. Make copies as required. ```go title="Signature" func (r *Request) Headers() iter.Seq2[string, []string] ```
Example ```go title="Example" req := client.AcquireRequest() req.AddHeader("Golang", "Fiber") req.AddHeader("Test", "123456") req.AddHeader("Test", "654321") for k, v := range req.Headers() { fmt.Printf("Header Key: %s, Header Value: %v\n", k, v) } ``` ```sh Header Key: Golang, Header Value: [Fiber] Header Key: Test, Header Value: [123456 654321] ```
Example with maps.Collect() ```go title="Example with maps.Collect()" req := client.AcquireRequest() req.AddHeader("Golang", "Fiber") req.AddHeader("Test", "123456") req.AddHeader("Test", "654321") headers := maps.Collect(req.Headers()) // Collect all headers into a map for k, v := range headers { fmt.Printf("Header Key: %s, Header Value: %v\n", k, v) } ``` ```sh Header Key: Golang, Header Value: [Fiber] Header Key: Test, Header Value: [123456 654321] ```
#### AddHeader **AddHeader** adds a single header field and its value to the request. ```go title="Signature" func (r *Request) AddHeader(key, val string) *Request ```
Example ```go title="Example" req := client.AcquireRequest() defer client.ReleaseRequest(req) req.AddHeader("Golang", "Fiber") req.AddHeader("Test", "123456") req.AddHeader("Test", "654321") resp, err := req.Get("https://httpbin.org/headers") if err != nil { panic(err) } fmt.Println(resp.String()) ``` ```json { "headers": { "Golang": "Fiber", "Host": "httpbin.org", "Test": "123456,654321", "User-Agent": "fiber", "X-Amzn-Trace-Id": "Root=1-664105d2-033cf7173457adb56d9e7193" } } ```
#### SetHeader **SetHeader** sets a single header field and its value, overriding any previously set header with the same key. ```go title="Signature" func (r *Request) SetHeader(key, val string) *Request ```
Example ```go title="Example" req := client.AcquireRequest() defer client.ReleaseRequest(req) req.SetHeader("Test", "123456") req.SetHeader("Test", "654321") resp, err := req.Get("https://httpbin.org/headers") if err != nil { panic(err) } fmt.Println(resp.String()) ``` ```json { "headers": { "Golang": "Fiber", "Host": "httpbin.org", "Test": "654321", "User-Agent": "fiber", "X-Amzn-Trace-Id": "Root=1-664105e5-5d676ba348450cdb62847f04" } } ```
#### AddHeaders **AddHeaders** adds multiple headers at once from a map of string slices. ```go title="Signature" func (r *Request) AddHeaders(h map[string][]string) *Request ``` #### SetHeaders **SetHeaders** sets multiple headers at once from a map of strings, overriding any previously set headers. ```go title="Signature" func (r *Request) SetHeaders(h map[string]string) *Request ``` ### Param **Param** returns all values associated with a given query parameter key. ```go title="Signature" func (r *Request) Param(key string) []string ``` #### Params **Params** returns an iterator over all query parameters. Use `maps.Collect()` if you need them in a map. The returned values are valid only until the request is released. ```go title="Signature" func (r *Request) Params() iter.Seq2[string, []string] ``` #### AddParam **AddParam** adds a single query parameter key-value pair. ```go title="Signature" func (r *Request) AddParam(key, val string) *Request ```
Example ```go title="Example" req := client.AcquireRequest() defer client.ReleaseRequest(req) req.AddParam("name", "john") req.AddParam("hobbies", "football") req.AddParam("hobbies", "basketball") resp, err := req.Get("https://httpbin.org/response-headers") if err != nil { panic(err) } fmt.Println(string(resp.Body())) ``` ```json { "Content-Length": "145", "Content-Type": "application/json", "hobbies": [ "football", "basketball" ], "name": "joe" } ```
#### SetParam **SetParam** sets a single query parameter key-value pair, overriding any previously set values for that key. ```go title="Signature" func (r *Request) SetParam(key, val string) *Request ``` #### AddParams **AddParams** adds multiple query parameters from a map of string slices. ```go title="Signature" func (r *Request) AddParams(m map[string][]string) *Request ``` #### SetParams **SetParams** sets multiple query parameters from a map of strings, overriding previously set values. ```go title="Signature" func (r *Request) SetParams(m map[string]string) *Request ``` #### SetParamsWithStruct **SetParamsWithStruct** sets multiple query parameters from a struct. Each exported field becomes a parameter named by its `param` struct tag, or by the field name when it has no tag. Nested structs are not supported; see [SetValWithStruct](#setvalwithstruct) for the field types that are used. ```go title="Signature" func (r *Request) SetParamsWithStruct(v any) *Request ```
Example ```go title="Example" req := client.AcquireRequest() defer client.ReleaseRequest(req) req.SetParamsWithStruct(struct { Name string `param:"name"` Hobbies []string `param:"hobbies"` }{ Name: "John Doe", Hobbies: []string{ "Football", "Basketball", }, }) resp, err := req.Get("https://httpbin.org/response-headers") if err != nil { panic(err) } fmt.Println(string(resp.Body())) ``` ```json { "Content-Length": "147", "Content-Type": "application/json", "hobbies": [ "Football", "Basketball" ], "name": "John Doe" } ```
#### DelParams **DelParams** removes one or more query parameters by their keys. ```go title="Signature" func (r *Request) DelParams(key ...string) *Request ``` ### UserAgent **UserAgent** returns the user agent currently set in the request. ```go title="Signature" func (r *Request) UserAgent() string ``` ### SetUserAgent **SetUserAgent** sets the user agent header for the request, overriding the one set at the client level if any. Without either, `User-Agent: fiber` is sent. A `User-Agent` set with `SetHeader` or `AddHeader` is replaced when the request is sent, so use this method for it. ```go title="Signature" func (r *Request) SetUserAgent(ua string) *Request ``` ### Boundary **Boundary** returns the multipart boundary used by the request. ```go title="Signature" func (r *Request) Boundary() string ``` ### SetBoundary **SetBoundary** sets the multipart boundary for file uploads. Without one, a boundary made of `FiberFormBoundary` and 16 random characters is generated when a request with files is sent. ```go title="Signature" func (r *Request) SetBoundary(b string) *Request ``` ### Referer **Referer** returns the Referer header value currently set in the request. ```go title="Signature" func (r *Request) Referer() string ``` ### SetReferer **SetReferer** sets the Referer header for the request, overriding the one set at the client level if any. When neither is set, no `Referer` header is sent. A `Referer` set with `SetHeader` or `AddHeader` is replaced when the request is sent, or removed when no referer is set, so use this method for it. ```go title="Signature" func (r *Request) SetReferer(referer string) *Request ``` ### Cookie **Cookie** returns the value of the specified cookie. If the cookie does not exist, it returns an empty string. ```go title="Signature" func (r *Request) Cookie(key string) string ``` #### Cookies **Cookies** returns an iterator over all cookies set in the request. Use `maps.Collect()` to gather them into a map. ```go title="Signature" func (r *Request) Cookies() iter.Seq2[string, string] ``` #### SetCookie **SetCookie** sets a single cookie key-value pair, overriding any previously set cookie with the same key. ```go title="Signature" func (r *Request) SetCookie(key, val string) *Request ``` #### SetCookies **SetCookies** sets multiple cookies from a map, overriding previously set values. ```go title="Signature" func (r *Request) SetCookies(m map[string]string) *Request ```
Example ```go title="Example" req := client.AcquireRequest() defer client.ReleaseRequest(req) req.SetCookies(map[string]string{ "cookie1": "value1", "cookie2": "value2", }) resp, err := req.Get("https://httpbin.org/cookies") if err != nil { panic(err) } fmt.Println(string(resp.Body())) ``` ```json { "cookies": { "cookie1": "value1", "cookie2": "value2" } } ```
#### SetCookiesWithStruct **SetCookiesWithStruct** sets multiple cookies from a struct. Each field is named by its `cookie` struct tag, or by the field name when it has no tag. ```go title="Signature" func (r *Request) SetCookiesWithStruct(v any) *Request ``` #### DelCookies **DelCookies** removes one or more cookies by their keys. ```go title="Signature" func (r *Request) DelCookies(key ...string) *Request ``` ### PathParam **PathParam** returns the value of a named path parameter. If not found, returns an empty string. ```go title="Signature" func (r *Request) PathParam(key string) string ``` #### PathParams **PathParams** returns an iterator over all path parameters in the request. Use `maps.Collect()` to convert them into a map. ```go title="Signature" func (r *Request) PathParams() iter.Seq2[string, string] ``` #### SetPathParam **SetPathParam** sets a single path parameter key-value pair, overriding previously set values. Placeholders are matched as `:name`, and where several registered names fit, the longest one wins, so a name holding a `-`, `.` or `:` resolves as itself. A name otherwise ends at a path-segment boundary (`/`, `-`, `.`, `:`, `\`, `?` or `#`), so `:id` does not also match the start of `:idx`. Values are percent-encoded with path-segment rules, and each placeholder is resolved once — a substituted value is never scanned for further placeholders. A placeholder with no value set is sent as written. A value stays inside the one path segment its placeholder occupies. Path normalizing percent-decodes the path before it collapses `.` and `..`, so escaping a separator is not enough on its own: a value containing `/` or `\`, a value that is exactly `.` or `..`, or an empty value — which leaves a `//` that collapses and drops the segment — fails the request with `ErrPathParamInPath` rather than changing the path the template set. A `%2F` you write yourself is not a separator — it is escaped to `%252F` and decodes back to the literal text `%2F`. The host can be templated too (`https://:tenant.example.com/api`). A placeholder in the authority whose name is only digits is treated as the port and is left unchanged, and the colons inside an IPv6 literal such as `http://[2001:db8::1]/api` are part of the address rather than placeholders. A value substituted into the authority is **not** escaped — a `%XX` below `0x80` inside a host makes the URL parser discard the rest of the URL — so it must already be a valid host: unreserved characters, `$`, `&`, `+`, `=` and valid UTF-8 for an IDN label. Malformed UTF-8 is rejected too — the bytes reach the `Host` header verbatim, and an overlong sequence such as `%C0%AF` is a `/` to a lenient decoder. Anything else, including a `:` or `@` that would add a port or a userinfo section and send the request to another host, fails the request with `ErrPathParamInHost`. A port can still be templated by giving the placeholder a name that is not all digits, as in `https://example.com::port/api`. `SetDisablePathNormalizing(true)` on the client or request turns that check off for `/` and `\`, but only for a client that Fiber knows sends the path as built: one whose wrapped fasthttp client has path normalizing turned off and has not served a host while it was on. [SetDisablePathNormalizing](./rest.md#setdisablepathnormalizing) on the client does that for a client from `New`, `NewWithHostClient` or `NewWithLBClient`. A `fasthttp.Client` given to [NewWithClient](./rest.md#newwithclient) may have served hosts already and Fiber cannot tell, so it counts only when it was created with `DisablePathNormalizing: true`. Nothing decodes the path afterwards then, a `/` in a value is emitted as `%2F` and the value is sent as one segment. Whether it stays one segment is up to the server — one that unescapes paths, including a Fiber server configured with `UnescapePath`, decodes it again while routing. On any other client the check stays on, whatever the client or the request asks for, so a value such as `x/../../admin` is rejected rather than changing the path that is sent. The values the setting never lets through are `.` and `..`, which would go out as dot segments that the receiving server resolves, and an empty value, which leaves a `//` that a server merging slashes drops: no escape keeps them in place. A `?` or `#` in a value is always contained; normalizing does not decode those. ```go title="Signature" func (r *Request) SetPathParam(key, val string) *Request ```
Example ```go title="Example" req := client.AcquireRequest() defer client.ReleaseRequest(req) req.SetPathParam("base64", "R29maWJlcg==") resp, err := req.Get("https://httpbin.org/base64/:base64") if err != nil { panic(err) } fmt.Println(string(resp.Body())) ``` ```plaintext Gofiber ```
#### SetPathParams **SetPathParams** sets multiple path parameters at once, overriding previously set values. ```go title="Signature" func (r *Request) SetPathParams(m map[string]string) *Request ``` #### SetPathParamsWithStruct **SetPathParamsWithStruct** sets multiple path parameters from a struct. Each field is named by its `path` struct tag, or by the field name when it has no tag. ```go title="Signature" func (r *Request) SetPathParamsWithStruct(v any) *Request ``` #### DelPathParams **DelPathParams** deletes one or more path parameters by their keys. ```go title="Signature" func (r *Request) DelPathParams(key ...string) *Request ``` #### ResetPathParams **ResetPathParams** deletes all path parameters. ```go title="Signature" func (r *Request) ResetPathParams() *Request ``` ### SetJSON **SetJSON** sets the request body to a JSON-encoded payload. When the request is sent, the `Content-Type` and `Accept` headers are set to `application/json`, replacing any values set on the client or the request. ```go title="Signature" func (r *Request) SetJSON(v any) *Request ``` ### SetXML **SetXML** sets the request body to an XML-encoded payload. When the request is sent, the `Content-Type` header is set to `application/xml`. ```go title="Signature" func (r *Request) SetXML(v any) *Request ``` ### SetCBOR **SetCBOR** sets the request body to a CBOR-encoded payload. It automatically sets the `Content-Type` to `application/cbor`. ```go title="Signature" func (r *Request) SetCBOR(v any) *Request ``` ### SetRawBody **SetRawBody** sets the request body to raw bytes. ```go title="Signature" func (r *Request) SetRawBody(v []byte) *Request ``` ### FormData **FormData** returns all values associated with the given form data field. ```go title="Signature" func (r *Request) FormData(key string) []string ``` #### AllFormData **AllFormData** returns an iterator over all form data fields. Use `maps.Collect()` if needed. ```go title="Signature" func (r *Request) AllFormData() iter.Seq2[string, []string] ``` #### AddFormData **AddFormData** adds a single form data key-value pair. ```go title="Signature" func (r *Request) AddFormData(key, val string) *Request ```
Example ```go title="Example" req := client.AcquireRequest() defer client.ReleaseRequest(req) req.AddFormData("points", "80") req.AddFormData("points", "90") req.AddFormData("points", "100") resp, err := req.Post("https://httpbin.org/post") if err != nil { panic(err) } fmt.Println(string(resp.Body())) ``` ```json { "args": {}, "data": "", "files": {}, "form": { "points": [ "80", "90", "100" ] }, // ... } ```
#### SetFormData **SetFormData** sets a single form data field, overriding any previously set values. ```go title="Signature" func (r *Request) SetFormData(key, val string) *Request ```
Example ```go title="Example" req := client.AcquireRequest() defer client.ReleaseRequest(req) req.SetFormData("name", "john") req.SetFormData("email", "john@doe.com") resp, err := req.Post("https://httpbin.org/post") if err != nil { panic(err) } fmt.Println(string(resp.Body())) ``` ```json { "args": {}, "data": "", "files": {}, "form": { "email": "john@doe.com", "name": "john" }, // ... } ```
#### AddFormDataWithMap **AddFormDataWithMap** adds multiple form data fields and values from a map of string slices. ```go title="Signature" func (r *Request) AddFormDataWithMap(m map[string][]string) *Request ``` #### SetFormDataWithMap **SetFormDataWithMap** sets multiple form data fields from a map of strings. ```go title="Signature" func (r *Request) SetFormDataWithMap(m map[string]string) *Request ``` #### SetFormDataWithStruct **SetFormDataWithStruct** sets multiple form data fields from a struct. Each field is named by its `form` struct tag, or by the field name when it has no tag. ```go title="Signature" func (r *Request) SetFormDataWithStruct(v any) *Request ``` #### DelFormData **DelFormData** deletes one or more form data fields by their keys. ```go title="Signature" func (r *Request) DelFormData(key ...string) *Request ``` ### File **File** returns a file from the request by its name. A file added without a name is matched by the base name of its path. ```go title="Signature" func (r *Request) File(name string) *File ``` #### Files **Files** returns all files in the request as a slice. The returned slice is valid only until the request is released. ```go title="Signature" func (r *Request) Files() []*File ``` #### FileByPath **FileByPath** returns a file from the request by its file path. ```go title="Signature" func (r *Request) FileByPath(path string) *File ``` #### AddFile **AddFile** adds a single file to the request from a file path. ```go title="Signature" func (r *Request) AddFile(path string) *Request ```
Example ```go title="Example" req := client.AcquireRequest() defer client.ReleaseRequest(req) req.AddFile("test.txt") resp, err := req.Post("https://httpbin.org/post") if err != nil { panic(err) } fmt.Println(string(resp.Body())) ``` ```json { "args": {}, "data": "", "files": { "file1": "This is an empty file!\n" }, "form": {}, // ... } ```
#### AddFileWithReader **AddFileWithReader** adds a single file to the request from an `io.ReadCloser`. ```go title="Signature" func (r *Request) AddFileWithReader(name string, reader io.ReadCloser) *Request ```
Example ```go title="Example" req := client.AcquireRequest() defer client.ReleaseRequest(req) buf := bytes.NewBuffer([]byte("Hello, World!")) req.AddFileWithReader("test.txt", io.NopCloser(buf)) resp, err := req.Post("https://httpbin.org/post") if err != nil { panic(err) } fmt.Println(string(resp.Body())) ``` ```json { "args": {}, "data": "", "files": { "file1": "Hello, World!" }, "form": {}, // ... } ```
#### AddFiles **AddFiles** adds multiple files to the request at once. ```go title="Signature" func (r *Request) AddFiles(files ...*File) *Request ``` ### Timeout **Timeout** returns the timeout duration set in the request. ```go title="Signature" func (r *Request) Timeout() time.Duration ``` ### SetTimeout **SetTimeout** sets a timeout for the request, overriding any timeout set at the client level. When the timeout elapses, or the request's context is canceled, the request fails with `ErrTimeoutOrCancel`. ```go title="Signature" func (r *Request) SetTimeout(t time.Duration) *Request ```
Example 1 ```go title="Example 1" req := client.AcquireRequest() defer client.ReleaseRequest(req) req.SetTimeout(5 * time.Second) resp, err := req.Get("https://httpbin.org/delay/4") if err != nil { panic(err) } fmt.Println(string(resp.Body())) ``` ```json { "args": {}, "data": "", "files": {}, "form": {}, // ... } ```
Example 2 ```go title="Example 2" req := client.AcquireRequest() defer client.ReleaseRequest(req) req.SetTimeout(5 * time.Second) resp, err := req.Get("https://httpbin.org/delay/6") if err != nil { panic(err) } fmt.Println(string(resp.Body())) ``` ```shell panic: timeout or cancel goroutine 1 [running]: main.main() main.go:18 +0xeb exit status 2 ```
### MaxRedirects **MaxRedirects** returns the maximum number of redirects allowed for the request. The default is `0`. ```go title="Signature" func (r *Request) MaxRedirects() int ``` ### SetMaxRedirects **SetMaxRedirects** sets the maximum number of redirects to follow for the request. The default is `0`, which follows none: the redirect response is returned as-is. The client has no redirect limit of its own, so set one on each request or with `Config.MaxRedirects`. ```go title="Signature" func (r *Request) SetMaxRedirects(count int) *Request ``` :::info Redirects are followed only for `GET`, `HEAD` and `QUERY` requests. Other methods receive the redirect response as-is, whatever the limit. ::: While redirects are followed: - After a `301`, `302` or `303` the request is repeated as a `GET` without its body; a `HEAD` stays a `HEAD`. A `307` or `308` keeps the method and the body. - When the target is on another host, the `Authorization`, `Proxy-Authorization`, `Proxy-Authenticate`, `WWW-Authenticate`, `Cookie` and `Cookie2` headers are dropped. The same host on any port, and its subdomains, keep them. - A redirect from `https` to `http` fails with `ErrRedirectDowngrade`. Leave the limit at `0` to take such a hop yourself. - A redirect without a `Location` fails with `fasthttp.ErrMissingLocation`. A `Location` that cannot be parsed or names a `scheme://` other than `http` or `https` fails with `fasthttp.ErrorInvalidURI` without being requested, as does a request URL that cannot be parsed. A chain longer than the limit fails with `fasthttp.ErrTooManyRedirects`. ### DisablePathNormalizing **DisablePathNormalizing** reports whether path normalizing is disabled for this request. It does not reflect the client's setting; Fiber builds the URL without normalizing it when either one disables it, see [SetDisablePathNormalizing](#setdisablepathnormalizing) for what reaches the wire. ```go title="Signature" func (r *Request) DisablePathNormalizing() bool ``` ### SetDisablePathNormalizing **SetDisablePathNormalizing** turns path normalizing off or on for the request. It is on by default: the request path is normalized before it is sent, which decodes percent-escapes, collapses `//` and resolves `.` and `..` segments, so `/a//b/./c%2Fd` goes out as `/a/b/c/d`. With normalizing off, Fiber builds the URL without normalizing it. Whether the path is normalized again when the request is written is up to the wrapped fasthttp client, which a single request cannot change: the path goes out as written only when the client has normalizing turned off, with [SetDisablePathNormalizing](./rest.md#setdisablepathnormalizing) on the client or with a fasthttp client created with `DisablePathNormalizing: true` (see [NewWithClient](./rest.md#newwithclient)). On any other client this setting changes neither what is sent nor the path parameter check. Path parameter values are checked differently while normalizing is off, as described under [SetPathParam](#setpathparam). ```go title="Signature" func (r *Request) SetDisablePathNormalizing(disable bool) *Request ``` ### Send **Send** executes the HTTP request and returns a `Response`. A request without a client, such as one from `AcquireRequest`, is sent with the default client returned by `client.C()`. ```go title="Signature" func (r *Request) Send() (*Response, error) ``` ### Reset **Reset** clears the `Request` object, making it ready for reuse. This is used by `ReleaseRequest`. ```go title="Signature" func (r *Request) Reset() ``` ### Header **Header** is a wrapper around `fasthttp.RequestHeader`, storing headers for both the client and request. ```go type Header struct { *fasthttp.RequestHeader } ``` #### PeekMultiple **PeekMultiple** returns multiple values associated with the same header key. ```go title="Signature" func (h *Header) PeekMultiple(key string) []string ``` #### AddHeaders **AddHeaders** adds multiple headers from a map of string slices. ```go title="Signature" func (h *Header) AddHeaders(r map[string][]string) ``` #### SetHeaders **SetHeaders** sets multiple headers from a map of strings, overriding previously set headers. ```go title="Signature" func (h *Header) SetHeaders(r map[string]string) ``` ### QueryParam **QueryParam** is a wrapper around `fasthttp.Args`, storing query parameters. ```go type QueryParam struct { *fasthttp.Args } ``` #### Keys **Keys** returns all keys in the query parameters. ```go title="Signature" func (p *QueryParam) Keys() []string ``` #### AddParams **AddParams** adds multiple query parameters from a map of string slices. ```go title="Signature" func (p *QueryParam) AddParams(r map[string][]string) ``` #### SetParams **SetParams** sets multiple query parameters from a map of strings, overriding previously set values. ```go title="Signature" func (p *QueryParam) SetParams(r map[string]string) ``` #### SetParamsWithStruct **SetParamsWithStruct** sets multiple query parameters from a struct, naming each field by its `param` struct tag. Nested structs are not supported. ```go title="Signature" func (p *QueryParam) SetParamsWithStruct(v any) ``` ### Cookie **Cookie** is a map that stores cookies. ```go type Cookie map[string]string ``` #### Add **Add** adds a cookie key-value pair. ```go title="Signature" func (c Cookie) Add(key, val string) ``` #### Del **Del** removes a cookie by its key. ```go title="Signature" func (c Cookie) Del(key string) ``` #### SetCookie **SetCookie** sets a single cookie key-value pair, overriding previously set values. ```go title="Signature" func (c Cookie) SetCookie(key, val string) ``` #### SetCookies **SetCookies** sets multiple cookies from a map of strings. ```go title="Signature" func (c Cookie) SetCookies(m map[string]string) ``` #### SetCookiesWithStruct **SetCookiesWithStruct** sets multiple cookies from a struct, naming each field by its `cookie` struct tag. ```go title="Signature" func (c Cookie) SetCookiesWithStruct(v any) ``` #### DelCookies **DelCookies** deletes one or more cookies by their keys. ```go title="Signature" func (c Cookie) DelCookies(key ...string) ``` #### All **All** returns an iterator over all cookies. The key and value returned should not be retained after the loop ends. ```go title="Signature" func (c Cookie) All() iter.Seq2[string, string] ``` #### Reset **Reset** clears all cookies. ```go title="Signature" func (c Cookie) Reset() ``` ### PathParam **PathParam** is a map that stores path parameters. ```go type PathParam map[string]string ``` #### Add **Add** adds a path parameter key-value pair. ```go title="Signature" func (p PathParam) Add(key, val string) ``` #### Del **Del** removes a path parameter by its key. ```go title="Signature" func (p PathParam) Del(key string) ``` #### SetParam **SetParam** sets a single path parameter key-value pair, overriding previously set values. ```go title="Signature" func (p PathParam) SetParam(key, val string) ``` #### SetParams **SetParams** sets multiple path parameters from a map of strings. ```go title="Signature" func (p PathParam) SetParams(m map[string]string) ``` #### SetParamsWithStruct **SetParamsWithStruct** sets multiple path parameters from a struct, naming each field by its `path` struct tag. ```go title="Signature" func (p PathParam) SetParamsWithStruct(v any) ``` #### DelParams **DelParams** deletes one or more path parameters by their keys. ```go title="Signature" func (p PathParam) DelParams(key ...string) ``` #### All **All** returns an iterator over all path parameters. The key and value returned should not be retained after the loop ends. ```go title="Signature" func (p PathParam) All() iter.Seq2[string, string] ``` #### Reset **Reset** clears all path parameters. ```go title="Signature" func (p PathParam) Reset() ``` ### FormData **FormData** is a wrapper around `fasthttp.Args`, used to handle URL-encoded and form-data (multipart) request bodies. ```go type FormData struct { *fasthttp.Args } ``` #### Keys **Keys** returns all form data keys. ```go title="Signature" func (f *FormData) Keys() []string ``` #### Add **Add** adds a single form field key-value pair. ```go title="Signature" func (f *FormData) Add(key, val string) ``` #### Set **Set** sets a single form field key-value pair, overriding any previously set values. ```go title="Signature" func (f *FormData) Set(key, val string) ``` #### AddWithMap **AddWithMap** adds multiple form fields from a map of string slices. ```go title="Signature" func (f *FormData) AddWithMap(m map[string][]string) ``` #### SetWithMap **SetWithMap** sets multiple form fields from a map of strings. ```go title="Signature" func (f *FormData) SetWithMap(m map[string]string) ``` #### SetWithStruct **SetWithStruct** sets multiple form fields from a struct, naming each field by its `form` struct tag. ```go title="Signature" func (f *FormData) SetWithStruct(v any) ``` #### DelData **DelData** deletes one or more form fields by their keys. ```go title="Signature" func (f *FormData) DelData(key ...string) ``` #### Reset **Reset** clears all form data fields. ```go title="Signature" func (f *FormData) Reset() ``` ### File **File** represents a file to be uploaded. It can be specified by name, path, or an `io.ReadCloser`. ```go type File struct { name string fieldName string path string reader io.ReadCloser } ``` When the request is sent, a file with a path but no name takes the base name of the path as its file name, and a file without a field name is sent in the field `file1`, `file2`, and so on, by its position. A file with neither a name nor a path makes the request fail with `ErrFileNoName`. #### AcquireFile **AcquireFile** returns a `File` from the pool and applies any provided `SetFileFunc` functions to it. Release it with `ReleaseFile` when done. A file that you add to a request is released together with the request, so do not release it yourself in that case. ```go title="Signature" func AcquireFile(setter ...SetFileFunc) *File ``` #### ReleaseFile **ReleaseFile** returns the `File` to the pool. Do not use the file afterward. ```go title="Signature" func ReleaseFile(f *File) ``` #### SetFileFunc **SetFileFunc** is a function that modifies a `File`, the option type that `AcquireFile` accepts. These helpers create the common ones: - **SetFileName** sets the file name that is sent in the multipart form. - **SetFileFieldName** sets the name of the form field that carries the file. - **SetFilePath** sets the path the file is read from. - **SetFileReader** sets the `io.ReadCloser` the file is read from. It is closed automatically when the request body is built. ```go title="Signature" type SetFileFunc func(f *File) func SetFileName(n string) SetFileFunc func SetFileFieldName(p string) SetFileFunc func SetFilePath(p string) SetFileFunc func SetFileReader(r io.ReadCloser) SetFileFunc ```
Example ```go title="Example" req := client.AcquireRequest() defer client.ReleaseRequest(req) req.AddFiles(client.AcquireFile( client.SetFilePath("./avatar.png"), client.SetFileFieldName("avatar"), )) resp, err := req.Post("https://httpbin.org/post") if err != nil { panic(err) } fmt.Println(string(resp.Body())) ```
#### SetName **SetName** sets the file's name. ```go title="Signature" func (f *File) SetName(n string) ``` #### SetFieldName **SetFieldName** sets the field name of the file in the multipart form. ```go title="Signature" func (f *File) SetFieldName(n string) ``` #### SetPath **SetPath** sets the file's path. ```go title="Signature" func (f *File) SetPath(p string) ``` #### SetReader **SetReader** sets the file's `io.ReadCloser`. The reader is closed automatically when the request body is parsed. ```go title="Signature" func (f *File) SetReader(r io.ReadCloser) ``` #### Reset **Reset** clears the file's fields. ```go title="Signature" func (f *File) Reset() ``` ### SetValWithStruct **SetValWithStruct** fills a `WithStruct` from the fields of a struct. The struct setters of the request and the client, such as `SetParamsWithStruct`, use it with the struct tags `param`, `cookie`, `path` and `form`. Call it yourself to fill a collection of your own that implements `WithStruct`. `v` must be a struct or a non-nil pointer to a struct. `nil` panics, and so does a nil pointer to a struct that has exported fields. Any other value is ignored, including a nil map, a nil slice, a nil pointer to a type that is not a struct, and a pointer to a pointer. Each exported field is added under the name in its `tagName` struct tag, or under the field name when the tag is missing or empty. Fields of type string, integer, float, complex or bool are written as text, and a slice or array of those adds one value per element under the same name. Fields of other types, such as nested structs, maps and pointers, are skipped. A name that is already present is deleted first, even when the field itself is skipped, so the struct's values replace the earlier ones. A cookie or path parameter holds one value per name, so for a slice only the last element remains. ```go title="Signature" type WithStruct interface { Add(name, obj string) Del(name string) } func SetValWithStruct(p WithStruct, tagName string, v any) ```
Example ```go title="Example" type filter struct { Query string `param:"q"` Tags []string `param:"tag"` Page int skip string } args := fasthttp.AcquireArgs() defer fasthttp.ReleaseArgs(args) // *fasthttp.Args has the Add and Del methods that WithStruct needs. client.SetValWithStruct(args, "param", filter{ Query: "fiber", Tags: []string{"go", "http"}, Page: 2, skip: "ignored", }) fmt.Println(args.String()) ``` ```text q=fiber&tag=go&tag=http&Page=2 ```
--- ## 📥 Response The `Response` struct in Fiber's HTTP client represents the server's reply and exposes: - **Status Code**: The HTTP status code returned by the server (e.g., `200 OK`, `404 Not Found`). - **Headers**: All HTTP headers returned by the server, providing additional response-related information. - **Body**: The response body content, which can be JSON, XML, plain text, or other formats. - **Cookies**: Any cookies the server sent along with the response. It makes it easy to inspect and handle data returned by the server. ```go type Response struct { client *Client request *Request RawResponse *fasthttp.Response cookie []*fasthttp.Cookie respondedHost []byte respondedPath []byte requestOwned bool } ``` Use the index to jump straight to any response method; filter by name: ### AcquireResponse **AcquireResponse** returns a new pooled `Response`. Call `ReleaseResponse` when you're done to return it to the pool and limit allocations. ```go title="Signature" func AcquireResponse() *Response ``` ### ReleaseResponse **ReleaseResponse** puts the `Response` back into the pool. Do not use it after releasing; doing so can trigger data races. ```go title="Signature" func ReleaseResponse(resp *Response) ``` ### Status **Status** returns the HTTP status message (e.g., `OK`, `Not Found`) associated with the response. ```go title="Signature" func (r *Response) Status() string ``` ### StatusCode **StatusCode** returns the numeric HTTP status code of the response. ```go title="Signature" func (r *Response) StatusCode() int ``` ### Protocol **Protocol** returns the HTTP protocol used (e.g., `HTTP/1.1`) for the response. The client is built on fasthttp, which speaks HTTP/1.x only. ```go title="Signature" func (r *Response) Protocol() string ```
Example ```go title="Example" resp, err := client.Get("https://httpbin.org/get") if err != nil { panic(err) } fmt.Println(resp.Protocol()) ``` **Output:** ```text HTTP/1.1 ```
### Header **Header** retrieves the value of a specific response header by key. If multiple values exist for the same header, this returns the first one. The returned string is a copy, so it stays valid after the response is released. ```go title="Signature" func (r *Response) Header(key string) string ``` ### Headers **Headers** returns an iterator over all response headers. Use `maps.Collect()` to convert them into a map if desired. The returned values are only valid until the response is released, so make copies if needed. ```go title="Signature" func (r *Response) Headers() iter.Seq2[string, []string] ```
Example ```go title="Example" resp, err := client.Get("https://httpbin.org/get") if err != nil { panic(err) } for key, values := range resp.Headers() { fmt.Printf("%s => %s\n", key, strings.Join(values, ", ")) } ``` **Output:** ```text Date => Wed, 04 Dec 2024 15:28:29 GMT Connection => keep-alive Access-Control-Allow-Origin => * Access-Control-Allow-Credentials => true ```
Example with maps.Collect() ```go title="Example with maps.Collect()" resp, err := client.Get("https://httpbin.org/get") if err != nil { panic(err) } headers := maps.Collect(resp.Headers()) for key, values := range headers { fmt.Printf("%s => %s\n", key, strings.Join(values, ", ")) } ``` **Output:** ```text Date => Wed, 04 Dec 2024 15:28:29 GMT Connection => keep-alive Access-Control-Allow-Origin => * Access-Control-Allow-Credentials => true ```
### Cookies **Cookies** returns a slice of all cookies set by the server in this response. The slice is only valid until the response is released. An attribute the parser cannot read — a `Max-Age` that is not an integer, an `Expires` in an unrecognized format — is ignored on its own (RFC 6265 §5.2); the cookie keeps its name, its value and every other attribute rather than being dropped. ```go title="Signature" func (r *Response) Cookies() []*fasthttp.Cookie ```
Example ```go title="Example" resp, err := client.Get("https://httpbin.org/cookies/set/go/fiber") if err != nil { panic(err) } cookies := resp.Cookies() for _, cookie := range cookies { fmt.Printf("%s => %s\n", string(cookie.Key()), string(cookie.Value())) } ``` **Output:** ```text go => fiber ```
### Body **Body** returns the raw response body as a byte slice. ```go title="Signature" func (r *Response) Body() []byte ``` ### BodyStream **BodyStream** returns the response body as an `io.Reader`, allowing incremental reading without loading the entire body into memory. This is particularly useful when `Client.SetStreamResponseBody(true)` is enabled. When streaming is enabled, the underlying stream from fasthttp is returned directly. When streaming is not enabled, a `bytes.Reader` wrapping the body is returned as a fallback. :::note When using `BodyStream()`, the response body is consumed as you read. Calling `Body()` afterward may return an empty slice if the stream has been fully read. ::: ```go title="Signature" func (r *Response) BodyStream() io.Reader ```
Example ```go title="Example" cc := client.New() cc.SetStreamResponseBody(true) resp, err := cc.Get("https://httpbin.org/bytes/1024") if err != nil { panic(err) } defer resp.Close() buf := make([]byte, 256) total, err := io.CopyBuffer(io.Discard, resp.BodyStream(), buf) if err != nil { panic(err) } fmt.Printf("Read %d bytes\n", total) ``` **Output:** ```text Read 1024 bytes ```
### IsStreaming **IsStreaming** returns `true` if the response body is being streamed (i.e., when `Client.SetStreamResponseBody(true)` was set and the underlying transport provided a stream). ```go title="Signature" func (r *Response) IsStreaming() bool ```
Example ```go title="Example" cc := client.New() cc.SetStreamResponseBody(true) resp, err := cc.Get("https://httpbin.org/get") if err != nil { panic(err) } defer resp.Close() if resp.IsStreaming() { fmt.Println("Response is streaming") // Use resp.BodyStream() to read incrementally } else { fmt.Println("Response is buffered") // Use resp.Body() for direct access } ```
### String **String** returns the response body as a trimmed string. ```go title="Signature" func (r *Response) String() string ``` ### JSON **JSON** unmarshal the response body into the provided variable `v` using JSON. `v` should be a pointer to a struct or a type compatible with JSON unmarshal. It uses the client's JSON unmarshaler, `encoding/json` unless replaced with [SetJSONUnmarshal](./rest.md#setjsonunmarshal), and returns `ErrClientNil` for a response that has no client, such as one from `AcquireResponse`. `XML` and `CBOR` behave the same way. ```go title="Signature" func (r *Response) JSON(v any) error ```
Example ```go title="Example" type Body struct { Slideshow struct { Author string `json:"author"` Date string `json:"date"` Title string `json:"title"` } `json:"slideshow"` } var out Body resp, err := client.Get("https://httpbin.org/json") if err != nil { panic(err) } if err = resp.JSON(&out); err != nil { panic(err) } fmt.Printf("%+v\n", out) ``` **Output:** ```text {Slideshow:{Author:Yours Truly Date:date of publication Title:Sample Slide Show}} ```
### XML **XML** unmarshal the response body into the provided variable `v` using XML decoding, with the client's XML unmarshaler ([SetXMLUnmarshal](./rest.md#setxmlunmarshal)). ```go title="Signature" func (r *Response) XML(v any) error ``` ### CBOR **CBOR** unmarshal the response body into `v` using CBOR decoding, with the client's CBOR unmarshaler ([SetCBORUnmarshal](./rest.md#setcborunmarshal)). ```go title="Signature" func (r *Response) CBOR(v any) error ``` ### Save **Save** writes the response body to a file or an `io.Writer`. If `v` is a string, it interprets it as a file path, creates the file (and directories if needed), and writes the response to it. If `v` is an `io.Writer`, it writes directly to it and, when the copy succeeds, closes it if it also implements `io.Closer`, as an `*os.File` does. Any other type of `v` returns `ErrNotSupportSaveMethod`. With response streaming enabled, the body is copied from the stream, so it cannot be read again afterward. ```go title="Signature" func (r *Response) Save(v any) error ``` ### Reset **Reset** clears the `Response` object, making it ready for reuse by `ReleaseResponse`. ```go title="Signature" func (r *Response) Reset() ``` ### Close **Close** releases the `Response` back to its pool, along with the `Request` when the client created it for this call (`Client.Get` and the other REST helpers). A `Request` you acquired yourself, with `AcquireRequest` or `Client.R()`, stays yours to release with `ReleaseRequest`. :::warning After calling `Close`, any attempt to use the response, or a request it released, may result in data races or undefined behavior. Ensure all processing is complete before closing. ::: ```go title="Signature" func (r *Response) Close() ``` --- ## 🖥️ REST The Fiber Client is a high-performance HTTP client built on FastHTTP. It handles both internal service calls and external requests with minimal overhead. Use the index to jump straight to any client method; filter by name or by category: ### Features - **Lightweight and fast**: built on FastHTTP for minimal overhead. - **Flexible configuration**: set global defaults like timeouts or headers and override them per request. - **Connection pooling**: reuses persistent connections instead of opening new ones. - **Timeouts and retries**: supports per-request deadlines and retry policies for transient errors. ### Usage Create a client with any required configuration, then send requests: ```go package main import ( "fmt" "time" "github.com/gofiber/fiber/v3/client" ) func main() { cc := client.New() cc.SetTimeout(10 * time.Second) // Send a GET request resp, err := cc.Get("https://httpbin.org/get") if err != nil { panic(err) } fmt.Printf("Status: %d\n", resp.StatusCode()) fmt.Printf("Body: %s\n", string(resp.Body())) } ``` See [examples](examples.md) for more detailed usage. :::note A client can send requests from many goroutines at once after its configuration is complete. Changing the configuration while requests are in flight needs your own synchronization. ::: ```go type Client struct { logger log.CommonLogger transport httpClientTransport header *Header params *QueryParam cookies *Cookie path *PathParam jsonMarshal utils.JSONMarshal jsonUnmarshal utils.JSONUnmarshal xmlMarshal utils.XMLMarshal xmlUnmarshal utils.XMLUnmarshal cborMarshal utils.CBORMarshal cborUnmarshal utils.CBORUnmarshal cookieJar *CookieJar retryConfig *RetryConfig baseURL string userAgent string referer string userRequestHooks []RequestHook builtinRequestHooks []RequestHook finalRequestHooks []RequestHook userResponseHooks []ResponseHook builtinResponseHooks []ResponseHook timeout time.Duration mu sync.RWMutex isDebug bool isPathNormalizingDisabled bool } ``` #### New **New** creates and returns a new Client object. ```go title="Signature" func New() *Client ``` #### NewWithClient **NewWithClient** creates and returns a new Client object from an existing `fasthttp.Client`. It panics if `c` is `nil`. Fiber cannot tell whether that client has served a host already. Unless it was created with `DisablePathNormalizing: true`, [SetDisablePathNormalizing](#setdisablepathnormalizing) changes what is sent to hosts it has not served yet, but the check that keeps a path parameter value inside its segment stays on. ```go title="Signature" func NewWithClient(c *fasthttp.Client) *Client ``` #### NewWithHostClient **NewWithHostClient** creates and returns a new Client object from an existing `fasthttp.HostClient`. A `HostClient` connects to its own `Addr` whatever host the request URL names; that host only becomes the `Host` header. It panics if `c` is `nil`. ```go title="Signature" func NewWithHostClient(c *fasthttp.HostClient) *Client ``` #### NewWithLBClient **NewWithLBClient** creates and returns a new Client object from an existing `fasthttp.LBClient`. TLS, dialer, proxy and response streaming settings are applied to every host client behind the load balancer. Requests go through `LBClient.Do`, so they fail with `fasthttp.ErrTimeout` after `LBClient.Timeout`, which is one second when left unset, even if the client timeout is longer. It panics if `c` is `nil`. ```go title="Signature" func NewWithLBClient(c *fasthttp.LBClient) *Client ``` See [Reusing fasthttp transports](./examples.md#reusing-fasthttp-transports) for examples, and [Underlying Transport](#underlying-transport) for the methods that give access to the wrapped client. ### REST Methods These helpers mirror axios-style method names and send HTTP requests using the configured client. Each helper acquires its own `Request`, which `Response.Close` releases together with the response. When a helper returns an error there is no `Response` to close: the helper has already released its `Request`. #### Get Sends a GET request. ```go title="Signature" func (c *Client) Get(url string, cfg ...Config) (*Response, error) ``` #### Post Sends a POST request. ```go title="Signature" func (c *Client) Post(url string, cfg ...Config) (*Response, error) ``` #### Put Sends a PUT request. ```go title="Signature" func (c *Client) Put(url string, cfg ...Config) (*Response, error) ``` #### Patch Sends a PATCH request. ```go title="Signature" func (c *Client) Patch(url string, cfg ...Config) (*Response, error) ``` #### Query Sends a QUERY request. ```go title="Signature" func (c *Client) Query(url string, cfg ...Config) (*Response, error) ``` #### Delete Sends a DELETE request. ```go title="Signature" func (c *Client) Delete(url string, cfg ...Config) (*Response, error) ``` #### Head Sends a HEAD request. ```go title="Signature" func (c *Client) Head(url string, cfg ...Config) (*Response, error) ``` #### Options Sends an OPTIONS request. ```go title="Signature" func (c *Client) Options(url string, cfg ...Config) (*Response, error) ``` #### Custom Sends a request with any HTTP method. ```go title="Signature" func (c *Client) Custom(url, method string, cfg ...Config) (*Response, error) ``` ### Request Configuration The `Config` type holds per-request parameters. JSON is used to serialize the body by default. If multiple body sources are set, precedence is: 1. Body 2. FormData 3. File ```go type Config struct { Ctx context.Context UserAgent string Referer string Header map[string]string Param map[string]string Cookie map[string]string PathParam map[string]string Timeout time.Duration MaxRedirects int DisablePathNormalizing bool Body any FormData map[string]string File []*File } ``` The REST helpers accept an optional `Config` and use only the first one passed. A `string` or `[]byte` `Body` is sent as is; any other value is encoded as JSON. `MaxRedirects` is honored for `GET`, `HEAD` and `QUERY` requests only; other methods receive the redirect response as-is. The default is `0`, which follows no redirects. See [SetMaxRedirects](./request.md#setmaxredirects) for how redirects are followed. `DisablePathNormalizing: true` turns path normalizing off for the request, like [SetDisablePathNormalizing](./request.md#setdisablepathnormalizing) on the request, which also says when the path is really sent as written; `false` leaves the client's setting in place. ### R **R** gets a `Request` object from the pool. Call `ReleaseRequest` when finished. ```go title="Signature" func (c *Client) R() *Request ``` ### Hooks Hooks allow you to add custom logic before a request is sent or after a response is received. #### RequestHook **RequestHook** returns user-defined request hooks. ```go title="Signature" func (c *Client) RequestHook() []RequestHook ``` #### ResponseHook **ResponseHook** returns user-defined response hooks. ```go title="Signature" func (c *Client) ResponseHook() []ResponseHook ``` #### AddRequestHook Adds one or more user-defined request hooks. ```go title="Signature" func (c *Client) AddRequestHook(h ...RequestHook) *Client ``` #### FinalRequestHook Returns final request hooks. These run after Fiber has serialized the URL, headers, and body, immediately before the transport sends the request. ```go title="Signature" func (c *Client) FinalRequestHook() []RequestHook ``` #### AddFinalRequestHook Adds final request hooks. Use these when request signing or authentication must inspect the serialized request body together with the merged headers. See [Final Request Hooks](./hooks.md#final-request-hooks) for what is already set at that point and what fasthttp adds afterward. ```go title="Signature" func (c *Client) AddFinalRequestHook(h ...RequestHook) *Client ``` #### AddResponseHook Adds one or more user-defined response hooks. ```go title="Signature" func (c *Client) AddResponseHook(h ...ResponseHook) *Client ``` ### JSON #### JSONMarshal Returns the JSON marshaler function used by the client. ```go title="Signature" func (c *Client) JSONMarshal() utils.JSONMarshal ``` #### JSONUnmarshal Returns the JSON unmarshaler function used by the client. ```go title="Signature" func (c *Client) JSONUnmarshal() utils.JSONUnmarshal ``` #### SetJSONMarshal Sets a custom JSON marshaler. ```go title="Signature" func (c *Client) SetJSONMarshal(f utils.JSONMarshal) *Client ``` #### SetJSONUnmarshal Sets a custom JSON unmarshaler. ```go title="Signature" func (c *Client) SetJSONUnmarshal(f utils.JSONUnmarshal) *Client ``` ### XML #### XMLMarshal Returns the XML marshaler function used by the client. ```go title="Signature" func (c *Client) XMLMarshal() utils.XMLMarshal ``` #### XMLUnmarshal Returns the XML unmarshaler function used by the client. ```go title="Signature" func (c *Client) XMLUnmarshal() utils.XMLUnmarshal ``` #### SetXMLMarshal Sets a custom XML marshaler. ```go title="Signature" func (c *Client) SetXMLMarshal(f utils.XMLMarshal) *Client ``` #### SetXMLUnmarshal Sets a custom XML unmarshaler. ```go title="Signature" func (c *Client) SetXMLUnmarshal(f utils.XMLUnmarshal) *Client ``` ### CBOR #### CBORMarshal Returns the CBOR marshaler function used by the client. ```go title="Signature" func (c *Client) CBORMarshal() utils.CBORMarshal ``` #### CBORUnmarshal Returns the CBOR unmarshaler function used by the client. ```go title="Signature" func (c *Client) CBORUnmarshal() utils.CBORUnmarshal ``` #### SetCBORMarshal Sets a custom CBOR marshaler. ```go title="Signature" func (c *Client) SetCBORMarshal(f utils.CBORMarshal) *Client ``` #### SetCBORUnmarshal Sets a custom CBOR unmarshaler. ```go title="Signature" func (c *Client) SetCBORUnmarshal(f utils.CBORUnmarshal) *Client ``` ### TLS :::note Set the TLS configuration before the first request. fasthttp copies it for a host the first time the client connects there, so a later `SetTLSConfig` or `SetCertificates` call does not reach a host the client already uses. On a client built with `New` or `NewWithClient` the copy lives as long as fasthttp keeps its entry for that host. The entry goes at the first pass of fasthttp's cleaner after the host has no open connection and no request waiting for one, and the passes come every second, or as far apart as `MaxIdleConnDuration`, up to 10 seconds, when that is longer. A failed first handshake leaves no open connection either, so a retry within that second still runs with the old settings. A client built with `NewWithHostClient` or `NewWithLBClient` keeps the copy for as long as the host client lives. Certificates that [SetRootCertificate](#setrootcertificate) or [SetRootCertificateFromString](#setrootcertificatefromstring) add to a `RootCAs` pool that already existed when the host was first used are the exception: the copy shares the pool, so they reach the host at once. The first call on a client without a pool creates the pool, and a copy made before that does not see it. ::: #### TLSConfig Returns the client's TLS configuration. If none is set, it initializes a new configuration with `MinVersion` defaulting to TLS 1.2. ```go title="Signature" func (c *Client) TLSConfig() *tls.Config ``` #### SetTLSConfig Sets the TLS configuration for the client. ```go title="Signature" func (c *Client) SetTLSConfig(config *tls.Config) *Client ``` #### SetCertificates Adds client certificates to the TLS configuration. ```go title="Signature" func (c *Client) SetCertificates(certs ...tls.Certificate) *Client ``` #### SetRootCertificate Adds one or more root certificates from a PEM file to the `RootCAs` pool of the client's TLS configuration. A new client has no pool (`RootCAs` is `nil`, which means the system roots), so the first call creates one that holds only the given certificates: they replace the system roots instead of being added to them, and later calls add to that pool. If the configuration already has a `RootCAs` pool, for example one built from `x509.SystemCertPool()` and set with [SetTLSConfig](#settlsconfig), the certificates are appended to that pool, and they reach a host the client already uses; the call that creates the pool does not, see the note under [TLS](#tls). To keep trusting the system roots, build the pool that way, as in the [TLS example](./examples.md#tls). It panics if the file cannot be read or holds no valid PEM certificate. ```go title="Signature" func (c *Client) SetRootCertificate(path string) *Client ``` #### SetRootCertificateFromString Adds one or more root certificates from a PEM string to the `RootCAs` pool of the client's TLS configuration, with the same rules as [SetRootCertificate](#setrootcertificate): the first call on a client without a pool replaces the system roots. It panics if the string holds no valid PEM certificate. ```go title="Signature" func (c *Client) SetRootCertificateFromString(pem string) *Client ``` ### SetProxyURL Sets a proxy URL for the client. All subsequent requests will use this proxy. It returns an error when the URL has a host and its scheme is not `http`, `socks5` or `socks5h`, for example `ftp://proxy:1080`. Other malformed URLs are not rejected here. If `net/url` cannot parse the URL, even with `http://` put in front of it, for example because of a trailing newline, a leading space, `%zz` in the port, or a non-numeric port on an address without a scheme such as `127.0.0.1:abc`, the URL is dropped without an error and requests go to their origin directly, without a proxy. An empty string is dropped the same way, and a dropped URL also replaces a proxy set earlier. A URL that parses, or that parses once `http://` is put in front of it, but names no usable address makes each request fail when it dials. That covers `http://127.0.0.1:abc` with its scheme, `http://` or `ftp://` with nothing after it, and a trailing `/` or a path. The proxy is installed as the client's dial function, so a later [SetDial](#setdial) replaces it and the other way around. Call it before the first request: on a client built with `New` or `NewWithClient`, a host the client already uses keeps its previous dial function until fasthttp drops its entry for that host, see the note under [TLS](#tls) for when. A client built with `NewWithHostClient` or `NewWithLBClient` uses the new function for every new connection. ```go title="Signature" func (c *Client) SetProxyURL(proxyURL string) error ``` ### Response Streaming #### StreamResponseBody Returns whether response body streaming is enabled. When enabled, the response body is not fully loaded into memory and can be read as a stream using `Response.BodyStream()`. This is useful for handling large responses or server-sent events (SSE). ```go title="Signature" func (c *Client) StreamResponseBody() bool ``` #### SetStreamResponseBody Enables or disables response body streaming. When enabled, responses can be consumed incrementally without loading the entire body into memory. ```go title="Signature" func (c *Client) SetStreamResponseBody(enable bool) *Client ``` **Example:** ```go title="Example" cc := client.New() cc.SetStreamResponseBody(true) resp, err := cc.Get("https://example.com/large-file") if err != nil { panic(err) } defer resp.Close() // Check if response is streaming if resp.IsStreaming() { // Read body incrementally reader := resp.BodyStream() buf := make([]byte, 4096) for { n, err := reader.Read(buf) if n > 0 { // Process chunk... } if err == io.EOF { break } if err != nil { panic(err) } } } else { // Regular body access body := resp.Body() fmt.Println(string(body)) } ``` **Server-Sent Events Example:** ```go title="SSE Example" cc := client.New() cc.SetStreamResponseBody(true) resp, err := cc.Get("https://example.com/events") if err != nil { panic(err) } defer resp.Close() reader := bufio.NewReader(resp.BodyStream()) for { line, err := reader.ReadString('\n') if err == io.EOF { break } if err != nil { panic(err) } fmt.Print(line) // Process SSE event } ``` ### RetryConfig Returns a copy of the retry configuration of the client, or `nil` when none is set. ```go title="Signature" func (c *Client) RetryConfig() *RetryConfig ``` ### SetRetryConfig Sets the retry configuration for the client. `RetryConfig` is an alias of `retry.Config` from the [Retry addon](../addon/retry.md), which describes its fields and defaults. Without a retry configuration, which is the default, Fiber does not retry failed requests. With one, a request that fails to send is retried with exponential backoff; an HTTP error status such as `500` is a response rather than a failure, so it is not retried. ```go title="Signature" func (c *Client) SetRetryConfig(config *RetryConfig) *Client ``` ### BaseURL #### BaseURL **BaseURL** returns the base URL currently set in the client. ```go title="Signature" func (c *Client) BaseURL() string ``` #### SetBaseURL Sets a base URL prefix for all requests made by the client. A request URL that already carries an `http://` or `https://` scheme, in any letter case, is used as-is; any other URL is appended to the base URL. If the result still has no `http://` or `https://` scheme, the request fails with `ErrURLFormat`. ```go title="Signature" func (c *Client) SetBaseURL(url string) *Client ``` **Example:** ```go title="Example" cc := client.New() cc.SetBaseURL("https://httpbin.org/") resp, err := cc.Get("/get") if err != nil { panic(err) } fmt.Println(string(resp.Body())) ```
Click here to see the result ```json { "args": {}, ... } ```
### Headers Client-level headers are applied to every request when it is sent. A header set on the request itself with the same key replaces the client's values rather than being added to them, and each send starts from those keys cleared, so re-sending a request does not accumulate values. #### Header Retrieves all values of a header key at the client level. The returned values apply to all requests. ```go title="Signature" func (c *Client) Header(key string) []string ``` #### AddHeader Adds a single header to all requests initiated by this client. ```go title="Signature" func (c *Client) AddHeader(key, val string) *Client ``` #### SetHeader Sets a single header, overriding any existing headers with the same key. ```go title="Signature" func (c *Client) SetHeader(key, val string) *Client ``` #### AddHeaders Adds multiple headers at once, all applying to all future requests from this client. ```go title="Signature" func (c *Client) AddHeaders(h map[string][]string) *Client ``` #### SetHeaders Sets multiple headers at once, overriding previously set headers. ```go title="Signature" func (c *Client) SetHeaders(h map[string]string) *Client ``` ### Query Parameters Query parameters set on the client are added to the ones on each request and to those already in the URL. A request does not replace a client parameter of the same name, so the server receives every value. #### Param Returns the values for a given query parameter key. ```go title="Signature" func (c *Client) Param(key string) []string ``` #### AddParam Adds a single query parameter for all requests. ```go title="Signature" func (c *Client) AddParam(key, val string) *Client ``` #### SetParam Sets a single query parameter, overriding previously set values. ```go title="Signature" func (c *Client) SetParam(key, val string) *Client ``` #### AddParams Adds multiple query parameters from a map of string slices. ```go title="Signature" func (c *Client) AddParams(m map[string][]string) *Client ``` #### SetParams Sets multiple query parameters from a map, overriding previously set values. ```go title="Signature" func (c *Client) SetParams(m map[string]string) *Client ``` #### SetParamsWithStruct Sets multiple query parameters from a struct. Each field is named by its `param` struct tag, or by the field name when it has none. Nested structs are not currently supported. See [SetValWithStruct](./request.md#setvalwithstruct) for the supported field types. ```go title="Signature" func (c *Client) SetParamsWithStruct(v any) *Client ``` #### DelParams Deletes one or more query parameters. ```go title="Signature" func (c *Client) DelParams(key ...string) *Client ``` ### UserAgent & Referer Requests are sent with `User-Agent: fiber` unless a user agent is set on the client or on the request, and the request's value wins. They are sent without a `Referer` header unless a referer is set the same way. Use these methods, or `Config.UserAgent` and `Config.Referer`, for these two headers: a `User-Agent` or `Referer` set through `SetHeader`, `AddHeader` or `Config.Header` is replaced when the request is sent, and a `Referer` is removed when no referer is set. #### SetUserAgent Sets the user agent header for all requests. ```go title="Signature" func (c *Client) SetUserAgent(ua string) *Client ``` #### SetReferer Sets the referer header for all requests. ```go title="Signature" func (c *Client) SetReferer(r string) *Client ``` ### Path Parameters Path parameters fill the `:name` placeholders in a request URL, including one in the base URL, as in `/users/:id`. A path parameter set on a request overrides a client-level one with the same name. See [SetPathParam](./request.md#setpathparam) for how placeholders are matched and which values are accepted. #### PathParam Returns the value of a named path parameter, if set. ```go title="Signature" func (c *Client) PathParam(key string) string ``` #### SetPathParam Sets a single path parameter. ```go title="Signature" func (c *Client) SetPathParam(key, val string) *Client ``` #### SetPathParams Sets multiple path parameters at once. ```go title="Signature" func (c *Client) SetPathParams(m map[string]string) *Client ``` #### SetPathParamsWithStruct Sets multiple path parameters from a struct. Each field is named by its `path` struct tag, or by the field name when it has none. ```go title="Signature" func (c *Client) SetPathParamsWithStruct(v any) *Client ``` #### DelPathParams Deletes one or more path parameters. ```go title="Signature" func (c *Client) DelPathParams(key ...string) *Client ``` ### Path Normalizing #### DisablePathNormalizing Reports whether path normalizing was disabled with [SetDisablePathNormalizing](#setdisablepathnormalizing). It does not read the setting of a fasthttp client that was created with `DisablePathNormalizing: true`. ```go title="Signature" func (c *Client) DisablePathNormalizing() bool ``` #### SetDisablePathNormalizing Turns path normalizing off or on for every request of the client. It is on by default: the request path is normalized before it is sent, which decodes percent-escapes, collapses `//` and resolves `.` and `..` segments, so `/a//b/./c%2Fd` goes out as `/a/b/c/d`. With normalizing off, the path is sent as written. fasthttp normalizes the path when it writes the request, according to the client that sends it, so the setting is applied to the `DisablePathNormalizing` field of the wrapped fasthttp client, and of every host client behind a load balancer: a client created with [NewWithClient](#newwithclient), [NewWithHostClient](#newwithhostclient) or [NewWithLBClient](#newwithlbclient) changes the fasthttp client it wraps, for everyone who shares it. Call it before the first request, because a `fasthttp.Client` copies the setting into each host client when it first creates it, and a host it already serves keeps normalizing until fasthttp drops its entry, see the note under [TLS](#tls) for when. Fiber keeps rejecting path parameter values that would leave their segment unless it knows that the path is sent as built. It knows for a client from `New`, until a request has gone out through it while it normalized; for a client from `NewWithHostClient`, which reads the field on every request; for a client from `NewWithLBClient` whose host clients all have the field on; and for a `fasthttp.Client` given to `NewWithClient` that was created with `DisablePathNormalizing: true`. A `fasthttp.Client` given to `NewWithClient` without it may have served hosts already, so Fiber cannot tell and keeps the check on, although hosts it has not served yet get the setting. A balancer that holds a client that is neither a `*fasthttp.HostClient` nor a wrapper exposing a nested balancer through `LBClient()` cannot be told either and counts as normalizing. A request skips Fiber's normalizing when either the client or the request disables it; see [SetDisablePathNormalizing](./request.md#setdisablepathnormalizing) for the request-level setting. ```go title="Signature" func (c *Client) SetDisablePathNormalizing(disable bool) *Client ``` ### Cookies A cookie set on a request replaces a client cookie with the same name, and a client cookie replaces one from the [cookie jar](#cookie-jar). #### Cookie Returns the value of a named cookie if set at the client level. ```go title="Signature" func (c *Client) Cookie(key string) string ``` #### SetCookie Sets a single cookie for all requests. ```go title="Signature" func (c *Client) SetCookie(key, val string) *Client ``` **Example:** ```go title="Example" cc := client.New() cc.SetCookie("john", "doe") resp, err := cc.Get("https://httpbin.org/cookies") if err != nil { panic(err) } fmt.Println(string(resp.Body())) ```
Click here to see the result ```json { "cookies": { "john": "doe" } } ```
#### SetCookies Sets multiple cookies at once. ```go title="Signature" func (c *Client) SetCookies(m map[string]string) *Client ``` #### SetCookiesWithStruct Sets multiple cookies from a struct. Each field is named by its `cookie` struct tag, or by the field name when it has none. ```go title="Signature" func (c *Client) SetCookiesWithStruct(v any) *Client ``` #### DelCookies Deletes one or more cookies. ```go title="Signature" func (c *Client) DelCookies(key ...string) *Client ``` ### Timeout #### SetTimeout Sets a default timeout for all requests, which can be overridden per request. A new client has no timeout. When the timeout elapses, or the request's context is canceled, the request fails with `ErrTimeoutOrCancel`. ```go title="Signature" func (c *Client) SetTimeout(t time.Duration) *Client ``` ### Debugging #### Debug Enables debug-level logging output. With debug enabled, the raw request and response are logged through the client's logger after each response, as they are: the `Authorization` and `Cookie` headers and the bodies are in the log, so keep debug logging off wherever people who should not see them can read it. When either body is a stream, only its headers are logged so the stream stays readable for the caller. A new client logs through Fiber's default logger, the one the `log` package writes to. Nothing is logged when the logger was set to `nil`. Use [SetLogger](#setlogger) to choose the logger. ```go title="Signature" func (c *Client) Debug() *Client ``` #### DisableDebug Disables debug-level logging output. ```go title="Signature" func (c *Client) DisableDebug() *Client ``` ### Cookie Jar #### SetCookieJar Assigns a cookie jar to the client to store and manage cookies across requests. A cookie received with a `Max-Age` attribute expires that many seconds after it was received, taking precedence over `Expires`; a `Max-Age` of zero or less expires it immediately (RFC 6265). A `Max-Age` that is not an integer is ignored on its own, as is any other attribute the parser cannot read, such as an `Expires` in an unrecognized format (RFC 6265 Section 5.2): the cookie keeps its name, its value and every other attribute, `Path`, `Domain` and `Secure` included. When the attribute repeats, the last integer value counts. A `Set-Cookie` whose name and value cannot be parsed is dropped. Cookies for the same host and path are stored under the origin that answered, so a redirect cannot plant cookies for another host. See [Cookie jar](./examples.md#cookie-jar) for how the jar scopes, orders and limits cookies. ```go title="Signature" func (c *Client) SetCookieJar(cookieJar *CookieJar) *Client ``` A `CookieJar` is safe for concurrent use, except for `Release`, which must not run at the same time as any other method. #### AcquireCookieJar **AcquireCookieJar** returns an empty `CookieJar` from the pool. ```go title="Signature" func AcquireCookieJar() *CookieJar ``` #### ReleaseCookieJar **ReleaseCookieJar** empties the jar and returns it to the pool. Do not use the jar afterward. ```go title="Signature" func ReleaseCookieJar(c *CookieJar) ``` #### CookieJar.Get **Get** returns the cookies that apply to a URI: the host and path match, `Secure` cookies are returned only for an `https` URI, and expired cookies are dropped. It returns `nil` when no cookie applies or the URI is `nil`. The jar keeps its own copies, so the returned cookies may be released with `fasthttp.ReleaseCookie`. ```go title="Signature" func (cj *CookieJar) Get(uri *fasthttp.URI) []*fasthttp.Cookie ``` #### CookieJar.Set **Set** stores copies of the cookies for the host of a URI, ignoring the port. A cookie without a usable `Path` is scoped to the default path of the URI (RFC 6265), so cookies set against `/a/b` apply to `/a` and below; set `Path` on the cookie to widen it. A cookie whose `Domain` the host may not set, such as an unrelated domain or a public suffix, is skipped. ```go title="Signature" func (cj *CookieJar) Set(uri *fasthttp.URI, cookies ...*fasthttp.Cookie) ``` #### CookieJar.SetByHost **SetByHost** stores copies of the cookies for a host. There is no request path to derive a default from, so a cookie without a usable `Path` is scoped to `/`. ```go title="Signature" func (cj *CookieJar) SetByHost(host []byte, cookies ...*fasthttp.Cookie) ``` #### CookieJar.SetKeyValue **SetKeyValue** stores a cookie with the given key and value for a host, scoped to `/`. ```go title="Signature" func (cj *CookieJar) SetKeyValue(host, key, value string) ``` #### CookieJar.SetKeyValueBytes **SetKeyValueBytes** is like `SetKeyValue` but takes the key and value as byte slices. ```go title="Signature" func (cj *CookieJar) SetKeyValueBytes(host string, key, value []byte) ``` #### CookieJar.Release **Release** removes every stored cookie. The jar must not be used afterward, and `ReleaseCookieJar` calls it for you. ```go title="Signature" func (cj *CookieJar) Release() ``` ### Dial & Logger #### SetDial Sets a custom dial function. Call it before the first request: [SetProxyURL](#setproxyurl) says when a new one reaches a host the client already uses. ```go title="Signature" func (c *Client) SetDial(dial fasthttp.DialFunc) *Client ``` #### SetLogger Sets the logger instance used by the client. A new client starts with Fiber's default logger. `nil` turns client logging off; [SetRootCertificate](#setrootcertificate) and [SetRootCertificateFromString](#setrootcertificatefromstring) still panic on invalid input, only without a log line. ```go title="Signature" func (c *Client) SetLogger(logger log.CommonLogger) *Client ``` #### Logger Returns the current logger instance. ```go title="Signature" func (c *Client) Logger() log.CommonLogger ``` ### Underlying Transport A client wraps one fasthttp client: the `fasthttp.Client` that `New` creates, or the one passed to `NewWithClient`, `NewWithHostClient` or `NewWithLBClient`. These methods give access to it. #### FasthttpClient Returns the underlying `*fasthttp.Client`, or `nil` if the client was created from a `HostClient` or an `LBClient`. ```go title="Signature" func (c *Client) FasthttpClient() *fasthttp.Client ``` #### HostClient Returns the underlying `*fasthttp.HostClient`, or `nil` if the client was not created with `NewWithHostClient`. ```go title="Signature" func (c *Client) HostClient() *fasthttp.HostClient ``` #### LBClient Returns the underlying `*fasthttp.LBClient`, or `nil` if the client was not created with `NewWithLBClient`. ```go title="Signature" func (c *Client) LBClient() *fasthttp.LBClient ``` #### Do Sends a raw `fasthttp` request through the underlying client, as the `Do` method of the wrapped `fasthttp.Client`, `HostClient` or `LBClient` does. It does not follow redirects. `Do`, `DoTimeout`, `DoDeadline` and `DoRedirects` work directly on `fasthttp` requests and responses and bypass Fiber's request pipeline: the base URL, default headers, query and path parameters, cookies, cookie jar, hooks, client timeout and retry configuration are not applied. Settings of the transport itself, such as the TLS configuration, the dial function or proxy, and response streaming, are. ```go title="Signature" func (c *Client) Do(req *fasthttp.Request, resp *fasthttp.Response) error ``` #### DoTimeout Like `Do`, but fails with `fasthttp.ErrTimeout` if no response arrives within `timeout`. ```go title="Signature" func (c *Client) DoTimeout(req *fasthttp.Request, resp *fasthttp.Response, timeout time.Duration) error ``` #### DoDeadline Like `Do`, but fails with `fasthttp.ErrTimeout` if no response arrives before `deadline`. ```go title="Signature" func (c *Client) DoDeadline(req *fasthttp.Request, resp *fasthttp.Response, deadline time.Time) error ``` #### DoRedirects Like `Do`, but follows up to `maxRedirects` redirects, on every kind of client, with the rules described under [SetMaxRedirects](./request.md#setmaxredirects). A chain of more than `maxRedirects` redirects fails with `fasthttp.ErrTooManyRedirects`, so `0` fails on the first redirect, and a negative value allows 16. Unlike the `Request` methods, it follows redirects for any request method. ```go title="Signature" func (c *Client) DoRedirects(req *fasthttp.Request, resp *fasthttp.Response, maxRedirects int) error ``` #### CloseIdleConnections Closes the idle keep-alive connections of the underlying client, or of every host client behind a load balancer. Connections that are in use are not interrupted. ```go title="Signature" func (c *Client) CloseIdleConnections() ``` ### Reset #### Reset Resets the client's configuration: it clears the base URL, timeout, user agent, referer, retry configuration, debug flag, path-normalizing setting, headers, query and path parameters and cookies, and it empties and detaches the cookie jar. It also replaces the transport with a new default `fasthttp.Client`, which drops a `HostClient` or `LBClient` the client wrapped together with any custom dial function, proxy, TLS configuration and response streaming setting. Request and response hooks, the JSON, XML and CBOR marshal and unmarshal functions and the logger are kept. ```go title="Signature" func (c *Client) Reset() ``` ### Default Client Fiber provides a default client (created with `New()`). You can configure it or replace it as needed. #### C **C** returns the default client. ```go title="Signature" func C() *Client ``` #### Get Get is a convenience method that sends a GET request using the `defaultClient`. ```go title="Signature" func Get(url string, cfg ...Config) (*Response, error) ``` #### Post Post is a convenience method that sends a POST request using the `defaultClient`. ```go title="Signature" func Post(url string, cfg ...Config) (*Response, error) ``` #### Put Put is a convenience method that sends a PUT request using the `defaultClient`. ```go title="Signature" func Put(url string, cfg ...Config) (*Response, error) ``` #### Patch Patch is a convenience method that sends a PATCH request using the `defaultClient`. ```go title="Signature" func Patch(url string, cfg ...Config) (*Response, error) ``` #### Query Query is a convenience method that sends a QUERY request using the `defaultClient`. ```go title="Signature" func Query(url string, cfg ...Config) (*Response, error) ``` #### Delete Delete is a convenience method that sends a DELETE request using the `defaultClient`. ```go title="Signature" func Delete(url string, cfg ...Config) (*Response, error) ``` #### Head Head sends a HEAD request using the `defaultClient`, a convenience method. ```go title="Signature" func Head(url string, cfg ...Config) (*Response, error) ``` #### Options Options is a convenience method that sends an OPTIONS request using the `defaultClient`. ```go title="Signature" func Options(url string, cfg ...Config) (*Response, error) ``` #### Replace **Replace** replaces the default client with a new one. It returns a function that can restore the old client. :::caution Replacing the default client is concurrency-safe, but mutating the same `Client` instance still requires external synchronization. ::: ```go title="Signature" func Replace(c *Client) func() ``` --- ## 🗺️ Ecosystem Fiber is a family of repositories around one core module. The map below shows what lives where and how the pieces plug into each other; select any block for details and the code that wires it up: --- ## 📊 Benchmarks ### Fiber v2 vs v3 The [benchmarks repository](https://github.com/gofiber/benchmarks) compares Fiber v2 and v3 on the same request scenarios, each written against the version's own API. CI runs both versions side by side and publishes time, memory and allocations per request for every run on the [results page](https://gofiber.github.io/benchmarks/). ### TechEmpower [TechEmpower](https://www.techempower.com/benchmarks/#section=test&runid=1d5bfc8a-5c4a-4fb2-a792-ad967f1eb138) provides a performance comparison of many web application frameworks that execute fundamental tasks such as JSON serialization, database access, and server-side template rendering. Each framework runs under a realistic production configuration. Results are recorded on both cloud instances and physical hardware. The test implementations are community contributed and live in the [FrameworkBenchmarks repository](https://github.com/TechEmpower/FrameworkBenchmarks). * Fiber `v3.0.0` * 56 Cores Intel(R) Xeon(R) Gold 6330 CPU @ 2.00GHz (Three homogeneous ProLiant DL360 Gen10 Plus) * 64GB RAM * Enterprise SSD * Ubuntu * Mellanox Technologies MT28908 Family ConnectX-6 40Gbps Ethernet #### Plaintext The Plaintext test measures basic request routing and demonstrates the capacity of high-performance platforms. Requests are pipelined, and the tiny response body demands high throughput to saturate the benchmark's gigabit Ethernet. See [Plaintext requirements](https://github.com/TechEmpower/FrameworkBenchmarks/wiki/Project-Information-Framework-Tests-Overview#plaintext) **Fiber** - **11,987,976** responses per second with an average latency of **1.0** ms. **Express** - **1,204,969** responses per second with an average latency of **8.8** ms. ![](/img/v3/plaintext.png) ![Fiber vs Express](/img/v3/plaintext_express.png) #### Data Updates **Fiber** handled **29,984** responses per second with an average latency of **16.9** ms. **Express** handled **54,887** responses per second with an average latency of **9.2** ms. ![](/img/v3/data_updates.png) ![Fiber vs Express](/img/v3/data_updates_express.png) #### Multiple Queries **Fiber** handled **54,002** responses per second with an average latency of **9.4** ms. **Express** handled **85,011** responses per second with an average latency of **6.0** ms. ![](/img/v3/multiple_queries.png) ![Fiber vs Express](/img/v3/multiple_queries_express.png) #### Single Query **Fiber** handled **953,016** responses per second with an average latency of **0.6** ms. **Express** handled **441,543** responses per second with an average latency of **1.3** ms. ![](/img/v3/single_query.png) ![Fiber vs Express](/img/v3/single_query_express.png) #### JSON Serialization **Fiber** handled **2,363,294** responses per second with an average latency of **0.2** ms. **Express** handled **949,717** responses per second with an average latency of **0.5** ms. ![](/img/v3/json.png) ![Fiber vs Express](/img/v3/json_express.png) --- ## 🤔 FAQ ### How should I structure my application? There's no single answer; the ideal structure depends on your application's scale and team. Fiber makes no assumptions about project layout. Routes and other application logic can live in any files or directories. For inspiration, see: * [gofiber/boilerplate](https://github.com/gofiber/boilerplate) * [thomasvvugt/fiber-boilerplate](https://github.com/thomasvvugt/fiber-boilerplate) * [Youtube - Building a REST API using Gorm and Fiber](https://www.youtube.com/watch?v=Iq2qT0fRhAA) * [embedmode/fiberseed](https://github.com/embedmode/fiberseed) ### How do I handle custom 404 responses? A request that matches no route reaches your `ErrorHandler` as a `*fiber.Error` with status `404` (`fiber.ErrNotFound`), so a custom error handler can render the response. See [How do I set up an error handler?](#how-do-i-set-up-an-error-handler) below or read more at [Error Handling](../guide/error-handling.md#custom-error-handler). You can also add a middleware function at the very bottom of the stack \(below all other functions\) to handle a 404 response: ```go title="Example" app.Use(func(c fiber.Ctx) error { return c.Status(fiber.StatusNotFound).SendString("Sorry can't find that!") }) ``` ### How can I use live reload? [Air](https://github.com/air-verse/air) automatically restarts your Go application when source files change, speeding development. To use Air in a Fiber project, follow these steps: * Install Air by downloading the appropriate binary for your operating system from the GitHub release page or by building the tool from source. * Create a configuration file for Air in your project directory, such as `.air.toml` or `air.conf`. Here's a sample configuration file that works with Fiber: ```toml # .air.toml root = "." tmp_dir = "tmp" [build] cmd = "go build -o ./tmp/main ." bin = "./tmp/main" delay = 1000 # ms exclude_dir = ["assets", "tmp", "vendor"] include_ext = ["go", "tpl", "tmpl", "html"] exclude_regex = ["_test\\.go"] ``` * Start your Fiber application with Air by running the following command: ```sh air ``` As you edit source files, Air detects the changes and restarts the application. A complete example is available in the [Fiber Recipes repository](https://github.com/gofiber/recipes/tree/master/air) and shows how to configure Air for a Fiber project. ### How do I set up an error handler? To override the default error handler, provide a custom one in the [Config](../api/fiber.md#errorhandler) when creating a new [Fiber instance](../api/fiber.md#new). ```go title="Example" app := fiber.New(fiber.Config{ ErrorHandler: func(c fiber.Ctx, err error) error { return c.Status(fiber.StatusInternalServerError).SendString(err.Error()) }, }) ``` We have a dedicated page explaining how error handling works in Fiber, see [Error Handling](../guide/error-handling.md). ### Which template engines does Fiber support? Fiber currently supports 9 template engines in our [gofiber/template](https://docs.gofiber.io/template/) middleware: * [ace](https://docs.gofiber.io/template/ace/) * [amber](https://docs.gofiber.io/template/amber/) * [django](https://docs.gofiber.io/template/django/) * [handlebars](https://docs.gofiber.io/template/handlebars/) * [html](https://docs.gofiber.io/template/html/) * [jet](https://docs.gofiber.io/template/jet/) * [mustache](https://docs.gofiber.io/template/mustache/) * [pug](https://docs.gofiber.io/template/pug/) * [slim](https://docs.gofiber.io/template/slim/) To learn more about using Templates in Fiber, see [Templates](../guide/templates.md). ### Does Fiber have a community chat? Yes, we have a [Discord](https://gofiber.io/discord) server with rooms for every topic. If you have questions or just want to chat, join us via this [invite link](https://gofiber.io/discord). ![](/img/support-discord.png) ### Does Fiber support subdomain routing? Yes. The simplest way is [`app.Domain`](../api/app.md#domain), which scopes routes to a hostname pattern. You can also dispatch by hostname yourself by keeping one app per host, as in this example:
Example ```go package main import ( "log" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/logger" ) type Host struct { Fiber *fiber.App } func main() { // Hosts hosts := map[string]*Host{} //----- // API //----- api := fiber.New() api.Use(logger.New(logger.Config{ Format: "[${ip}]:${port} ${status} - ${method} ${path}\n", })) hosts["api.localhost:3000"] = &Host{api} api.Get("/", func(c fiber.Ctx) error { return c.SendString("API") }) //------ // Blog //------ blog := fiber.New() blog.Use(logger.New(logger.Config{ Format: "[${ip}]:${port} ${status} - ${method} ${path}\n", })) hosts["blog.localhost:3000"] = &Host{blog} blog.Get("/", func(c fiber.Ctx) error { return c.SendString("Blog") }) //--------- // Website //--------- site := fiber.New() site.Use(logger.New(logger.Config{ Format: "[${ip}]:${port} ${status} - ${method} ${path}\n", })) hosts["localhost:3000"] = &Host{site} site.Get("/", func(c fiber.Ctx) error { return c.SendString("Website") }) // Server app := fiber.New() app.Use(func(c fiber.Ctx) error { host := hosts[c.Host()] if host == nil { return c.SendStatus(fiber.StatusNotFound) } else { host.Fiber.Handler()(c.RequestCtx()) return nil } }) log.Fatal(app.Listen(":3000")) } ```
For more information, see issue [#750](https://github.com/gofiber/fiber/issues/750). ### How can I handle conversions between Fiber and net/http? Fiber can register common `net/http` handlers directly—just pass an `http.Handler`, `http.HandlerFunc`, compatible function, or even a native `fasthttp.RequestHandler` to your routing method. For other interoperability scenarios, the `adaptor` middleware provides utilities for converting between Fiber and `net/http`. It allows seamless integration of `net/http` handlers, middleware, and requests into Fiber applications, and vice versa. :::caution Performance trade-offs Converted `net/http` handlers run through a compatibility layer. They won't expose `fiber.Ctx` or Fiber-specific helpers, and the extra adaptation work makes them slower than native Fiber handlers. Use them when interoperability matters, but prefer Fiber handlers for maximum performance. ::: For details on how to: * Convert `net/http` handlers to Fiber handlers * Convert Fiber handlers to `net/http` handlers * Convert `fiber.Ctx` to `http.Request` See the dedicated documentation: [Adaptor Documentation](../middleware/adaptor.md). ### Troubleshooting common errors Crashes that surface in unrelated packages, with stack traces that never mention Fiber, usually trace back to a context value kept past the handler. The cases below name the symptom so a search for the panic text lands here. #### Panic `id (N) <= evictCount (M)` (hpack / HTTP/2 / gRPC) {#panic-id-evictcount-hpack-grpc} This is not a bug in Fiber, gRPC, or `golang.org/x/net/http2`. It means a Fiber zero-copy context value (a header from `c.Get`, or `c.Query`, `c.Params`, `c.Cookies`, `c.Body`) was kept past the handler. Fiber reuses the request buffer on the next request, so the retained bytes change underneath whatever stored them. See [Zero Allocation](../intro.md#zero-allocation) for the underlying behavior. A common trigger is forwarding a header into a long-lived gRPC / HTTP/2 client's `metadata`. The value is held in the connection's HPACK dynamic table and later mutates, crashing the process inside the HPACK encoder, in a stack trace that never mentions Fiber: ```text panic: id (N) <= evictCount (M) golang.org/x/net/http2/hpack.(*headerFieldTable).idToIndex ... google.golang.org/grpc/internal/transport.(*loopyWriter).writeHeader ``` The crash is decoupled from your handler in both time and call stack, so ordinary tests and even the `-race` detector usually miss it. The fix is to detach the value before you keep it: copy it with `utils.CopyString` (or `strings.Clone`), or enable [`Immutable`](../api/fiber.md#immutable). A full reproduction is in [gofiber/fiber#4464](https://github.com/gofiber/fiber/issues/4464). --- ## 🏗️ Internal Architecture ### Overall Architecture At the heart of Fiber is the **App** struct. It is responsible for configuring the server, managing a pool of Contexts (either our default implementation, **DefaultCtx**, or a user‑supplied **CustomCtx**), and holding the router stack with all registered routes and groups. In addition, the App contains mount fields to support sub‑applications and hooks that allow developers to run custom code at key stages (e.g. when registering routes or starting the server). ```mermaid flowchart TD A[App] B["Configuration (Config)"] C[Context Pool] D["DefaultCtx \/ CustomCtx"] E[Router Stack] F["Groups & Routes"] G["MountFields (Sub‑Apps)"] H[Hooks] A --> B A --> C C --> D A --> E E --> F A --> G A --> H ``` #### Explanation - App: The central object that bootstraps and runs the Fiber server. - Configuration (Config): Contains settings for body limits, timeouts, TLS options, routing behavior (e.g. case‑sensitivity, strict routing), and more. - Context Pool: A synchronized pool from which Contexts are acquired per request. This design minimizes allocations by recycling DefaultCtx (or CustomCtx) instances. - Router Stack: Organizes all registered routes. It is later processed into a tree structure for fast route‑matching. - MountFields: Support for mounting sub‑applications so that large APIs can be segmented into independent routers. - Hooks: Allow for custom behavior at critical points (e.g., on route registration, route naming, on listen, on shutdown, etc.). ### Request Processing Flow Fiber’s request processing is designed for performance and minimal overhead. When an HTTP request is received by the underlying fasthttp server, the flow is as follows: 1. Request Arrival: The fasthttp server receives the HTTP request. 2. Context Acquisition: The App calls AcquireCtx() to fetch a Context from the pool. 3. Context Reset: The acquired Context is reset (via DefaultCtx.Reset()) with the new request’s data. 4. Request Handling: The request handler (default or custom) is invoked. 5. Route Matching: The framework uses the next() (or nextCustom()) function to traverse the pre‑built route tree and find a matching route based on the URL and HTTP method. 6. Middleware Chain Execution: The matched route’s handler chain is executed in sequence. 7. Error Handling (if required): Any errors encountered trigger the registered error handler. 8. Response Generation: The response is sent back to the client. 9. Context Release: Finally, the Context is cleaned up and returned to the pool. ```mermaid flowchart LR R["HTTP Request (fasthttp)"] A["App.Handler()
(default or custom request handler)"] C["Acquire Context
(from Pool)"] X["Reset Context
(DefaultCtx.Reset())"] N["Route Matching
(next() \/ nextCustom())"] M["Handler Chain Execution"] EH["Error Handling
(if needed)"] S["HTTP Response"] RC["Release Context
(to Pool)"] R --> A A --> C C --> X X --> N N --> M M --> EH EH --> S S --> RC ``` #### Additional Note Fiber minimizes memory allocations by reusing Context objects and uses an optimized route‑matching algorithm to rapidly determine the correct handler chain. ### Routing & Path Parsing Fiber allows you to register routes using helper methods (e.g. Get(), Post()) or by creating groups and sub‑routers. Internally, the route pattern is parsed by the parseRoute() function. This function decomposes the route string into segments: - Constant Segments: Fixed parts of the path (e.g. /api). - Parameter Segments: Dynamic parts that begin with a colon. For example, a route may be defined as: /api/\:userId<int> Here, the segment \:userId<int> is a parameter segment with a type constraint (an integer). - Constraints: Constraints (such as int, bool, datetime, or even regular expressions) are extracted from the parameter part and stored in the route’s metadata for validation at runtime. ```mermaid flowchart TD P["Route Pattern String
(e.g., '/api/\\:userId\\<int>')"] PA["parseRoute()"] RP[routeParser] RS["routeSegment(s)"] C["Constraints
(e.g., int, datetime, regex)"] PARAM[Extracted Parameter Names] P --> PA PA --> RP RP --> RS RS --> C RP --> PARAM ``` #### Explanation - parseRoute(): Takes a route string and returns a routeParser struct that includes a list of routeSegment objects. - routeSegment: Represents a portion of the route. If it is a parameter segment, it may include constraints that determine the allowed format (for example, ensuring that a parameter is an integer). - Extracted Parameter Names: These are later used to populate the request’s Context with the actual values parsed from the URL. ### Route Matching and Parameter Extraction When a request is processed, Fiber uses its pre‑computed route tree (the treeStack) to efficiently match the incoming URL against registered routes. 1. Normalization: The request path is normalized as [RFC 3986 Section 6.2.2](https://www.rfc-editor.org/rfc/rfc3986#section-6.2.2) describes (percent-encoded unreserved characters are decoded and dot segments are removed). The “detection path” that routes are matched against is that path in lowercase unless `CaseSensitive` is set, with trailing slashes trimmed unless `StrictRouting` is set. 2. Tree Traversal: The route tree, grouped by common prefixes, is traversed based on the HTTP method. 3. Candidate Filtering: Before a route's segments are walked, cheap precomputed checks discard most candidates: the leading bytes of the path are compared against the route's first constant, the number of `/` bytes in the path is checked against the bounds the pattern allows, and for parametric routes the first constant segment that follows a parameter is compared at the `/` it has to start at. 4. Matching: Constant segments are compared exactly, while parameter segments extract dynamic values. 5. Constraint Validation: Extracted parameter values are validated against any defined constraints. ```mermaid flowchart TD A["Incoming Request URL
(e.g., '/api/john')"] B["Normalize Path
(RFC 3986, case fold, trim trailing slashes)"] C["Detection Path"] D["Traverse Route Tree
(treeStack based on method)"] D2["Filter Candidates
(leading bytes, slash count, probe constant)"] E["Match Constant Segments"] F["Identify Parameter Segments
(e.g., ':userId')"] G["Extract Parameter Values"] H["Validate Constraints
(e.g., 'int', 'datetime', 'regex')"] I["Route Found"] A --> B B --> C C --> D D --> D2 D2 --> E E --> F F --> G G --> H H --> I ``` #### Insight This efficient matching mechanism leverages pre‑grouped routes to minimize comparisons, while dynamic segments allow for flexible URL structures and runtime validation. ### Middleware Chain Execution Once a matching route is found, Fiber executes the chain of middleware and route handlers sequentially. The process is as follows: 1. Initial Handler Execution: The first handler of the matched route is invoked. 2. Calling Next(): Each handler calls Ctx.Next() to pass control to the next handler in the chain. 3. Termination: When no further handlers remain, the chain terminates and the response is sent. ```mermaid flowchart TD A[Matched Route] B[Handler 1] C[Handler 2] D[Handler 3] E[Response Generation] A --> B B -- "Calls C via Next()" --> C C -- "Calls D via Next()" --> D D -- "No Next() available" --> E ``` #### Explanation - Each handler in the chain can perform operations (e.g. authentication, logging, transformation) before calling Next() to forward control. - This sequential processing ensures that middleware are executed in the order they were registered. - If an error occurs or a handler does not call Next(), the chain may be terminated early, and an error handler may be invoked. #### Observations Middleware are executed in the order they are registered. This sequential design allows each handler to perform tasks such as authentication, logging, or transformation before delegating to the next handler. ### Sub-Application Mounting & Grouping Fiber allows mounting sub‑applications (or sub‑routers) under specific path prefixes. This enables modular design of large APIs. The mounting process works as follows: 1. Defining a Mount Point: A parent application (or group) calls `Use` with a sub-app, which triggers the internal mount path logic. 2. Merging Mount Fields: The sub‑app’s mount fields are updated with the prefix of the parent, and its routes are integrated into the parent’s routing structure. 3. Processing Sub‑App Routes: During startup, the parent app collects routes from mounted sub‑apps and builds a unified route tree. ```mermaid flowchart TD A[Parent App] B["Sub-App (Mounted)"] C["Define Mount Point
(e.g. \'/admin\')"] D["Update MountFields
(assign mount path)"] E["Merge Sub-App Routes
(append to Router Stack)"] F[Generate Unified Route Tree] A --> C C --> B B --> D D --> E E --> F ``` #### Impact This mechanism enables large APIs to be broken down into smaller, maintainable modules while still benefiting from Fiber’s optimized routing and request handling. ### Route Tree Building Fiber builds a route tree (the treeStack) to optimize route matching. This involves grouping routes based on a prefix (usually the first few characters) to reduce the number of comparisons during a request. 1. Iterating Over the Router Stack: Each registered route is examined. 2. Computing the Tree Key: A key is computed from the route’s normalized path (e.g. the first 3 characters). 3. Grouping Routes: Routes are added to the appropriate branch of the tree. 4. Ordering: Within each group, routes keep their registration order (routes without a prefix key are added to every group at their original position), so the first registered route that matches wins. ```mermaid flowchart TD A["Router Stack
(All Registered Routes)"] B["Compute Tree Key
(e.g. first 3 characters)"] C["Group Routes by Key
(treeStack)"] D["Merge Global Routes
(key \'\' for global matches)"] E[Keep Registration Order within Groups] F[Optimized Route Tree] A --> B B --> C C --> D D --> E E --> F ``` #### Explanation - Building a route tree is an optimization step that reduces the matching overhead by limiting the search space to a subset of routes that share a common prefix. - The tree is rebuilt whenever new routes are registered, ensuring that the latest routing configuration is always used for matching. ### Context Lifecycle Management Fiber minimizes allocations by pooling Context objects. The lifecycle of a Context is as follows: 1. **Acquisition:** When a new HTTP request arrives, a Context is retrieved from the pool via `App.AcquireCtx()`. 2. **Reset:** The acquired Context is reset with the current `fasthttp.RequestCtx` to clear previous data and initialize new request‑specific values. 3. **Processing:** The Context is passed along the middleware and handler chain. 4. **Release:** After processing the request (or when an error occurs), the Context is released back to the pool via `App.ReleaseCtx()`, making it available for reuse. ```mermaid flowchart TD A["HTTP Request
(fasthttp)"] B["Acquire Context
(App.AcquireCtx())"] C["Reset Context
(DefaultCtx.Reset())"] D["Process Request
(Handlers & Middleware)"] E["Error Handling
(if needed)"] F["Release Context
(App.ReleaseCtx())"] A --> B B --> C C --> D D --> E E --> F ``` #### Key Benefit Reusing Context objects significantly reduces garbage collection overhead, ensuring Fiber remains fast and memory‑efficient even under heavy load. ### Preforking Mechanism To take full advantage of multi‑core systems, Fiber offers a prefork mode. In this mode, the master process spawns several child processes that listen on the same port. On Linux, this is typically done with `SO_REUSEPORT`; on Windows, Fiber falls back to a `SO_REUSEADDR`-based behavior that does not provide identical kernel-level load-distribution semantics. ```mermaid flowchart LR M["Master Process (App)"] C[Child Processes] GOMAX["Set GOMAXPROCS(1)"] REQ[Handle HTTP Requests] WM["watchMaster()"] M -->|Spawns| C C --> GOMAX C -->|Processes| REQ C --> WM ``` #### Explanation - Master Process: The main process determines the number of available CPU cores and spawns that many child processes. - Child Processes: Each child sets GOMAXPROCS(1) to run on a single CPU core and listens on the shared port. - watchMaster(): Each child process runs a watchdog routine to monitor the master process; if the master exits (or its parent process ID becomes 1 on Unix‑like systems), the child terminates gracefully. #### Detailed Preforking Workflow Fiber’s prefork mode uses OS‑level mechanisms to allow multiple processes to listen on the same port. Here’s a more detailed look: 1. Master Process Spawning: The master process detects the number of CPU cores and spawns that many child processes. 2. Child Process Initialization: Each child process sets GOMAXPROCS(1) so that it runs on a single core. 3. Binding to Port: - Linux: child processes use `SO_REUSEPORT` (for example through reuseport helpers) so multiple workers can bind the same address/port with kernel-level distribution. - Windows: Fiber uses a `SO_REUSEADDR` fallback path; behavior is platform-dependent and should not be treated as equivalent to Linux `SO_REUSEPORT`. 4. Parent Monitoring: Each child runs a watchdog function (watchMaster()) to monitor the master process; if the master terminates, children exit. 5. Request Handling: Each child independently handles incoming HTTP requests. ```mermaid flowchart TD A[Master Process] B[Determine CPU Cores] C[Spawn Child Processes] D["Child Process Initialization
(GOMAXPROCS(1))"] E["Bind to Port
(reuseport)"] F["Run watchMaster()
(Monitor Parent)"] G[Handle HTTP Requests] A --> B B --> C C --> D D --> E E --> F F --> G ``` ##### Explanation - Preforking improves performance by allowing multiple processes to handle requests concurrently. - Linux `SO_REUSEPORT` and Windows fallback behavior are functionally different; do not assume identical security or scheduling characteristics across operating systems. - The watchdog routine in each child ensures that they exit if the master process is no longer running, maintaining process integrity. #### Security Considerations Prefork intentionally relaxes strict single-owner assumptions on a listening socket. In multi-tenant or shared-host environments, a local co-resident attacker with sufficient local privileges may attempt to bind to the same address/port and compete for or observe traffic, depending on OS behavior, account boundaries, and deployment configuration. - **Privilege and user-boundary assumptions:** Prefork is safest when all participating workers run under the same dedicated service identity and within an isolated trust boundary. - **Linux vs Windows behavior:** Linux `SO_REUSEPORT` provides explicit multi-listener semantics; Windows fallback behavior differs and should be evaluated carefully before enabling prefork in sensitive environments. - **Strict ownership requirement:** If you require strict single-owner port semantics, run Fiber **without** prefork. - **Hardening guidance:** Prefer a dedicated service user, strong container/VM isolation, and avoid shared host namespaces between unrelated workloads. ### Redirection & Flash Messages Fiber’s redirection mechanism is implemented via the Redirect struct. This structure allows not only setting a new location for redirection but also passing along flash messages and old input data via a special cookie. ```mermaid flowchart LR R[Redirect Struct] RP[redirectPool] FM["Flash Messages \/ Old Inputs"] M["Methods:
To(), Route(), Back()"] LH[Set Location Header] CK["Flash Cookie
(fiber\_flash)"] R -->|Acquired from| RP R --> FM R --> M M --> LH FM -->|Serialized| CK ``` #### Explanation - Redirect Struct: Retrieved from a pool (to minimize allocations), it stores redirection settings such as the HTTP status code (defaulting to 303 See Other) and any flash messages. - Flash Messages & Old Inputs: These are collected via methods like With() or WithInput() and then serialized and stored in a cookie named fiber_flash. - Redirection Methods: The To(), Route(), and Back() methods determine the target URL and set the Location header accordingly. #### Flash Message Handling in Redirection When performing redirections, Fiber can send flash messages or preserve old input data. This process involves: 1. Collecting Flash Data: When a redirect is initiated, developers can add flash messages via Redirect.With() or old input data via Redirect.WithInput(). 2. Serialization: The flash messages and input data are serialized (using a fast marshaling method) into a byte sequence. 3. Setting a Cookie: The serialized data is stored in a special cookie (named fiber_flash) that will be sent to the client. The cookie is set `HttpOnly`, so the payload stays out of `document.cookie`. It is `Secure` only when the request that produced it arrived over TLS: a redirect that starts on plain HTTP sets a cookie without that attribute, which the browser will send over HTTP as well. It is hex-encoded rather than encrypted or signed, so treat everything in it as visible to the client. 4. Retrieval & Clearing: On the subsequent request, the flash data is read from the cookie, deserialized, and then cleared. The cookie is expired on every read, including when the stored value fails to decode, so a malformed payload is not re-parsed on later requests. ```mermaid flowchart TD A[Initiate Redirect] B["Add Flash Messages
(With(), WithInput())"] C[Serialize Flash Data] D["Set Flash Cookie
(\'fiber\_flash\')"] E[Client Receives Redirect] F[Next Request Reads Flash Cookie] G["Deserialize & Clear Flash Data"] A --> B B --> C C --> D D --> E E --> F F --> G ``` ##### Explanation - Flash messages provide a way to pass transient data (such as notifications or error messages) to the next request after a redirect. - The data is stored temporarily in a cookie, which is then read and cleared upon processing the next request. - This mechanism is essential for implementing post‑redirect‑get patterns and ensuring a smooth user experience. ### Hooks, Error Handling & Context Lifecycle #### Hooks Fiber provides a comprehensive hook system that allows you to run custom functions at key moments: - OnRoute: Called when a route is registered. - OnName: Invoked when a route is assigned a name. - OnGroup: Triggered when a group is created. - OnGroupName: Invoked when a group is assigned a name. - OnListen: Runs when the server starts listening. - OnPreStartupMessage: Runs before the startup message is printed and can customize it. - OnPostStartupMessage: Runs after the startup message is printed or skipped. - OnPreShutdown: Runs before the application shuts down. - OnPostShutdown: Runs after shutdown and receives the shutdown result. - OnFork: Invoked when a child process is forked. - OnMount: Used when a sub‑application is mounted. ```mermaid flowchart TD H[Hooks] OR[OnRoute] ON[OnName] OG[OnGroup] OGN[OnGroupName] OL[OnListen] PreStartup[OnPreStartupMessage] PostStartup[OnPostStartupMessage] OPRS[OnPreShutdown] OPOS[OnPostShutdown] OF[OnFork] OM[OnMount] H --> OR H --> ON H --> OG H --> OGN H --> OL H --> PreStartup H --> PostStartup H --> OPRS H --> OPOS H --> OF H --> OM ``` ##### Explanation - Hooks provide extension points for developers and maintainers to inject custom logic without modifying the core Fiber code. - They are executed at various stages (for example, every time a new route is registered, the OnRoute hooks are executed to allow for logging, validation, or transformation of the route). #### Error Handling & Context Lifecycle Fiber’s DefaultCtx (or CustomCtx) represents the per‑request context. The lifecycle is as follows: - Acquire: A Context is obtained from the pool at the beginning of a request. - Processing: The context is passed along to the route handlers and middleware. - Error Handling: If an error occurs (e.g., route not found, method not allowed, or a panic that the [Recover](../middleware/recover.md) middleware turned into an error), Fiber calls the registered error handler. Errors such as ErrMethodNotAllowed or ErrNotFound are generated as needed. - Release: Once the request is processed, the Context is released back into the pool for reuse. ```mermaid flowchart LR AC["Acquire Context
(from Pool)"] HP["Handle Request
(Handlers & Middleware)"] EH["Error Handling
(if needed)"] RC["Release Context
(to Pool)"] AC --> HP HP --> EH EH --> RC ``` ##### Explanation - This lifecycle ensures that Fiber minimizes allocations by reusing Context objects. - Errors are propagated and handled consistently, and the context is properly reset after every request. --- ## 📚 Learning Resources ### Interactive Tools in These Docs The documentation itself ships hands-on tools, no setup required: - [Route Matcher](./route-matcher.md): fire requests at your own route table and see which route wins, why the others lose, and which parameters are extracted - [Build Your First App, Step by Step](../intro.md#build-your-first-app-step-by-step): grow one `main.go` from Hello World to a small JSON API with simulated requests - [Anatomy of a route](../guide/routing.md#anatomy-of-a-route) and the [middleware chain visualizer](../guide/routing.md#middleware): interactive breakdowns inside the routing guide ### Interactive Learning Platforms Looking to practice Fiber concepts through hands-on exercises? Here are some community-driven learning resources: #### Go Interview Practice - Fiber Challenges A comprehensive platform offering progressive Fiber challenges that complement the official documentation. ![Learning Path Overview](/img/learning-resources/fiber-learning-path.png) **What You'll Learn:** - **High-Performance APIs** - Build ultra-fast RESTful APIs with zero-allocation routing - **Middleware & Security** - Implement custom middleware, rate limiting, CORS, and authentication - **Request Validation** - Input validation, error handling, and data transformation - **Authentication & JWT** - Secure authentication systems with JWT tokens and API key validation ![Challenge Interface](/img/learning-resources/fiber-challenge-interface.png) **Challenge Roadmap:** 1. **Basic Routing** - Setup Fiber, routes, and handlers (Beginner) 2. **Middleware & CORS** - Custom middleware and rate limiting (Intermediate) 3. **Validation & Errors** - Input validation and error handling (Intermediate) 4. **Authentication** - JWT tokens and API key validation (Advanced) ![Fiber Framework Overview](/img/learning-resources/fiber-framework-overview.png) ![Interactive Learning Experience](/img/learning-resources/fiber-learning-experience.png) [Explore Fiber Challenges →](https://rezasi.github.io/go-interview-practice/fiber) | [GitHub Repository →](https://github.com/RezaSi/go-interview-practice) --- ## 🎯 Route Matcher Edit the route table or the request and watch which route wins, why the others lose, and which parameters are extracted. Routes are tried in registration order, exactly like in a real Fiber app: :::note The playground simulates Fiber's matcher with default settings (case-insensitive, non-strict routing). `datetime` and custom constraints are not simulated, and `regex()` runs on the JS engine instead of Go's RE2. ::: The [routing guide](../guide/routing.md) explains the full syntax: [parameters](../guide/routing.md#parameters), wildcards, literal separators, and [constraints](../guide/routing.md#constraints). --- ## 🐛 Advanced Format ### MsgPack Fiber lets you use MessagePack for efficient binary serialization. Use one of the popular Go libraries below to encode and decode data in handlers. - Fiber recognizes the `application/vnd.msgpack` content type when binding, but it bundles no MsgPack implementation. Until you set `MsgPackEncoder` and `MsgPackDecoder`, `Bind().MsgPack()`, `Ctx.MsgPack()` and `Ctx.AutoFormat()` for a MsgPack `Accept` header return an error. See the [Binding documentation](../api/bind.md#msgpack) for details. - Use `Bind().MsgPack()` to bind data to structs, similar to JSON. `Ctx.AutoFormat()` responds with MsgPack when the `Accept` header is `application/vnd.msgpack`. See the [AutoFormat documentation](../api/ctx.md#autoformat) for more. #### Recommended Libraries - [github.com/vmihailenco/msgpack](https://pkg.go.dev/github.com/vmihailenco/msgpack) — A widely used, feature-rich MsgPack library. - [github.com/shamaton/msgpack/v3](https://pkg.go.dev/github.com/shamaton/msgpack/v3) — High-performance MsgPack library. #### Installation Install either library using: ```bash go get github.com/vmihailenco/msgpack # or go get github.com/shamaton/msgpack/v3 ``` > **Note:** Fiber doesn't bundle a MsgPack implementation because it's outside the Go standard library. Pick one of the popular libraries in the ecosystem; the two below are widely used and well maintained. #### Example: Using `shamaton/msgpack/v3` ```go import ( "github.com/gofiber/fiber/v3" "github.com/shamaton/msgpack/v3" ) type User struct { Name string `msgpack:"name"` // tag may vary depending on your MsgPack library Age int `msgpack:"age"` } func main() { app := fiber.New(fiber.Config{ // Required for MsgPack: Fiber ships no default encoder/decoder MsgPackEncoder: msgpack.Marshal, MsgPackDecoder: msgpack.Unmarshal, }) app.Post("/msgpack", func(c fiber.Ctx) error { var user User if err := c.Bind().MsgPack(&user); err != nil { return err } // Content type will be set automatically to application/vnd.msgpack return c.MsgPack(user) }) app.Listen(":3000") } ``` ### CBOR Fiber doesn't ship with a CBOR implementation. Use a library such as [fxamacker/cbor](https://github.com/fxamacker/cbor) to add encoding and decoding. Until you set `CBOREncoder` and `CBORDecoder`, `Bind().CBOR()`, `Ctx.CBOR()` and `Ctx.AutoFormat()` for a CBOR `Accept` header return an error. - Use `Bind().CBOR()` to bind CBOR to structs. `Ctx.AutoFormat()` replies with CBOR when the `Accept` header is `application/cbor`. See the [AutoFormat documentation](../api/ctx.md#autoformat) for details. ```bash go get github.com/fxamacker/cbor/v2 ``` Configure Fiber with the chosen library: ```go import ( "github.com/gofiber/fiber/v3" "github.com/fxamacker/cbor/v2" ) func main() { app := fiber.New(fiber.Config{ CBOREncoder: cbor.Marshal, CBORDecoder: cbor.Unmarshal, }) type User struct { Name string `cbor:"name"` Age int `cbor:"age"` } app.Post("/cbor", func(c fiber.Ctx) error { var user User if err := c.Bind().CBOR(&user); err != nil { return err } // Content type will be set automatically to application/cbor return c.CBOR(user) }) app.Listen(":3000") } ``` --- ## 🧠 Go Context ### Fiber Context as `context.Context` Fiber's [`Ctx`](../api/ctx.md) implements Go's [`context.Context`](https://pkg.go.dev/context#Context) interface. You can pass `c` directly to functions that expect a `context.Context` without adapters, but only to read values from it. `Ctx` is a context that can never be canceled: `Deadline`, `Done`, and `Err` always report no deadline, `nil`, and `nil`, whatever you set with `c.SetContext`. For cancellation and deadlines, use `c.Context()`. :::caution The `fiber.Ctx` instance is only valid within the lifetime of the handler. It is reused for subsequent requests, so avoid storing `c` or using it in goroutines that outlive the handler. For asynchronous work, call `c.Context()` inside the handler to obtain a `context.Context` that can safely be used after the handler returns. By default, this returns `context.Background()` unless a custom context was provided with `c.SetContext`. The context outlives the handler, but a string you read from the request does not unless [`Immutable`](../api/fiber.md#immutable) is set: copy such a value inside the handler before you start the goroutine, as [Context Helpers](#context-helpers) shows. ::: ```go title="Example" func doSomething(ctx context.Context) { // ... your logic here } app.Get("/", func(c fiber.Ctx) error { doSomething(c) // c satisfies context.Context return nil }) ``` #### Using context outside the handler `fiber.Ctx` is recycled after each request. If you need a context that lives longer—for example, for work performed in a new goroutine—obtain it with `c.Context()` before returning from the handler. ```go title="Async work" app.Get("/job", func(c fiber.Ctx) error { ctx := c.Context() go performAsync(ctx) return c.SendStatus(fiber.StatusAccepted) }) ``` You can customize the base context by calling `c.SetContext` before requesting it. This changes what `c.Context()` returns for the rest of the request; it does not make `c` itself cancelable. ```go app.Get("/job", func(c fiber.Ctx) error { c.SetContext(context.WithValue(context.Background(), "requestID", "123")) ctx := c.Context() go performAsync(ctx) return nil }) ``` #### Retrieving Values `Ctx.Value` is backed by [Locals](../api/ctx.md#locals). Values stored with `c.Locals` are accessible through `Value` or standard `context.WithValue` helpers. ```go title="Locals and Value" app.Get("/", func(c fiber.Ctx) error { c.Locals("role", "admin") role := c.Value("role") // returns "admin" return c.SendString(role.(string)) }) ``` ### Working with `RequestCtx` The underlying [`fasthttp.RequestCtx`](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx) can be accessed via `c.RequestCtx()`. This exposes low-level APIs and a `context.Context` implementation of its own. ```go title="Accessing RequestCtx" app.Get("/raw", func(c fiber.Ctx) error { fctx := c.RequestCtx() // use fasthttp APIs directly fctx.Response.Header.Set("X-Engine", "fasthttp") return nil }) ``` `fasthttp.RequestCtx` satisfies the `context.Context` interface itself. `Deadline` always reports no deadline. `Done` closes only when the server shuts down, and `Err` then returns `context.Canceled`. Note that this is the `fasthttp.RequestCtx`, not Fiber's `Ctx`, whose `Done` is always `nil`. :::caution `Done` does not fire when an individual client disconnects. To stop a long-running handler at a deadline, wrap `c.Context()` with `context.WithTimeout` as shown below. ::: ### Context Helpers Fiber and its middleware expose a number of helper functions that retrieve request-scoped values from the context. Middleware stores these values with `fiber.StoreInContext`, and the helpers read them back with `fiber.ValueFromContext`. The helpers accept a `fiber.Ctx` as well as a plain `context.Context`. `StoreInContext` always writes to `c.Locals()`. It also copies the value into the context returned by `c.Context()` when [`PassLocalsToContext`](../api/fiber.md#passlocalstocontext) is enabled, which it is not by default. Reading from `c` itself therefore always works, but a helper called with `c.Context()`, as you would in code that only receives a `context.Context`, finds the value only when that setting is on: ```go app := fiber.New(fiber.Config{PassLocalsToContext: true}) app.Use(requestid.New()) app.Get("/job", func(c fiber.Ctx) error { ctx := c.Context() // take it inside the handler // empty unless PassLocalsToContext is true; copy it, see the caution below id := strings.Clone(requestid.FromContext(ctx)) go func() { _ = id }() return c.SendStatus(fiber.StatusAccepted) }) ``` :::caution Read what you need from the context inside the handler and hand the goroutine a copy. A retained context keeps working after the handler returns, but what it holds may not stay valid: - A string that comes from the request, such as the request ID when the client sent one or the key that `keyauth` extracted, points into a buffer that fasthttp reuses for the next request on the connection. Unless [`Immutable`](../api/fiber.md#immutable) is set, a goroutine that reads it later can see another request's ID or token. Copy it with `strings.Clone` before you hand it over; see [Zero Allocation](../intro.md#zero-allocation). - A context you keep stops resolving a session when its request ends: `session.FromContext` returns `nil` for it from then on, so a goroutine that outlives the handler cannot reach the session, and never another user's. The `*session.Middleware` it returned while the request was running is only valid until the handler returns, so do not hand that pointer to a goroutine that may outlive the handler either. Read what you need inside the handler, for example the session ID, and hand that to the goroutine; see the [Store Pattern](../middleware/session.md#store-pattern-advanced) for background work on a session. ::: Use the same pair for your own values: ```go type userKey struct{} fiber.StoreInContext(c, userKey{}, "john") name, ok := fiber.ValueFromContext[string](c, userKey{}) ``` #### Request ID The RequestID middleware stores the generated identifier in the context. Use `requestid.FromContext` to read it later. ```go app.Use(requestid.New()) app.Get("/", func(c fiber.Ctx) error { id := requestid.FromContext(c) return c.SendString(id) }) ``` #### CSRF The CSRF middleware provides helpers to fetch the token or the handler attached to the current context. ```go app.Use(csrf.New()) app.Get("/form", func(c fiber.Ctx) error { token := csrf.TokenFromContext(c) return c.SendString(token) }) ``` ```go title="Deleting a token" app.Post("/logout", func(c fiber.Ctx) error { handler := csrf.HandlerFromContext(c) if handler != nil { // Invalidate the token on logout _ = handler.DeleteToken(c) } // ... other logout logic return c.SendString("Logged out") }) ``` #### Session Sessions are stored on the context and can be retrieved via `session.FromContext`. ```go app.Use(session.New()) app.Get("/", func(c fiber.Ctx) error { sess := session.FromContext(c) count := sess.Get("visits") return c.JSON(fiber.Map{"visits": count}) }) ``` #### Basic Authentication After successful authentication, the username is available with `basicauth.UsernameFromContext`. Passwords in `Users` must be pre-hashed. ```go app.Use(basicauth.New(basicauth.Config{ Users: map[string]string{ // "secret" hashed using SHA-256 "admin": "{SHA256}K7gNU3sdo+OL0wNhqoVWhr3g6s1xYv72ol/pe/Unols=", }, })) app.Get("/", func(c fiber.Ctx) error { user := basicauth.UsernameFromContext(c) return c.SendString(user) }) ``` #### Key Authentication For API key authentication, the extracted token is stored in the context and accessible via `keyauth.TokenFromContext`. The middleware requires a `Validator`, and reads the key from the `Authorization: Bearer ` header unless you set another `Extractor`. ```go app.Use(keyauth.New(keyauth.Config{ Validator: func(c fiber.Ctx, key string) (bool, error) { return subtle.ConstantTimeCompare([]byte(key), []byte("secret")) == 1, nil }, })) app.Get("/", func(c fiber.Ctx) error { token := keyauth.TokenFromContext(c) return c.SendString(token) }) ``` ### Using `context.WithValue` and Friends Standard helpers such as `context.WithValue`, `context.WithTimeout`, or `context.WithCancel` can wrap the request context when needed. Derive them from `c.Context()`, not from `c`: `c` can never be canceled, so a context derived from it would never inherit a deadline or a cancellation. ```go app.Get("/job", func(c fiber.Ctx) error { ctx, cancel := context.WithTimeout(c.Context(), 5*time.Second) defer cancel() // pass ctx to async operations that honor cancellation if err := doWork(ctx); err != nil { return err } return c.SendStatus(fiber.StatusOK) }) ``` #### Context Cancellation with Goroutines in Fiber When starting asynchronous work inside a handler, Fiber does not cancel the base `fiber.Ctx` automatically. By wrapping the request context with `context.WithTimeout`, you can create a derived context that honors deadlines and cancellation signals. The goroutine checks `ctx.Done()` before sending a result. If the deadline elapses, the goroutine exits early and avoids leaking resources. The handler then waits for either: - a result from the goroutine, or - the `context timeout` (which returns a 504 Gateway Timeout) This pattern ensures that long-running operations (database queries, external API calls, background tasks) do not continue running after the request has ended. ```go func Handler(c fiber.Ctx) error { ctx, cancel := context.WithTimeout(c.Context(), 2*time.Second) defer cancel() resultChan := make(chan string, 1) go func() { select { case <-time.After(3 * time.Second): select { case <-ctx.Done(): return case resultChan <- "done": } case <-ctx.Done(): return } }() select { case res := <-resultChan: return c.SendString(res) case <-ctx.Done(): return c.Status(fiber.StatusGatewayTimeout).SendString("timeout") } } ``` This approach provides safe cancellation semantics for goroutine-based work while allowing you to integrate Fiber handlers with context-aware APIs. ### Summary - `fiber.Ctx` satisfies `context.Context` but its `Deadline`, `Done`, and `Err` methods are currently no-ops. - `RequestCtx` exposes the raw `fasthttp` context. Its `Done` channel closes only on server shutdown, not on client disconnect. - Use `fiber.StoreInContext(c, key, value)` to store request-scoped values in `c.Locals()` and, when `fiber.Config.PassLocalsToContext` is enabled, in `c.Context()` as well, so they are available through either API. - Middleware helpers like `requestid.FromContext` or `session.FromContext` make it easy to retrieve request-scoped data. Copy a string that comes from the request, or set `Immutable`, before a goroutine keeps it. - Standard helpers such as `context.WithTimeout` should wrap `c.Context()`, not `fiber.Ctx`, to create derived contexts that honor deadlines and can outlive the handler. - `fiber.Config.PassLocalsToContext` controls whether Fiber context helpers also propagate values into the request `context.Context` for Fiber-backed contexts when using `StoreInContext`. It defaults to `false` for backward compatibility, while `ValueFromContext` keeps reading from `c.Locals()`. - Use `c.Context()` to obtain a `context.Context` that can outlive the handler, and `c.SetContext()` to customize it with additional values or deadlines. With these tools, you can seamlessly integrate Fiber applications with Go's context-based APIs and manage request-scoped data effectively. --- ## 🚀 Minimal CRUD This guide shows a minimal Fiber app with two things most "hello world" examples leave out: - Liveness, readiness, and startup probes using the [`healthcheck`](../middleware/healthcheck.md) middleware - A tiny in-memory CRUD for `Item` (`list`, `create`, `get`, `update`, `delete`) :::caution This example stores data in memory. It is intended for **learning, smoke tests, and CI checks**, not for production persistence. Restarting the app clears all data. ::: ### Set up the module ```bash mkdir crud && cd crud go mod init example.com/crud go get github.com/gofiber/fiber/v3 ``` ### Full example ```go title="main.go" package main import ( "cmp" "log" "slices" "strconv" "sync" "sync/atomic" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/healthcheck" ) type Item struct { ID int `json:"id"` Name string `json:"name"` } type store struct { mu sync.Mutex items map[int]Item nextID int } func newStore() *store { return &store{items: make(map[int]Item), nextID: 1} } func (s *store) list() []Item { s.mu.Lock() out := make([]Item, 0, len(s.items)) for _, it := range s.items { out = append(out, it) } s.mu.Unlock() // Map iteration is unordered, so sort before answering, and do it off the // lock: out is local from here on. slices.SortFunc(out, func(a, b Item) int { return cmp.Compare(a.ID, b.ID) }) return out } func (s *store) create(name string) Item { s.mu.Lock() defer s.mu.Unlock() it := Item{ID: s.nextID, Name: name} s.items[it.ID] = it s.nextID++ return it } func (s *store) get(id int) (Item, bool) { s.mu.Lock() defer s.mu.Unlock() it, ok := s.items[id] return it, ok } func (s *store) update(id int, name string) (Item, bool) { s.mu.Lock() defer s.mu.Unlock() it, ok := s.items[id] if !ok { return Item{}, false } it.Name = name s.items[id] = it return it, true } func (s *store) delete(id int) bool { s.mu.Lock() defer s.mu.Unlock() if _, ok := s.items[id]; !ok { return false } delete(s.items, id) return true } func main() { app := fiber.New() s := newStore() // Readiness needs a probe of its own: the default one always answers // "healthy", so it could never take this instance out of rotation. var ready atomic.Bool readiness := healthcheck.Config{Probe: func(fiber.Ctx) bool { return ready.Load() }} app.Get(healthcheck.LivenessEndpoint, healthcheck.New()) app.Get(healthcheck.ReadinessEndpoint, healthcheck.New(readiness)) app.Get(healthcheck.StartupEndpoint, healthcheck.New()) app.Get("/items", func(c fiber.Ctx) error { return c.JSON(s.list()) }) app.Post("/items", func(c fiber.Ctx) error { var body struct { Name string `json:"name"` } if err := c.Bind().Body(&body); err != nil { return fiber.NewError(fiber.StatusBadRequest, "invalid body") } return c.Status(fiber.StatusCreated).JSON(s.create(body.Name)) }) app.Get("/items/:id", func(c fiber.Ctx) error { id, err := strconv.Atoi(c.Params("id")) if err != nil { return fiber.NewError(fiber.StatusBadRequest, "invalid id") } it, ok := s.get(id) if !ok { return fiber.NewError(fiber.StatusNotFound, "not found") } return c.JSON(it) }) app.Put("/items/:id", func(c fiber.Ctx) error { id, err := strconv.Atoi(c.Params("id")) if err != nil { return fiber.NewError(fiber.StatusBadRequest, "invalid id") } var body struct { Name string `json:"name"` } if err := c.Bind().Body(&body); err != nil { return fiber.NewError(fiber.StatusBadRequest, "invalid body") } it, ok := s.update(id, body.Name) if !ok { return fiber.NewError(fiber.StatusNotFound, "not found") } return c.JSON(it) }) app.Delete("/items/:id", func(c fiber.Ctx) error { id, err := strconv.Atoi(c.Params("id")) if err != nil { return fiber.NewError(fiber.StatusBadRequest, "invalid id") } if !s.delete(id) { return fiber.NewError(fiber.StatusNotFound, "not found") } return c.SendStatus(fiber.StatusNoContent) }) // In memory, so ready right away and the flag is never observed false. A // real service starts Listen first and flips this once its database is up. ready.Store(true) log.Fatal(app.Listen(":3000")) } ``` The route path `:id` is a parameter; see [Routing](./routing.md#parameters). `c.Bind().Body` maps the request body onto a struct, see [Bind](../api/bind.md#body), and `fiber.NewError` hands the status and message to the central error handler, see [Error handling](./error-handling.md). `atomic.Bool` is a flag that is safe to read and write from many requests at once. ### Try it out Start the app: ```bash go run main.go ``` Check the probes. Each answers `OK` as plain text, and `-f` makes `curl` exit non-zero on a 4xx or 5xx, which is what lets a CI step fail. Use `--fail-with-body` instead when the log should also carry the error message: ```bash curl -fsS http://localhost:3000/livez curl -fsS http://localhost:3000/readyz curl -fsS http://localhost:3000/startupz ``` Pass `ResponseFormat: healthcheck.FormatJSON` in the config to answer `{"status":"OK"}` instead; see [Healthcheck](../middleware/healthcheck.md). Create an item: ```bash curl -fsS -X POST http://localhost:3000/items \ -H "Content-Type: application/json" \ -d '{"name":"first"}' # {"id":1,"name":"first"} ``` List, get, update, delete: ```bash curl -fsS http://localhost:3000/items curl -fsS http://localhost:3000/items/1 curl -fsS -X PUT http://localhost:3000/items/1 \ -H "Content-Type: application/json" \ -d '{"name":"updated"}' curl -fsS -X DELETE http://localhost:3000/items/1 ``` A request for an item that is gone answers `404` with the message as plain text, not as JSON. [Error handling](./error-handling.md) shows how to answer JSON instead: ```bash curl -sS -w '\n%{http_code}\n' http://localhost:3000/items/1 # not found # 404 ``` ### Notes - The store uses a `sync.Mutex` so it is safe to call from multiple handlers. - The handlers accept any name, including an empty one. See [Validation](./validation.md) for rejecting bad input. - Store `false` in `ready` while shutting down so a load balancer drains this instance; `app.Hooks().OnPreShutdown` runs while the server still answers. - For production persistence, replace the `store` with a database of your choice. --- ## 🐛 Error Handling ### Catching Errors Return errors from route handlers and middleware so Fiber can handle them centrally. **Example** ```go app.Get("/", func(c fiber.Ctx) error { // Pass error to Fiber return c.SendFile("file-does-not-exist") }) ``` Fiber does not recover from [panics](https://go.dev/blog/defer-panic-and-recover) by default. Add the `Recover` middleware to catch panics in any handler: ```go title="Example" package main import ( "log" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/recover" ) func main() { app := fiber.New() app.Use(recover.New()) app.Get("/", func(c fiber.Ctx) error { panic("This panic is caught by fiber") }) log.Fatal(app.Listen(":3000")) } ``` A recovered panic reaches the error handler as an ordinary error, so the default handler answers **500** with the panic message as the response body, unless the panic value is itself a `*fiber.Error`, which keeps its status. Set the middleware's `PanicHandler` to return a generic error such as `fiber.ErrInternalServerError` instead; see [Recover](../middleware/recover.md). Use `fiber.NewError()` to create an error with a status code. If you omit the message, Fiber uses the standard status text (for example, `404` becomes `Not Found`). [`fiber.NewErrorf()`](../api/fiber.md#newerrorf) formats the message for you. ```go title="Example" app.Get("/", func(c fiber.Ctx) error { // 503 Service Unavailable return fiber.ErrServiceUnavailable // 503 On vacation! return fiber.NewError(fiber.StatusServiceUnavailable, "On vacation!") }) ``` :::note The status code has to fit a three-digit status line. When Fiber writes a response with a code outside 100-999, it logs an error and sends **500** instead. ::: ### Default Error Handler Fiber ships with a default error handler that sends **500 Internal Server Error** for generic errors. If the error is a [fiber.Error](https://pkg.go.dev/github.com/gofiber/fiber/v3#Error), the response uses the embedded status code and message. ```go title="Example" // Default error handler (fiber.DefaultErrorHandler); utils is github.com/gofiber/utils/v2 func DefaultErrorHandler(c fiber.Ctx, err error) error { // Status code defaults to 500 code := fiber.StatusInternalServerError var e *fiber.Error matched := errors.As(err, &e) if matched && e != nil { code = e.Code } message := utils.StatusMessage(code) if err != nil && (!matched || e != nil) { message = err.Error() } // Set Content-Type: text/plain; charset=utf-8 c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) // Return status code with error message return c.Status(code).SendString(message) } ``` ### Custom Error Handler Set a custom error handler in [`fiber.Config`](../api/fiber.md#errorhandler) when creating a new app. The default handler covers most cases, but a custom handler lets you react to specific error types—for example, by logging to a service or sending a tailored JSON or HTML response. The following example shows how to display error pages for different types of errors. ```go title="Example" // Create a new fiber instance with custom config app := fiber.New(fiber.Config{ // Override default error handler ErrorHandler: func(ctx fiber.Ctx, err error) error { // Status code defaults to 500 code := fiber.StatusInternalServerError // Retrieve the custom status code if it's a *fiber.Error var e *fiber.Error if errors.As(err, &e) && e != nil { code = e.Code } // Send custom error page err = ctx.Status(code).SendFile(fmt.Sprintf("./%d.html", code)) if err != nil { // In case the SendFile fails return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error") } // Return from handler return nil }, }) // ... ``` Call `fiber.DefaultErrorHandler(c, err)` from your handler to fall back to the default response for the errors you do not handle yourself. A sub-app mounted with `app.Use` can set its own `ErrorHandler`. It answers every error raised under the mount path, including the **404** for a path that matches none of the sub-app's routes. An app mounted inside it without a handler of its own uses it too, and any other request falls back to the parent's handler. This needs a literal mount path, taken from the top-level app down: the lookup compares the request path with the mount path as text, so under a parameter, as in `app.Use("/t/:tenant", tenantApp)`, the sub-app's routes still match and run but its handler is not found, and neither is the handler of an app mounted inside it. The handler of the nearest enclosing mount with a literal path answers instead, or the top-level app's handler when there is none. A sub-app mounted through [`Domain`](../api/app.md#domain) matches a parameterized mount path as a pattern and keeps its own handler. ### Errors Before a Handler Runs The error handler also answers requests that never reach a route handler. A request that matches no route reaches it as `fiber.ErrNotFound` (**404**), or as `fiber.ErrMethodNotAllowed` (**405**, with an `Allow` header) when the path exists for other methods. The HTTP server rejects some requests while it reads them. Fiber maps those errors to a `*fiber.Error` before it calls the error handler: | Cause | Status | | ------------------------------------------------------------------------ | ---------------------------------------------------- | | Headers larger than [`ReadBufferSize`](../api/fiber.md#readbuffersize) | `431 Request Header Fields Too Large` | | Body larger than [`BodyLimit`](../api/fiber.md#bodylimit) | `413 Request Entity Too Large` | | A method other than `GET` or `HEAD` while [`GETOnly`](../api/fiber.md#getonly) is enabled | `405 Method Not Allowed`, with `Allow: GET, HEAD` | | A method token the parser rejects, such as `GE(T` | `501 Not Implemented` | | A `Transfer-Encoding` naming a coding the server cannot decode | `501 Not Implemented` | | A timeout while reading the request, or another network error | `408 Request Timeout`, or `502 Bad Gateway` | | Any other malformed request | `400 Bad Request`, with the parser's message | When the request method is a known one, middleware registered with `Use` runs first and already sees the mapped status on the response, while route handlers are skipped. The error handler then writes the response. A well-formed method that is not in [`RequestMethods`](../api/fiber.md#requestmethods), such as `FOO`, or a known one in another letter case, such as `get`, does not get that far: Fiber answers `501 Not Implemented` itself, and neither middleware nor the error handler runs. > Special thanks to the [Echo](https://echo.labstack.com/) and [Express](https://expressjs.com/) frameworks for inspiring parts of this error-handling approach. --- ## 🔬 Extractors The extractors package provides shared value extraction utilities for Fiber middleware packages. It helps reduce code duplication across middleware packages while ensuring consistent behavior and security practices. ### Overview The `github.com/gofiber/fiber/v3/extractors` module provides standardized value extraction utilities integrated into Fiber's middleware ecosystem. This approach: - **Reduces Code Duplication**: Eliminates redundant extractor implementations across middleware packages - **Ensures Consistency**: Maintains identical behavior and security practices across all extractors - **Simplifies Maintenance**: Changes to extraction logic only need to be made in one place - **Enables Direct Usage**: Middleware can import and use extractors directly - **Improves Performance**: Shared, optimized extraction functions reduce overhead ### What Are Extractors? Extractors are utilities that middleware uses to get values from different parts of HTTP requests: #### Available Extractors - `FromAuthHeader(authScheme string)`: Extract from Authorization header with optional scheme - `FromCookie(key string)`: Extract from HTTP cookies - `FromParam(param string)`: Extract from URL path parameters - `FromForm(param string)`: Extract from form data - `FromHeader(header string)`: Extract from custom HTTP headers - `FromQuery(param string)`: Extract from URL query parameters - `FromCustom(key string, fn func(fiber.Ctx) (string, error))`: Define custom extraction logic with metadata - `Chain(extractors ...Extractor)`: Chain multiple extractors with fallback logic - `Resolve(e Extractor, c fiber.Ctx) (Result, error)`: Return the value and which extractor supplied it — `Result.Value`, `Result.Key`, `Result.Source` (preferred for security decisions on chains) - `Extractor.Walk(fn func(Extractor) bool)`: Visit this extractor and every nested chained extractor, depth first and in chain order, stopping early when `fn` returns false - `Extractor.Contains(pred func(Extractor) bool)`: Check whether this extractor, or any nested chained extractor, matches a predicate #### Sources and Errors Each extractor declares where it reads from in its `Source` field and which name it reads in `Key`: | Extractor | `Source` | `Key` | | ---------------- | ------------------------------------------- | ----------------------------------------------------------------------------------------- | | `FromHeader` | `SourceHeader` (the zero value of `Source`) | The header name | | `FromAuthHeader` | `SourceAuthHeader` | `Authorization`; the scheme you pass is kept in `AuthScheme` | | `FromCookie` | `SourceCookie` | The cookie name | | `FromParam` | `SourceParam` | The route parameter name | | `FromForm` | `SourceForm` | The form field name | | `FromQuery` | `SourceQuery` | The query parameter name | | `FromCustom` | `SourceCustom` | The `key` you pass | | `Chain` | That of the first child | That of the first child (`SourceCustom` and an empty `Key` for a chain without children) | An extractor fails with `ErrNotFound` when its value is missing or empty, and a chain that re-enters itself fails with `ErrChainCycle`. A custom `Extract` function may return any other error. A chain moves on to the next child after an error and returns the last error if no child succeeds. #### Extractor Behavior An empty value counts as missing for every extractor. A few more details matter when the value is a credential: - **`FromHeader`** matches the header name case-insensitively, also when `DisableHeaderNormalizing` is on. A header sent on several lines is joined into one value, with a comma and a space between the lines (RFC 9110 Section 5.3), so a repeated token does not equal the token that was issued. - **`FromAuthHeader`** returns `ErrNotFound` when the request carries more than one `Authorization` line, however each one is spelled, because the credential would be ambiguous. The accepted format is described under [Standards Compliance](#standards-compliance). - **`FromParam`** returns the parameter percent-decoded exactly once, whatever the [`UnescapePath`](../api/fiber.md#unescapepath) setting is: a literal `%20` sent as `%2520` comes back as `%20`. With `UnescapePath` off, a value that cannot be decoded returns `ErrNotFound`; with it on, the router has already decoded the path, so a malformed escape such as `100%` comes back as sent. `Ctx.Params` returns the raw value when `UnescapePath` is off. - **`FromForm`** reads through [`Ctx.FormValue`](../api/ctx.md#formvalue), which looks in the query string first, then in the url-encoded body, then in the multipart fields. A value in the URL therefore satisfies `FromForm`, and `Resolve` still reports `SourceForm` for it. Do not rely on `FromForm` alone to keep a value out of URLs. #### Extractor Structure Each `Extractor` contains the fields below (order matches the Go struct). Prefer **keyed** composite literals when constructing `Extractor` values. Source-aware extraction is provided by the package function `Resolve` — it is **not** a field on `Extractor`, so existing unkeyed literals keep compiling. ```go type Extractor struct { Extract func(fiber.Ctx) (string, error) // Extraction callback Key string // Parameter/header name AuthScheme string // Auth scheme (FromAuthHeader) Chain []Extractor // Chained extractors (introspection copy) Source Source // Declared/static source metadata } ``` `Resolve` reports provenance as a `Result`, which carries metadata only: ```go type Result struct { Value string // The extracted value Key string // The parameter/header/cookie name the winning extractor read Source Source // The kind of source the winning extractor read from Resolved bool // Whether Key and Source name the extractor that actually produced Value } ``` `Resolved` is false when a chain's own `Extract` answered without any child being observed to win. `Key` and `Source` are then the chain's declared metadata (its first child) and say nothing about where the value came from, so a security decision — such as writing a value back to where it was read from — must refuse rather than trust them. - **Headers**: `Authorization`, `X-API-Key`, custom headers - **Cookies**: Session cookies, authentication tokens - **Query Parameters**: URL parameters like `?token=abc123` - **Form Data**: Form fields of a url-encoded or multipart body (`FromForm` also checks the query string, see [Extractor Behavior](#extractor-behavior)) - **URL Parameters**: Route parameters like `/users/:id` #### Source-Aware Extraction `Extractor.Source` is **declared/static metadata**, not an authoritative origin signal by itself: - For a chain, static `Source` is the **first** child. - `SourceHeader` is the zero value of `Source`. A legacy `Extract`-only callback that omits `Source` therefore reports `SourceHeader` via `Resolve`. - Error paths may return static or last-child source metadata even when no value was supplied. Use the returned source for security decisions **only when `err == nil`**, and only when that source metadata is known to be accurate for your extractor. When middleware needs to know which extractor actually produced the value — to apply a stricter policy, or to write the value back to where it came from — call the package function `Resolve`: ```go tokenExtractor := extractors.Chain( extractors.FromHeader("X-API-Key"), extractors.FromQuery("api_key"), ) res, err := extractors.Resolve(tokenExtractor, c) if err != nil { return err } // res.Value is the extracted value. // res.Source is SourceHeader or SourceQuery depending on which child won — do // not assume tokenExtractor.Source (always SourceHeader for this chain). // res.Key is the header or query parameter name that child read. if res.Source == extractors.SourceQuery { // e.g. apply stricter policy or audit logging for query-sourced secrets } ``` `Resolve` always uses the public `Extract` callback when set (so leaf and chain-level `Extract` overrides for validation/normalization are honored). For a built-in `Chain`, that single `Extract` pass records the winning child and `Resolve` reads the capture — children are not extracted a second time. If `Extract` is fully replaced without delegating to the original (no capture), the declared static metadata is returned rather than re-walking `Chain` children. Leaves return `Extract`'s result with the static metadata. There is no second callback field to clear or re-point. When `Extract` is `nil`, `Resolve` walks the extractor's own `Chain` instead, nested chains included. An extractor with neither returns `ErrNotFound`. #### Chain Behavior The `Chain` function creates extractors that try multiple sources in order: - Returns the first successful extraction (non-empty value with no error) - If all extractors fail, returns the last error encountered or `ErrNotFound` - **Robust error handling**: Skips children with a `nil` `Extract` (and zero-value trailing children) - **Cycle prevention**: Detects recursive chain re-entry and returns `ErrChainCycle` (shared across `Extract` and `Resolve`) - Preserves `Source` and `Key` from the first extractor for static metadata (not `AuthScheme`) - On success with `Resolved` true, `Resolve` reports the winning child's `Key` and `Source` at runtime - On failure, the returned source is fallback metadata only — do not treat it as the origin of an extracted value - Exposes a **defensive copy** of children via the `Chain` field for introspection; mutating that slice does not change which children `Extract` runs - An empty `Chain()` always fails with `ErrNotFound` #### Chain Introspection Use `Contains` to inspect an extractor tree with a predicate: ```go chain := extractors.Chain( extractors.FromHeader("X-CSRF-Token"), extractors.FromCookie("CSRF"), ) hasCSRFCookie := chain.Contains(func(e extractors.Extractor) bool { return e.Source == extractors.SourceCookie && e.Key == "CSRF" }) ``` `Walk` visits every extractor in the tree, depth first and in chain order, starting with the extractor itself. Prefer `Walk` and `Contains` to ranging over the `Chain` field, which lists only direct children and judges a nested chain by its declared metadata (its first child) instead of by what is inside it. A chain is visited too, so skip entries with a non-empty `Chain` when you are looking for the extractors that actually read a value: ```go var cookies []extractors.Extractor chain.Walk(func(e extractors.Extractor) bool { if len(e.Chain) == 0 && e.Source == extractors.SourceCookie { cookies = append(cookies, e) } return true // keep walking; return false to stop early }) ``` ### Why Middleware Uses Extractors Middleware needs to extract values from requests for authentication, authorization, and other purposes. Extractors provide: - **Security Awareness**: Different sources have different security implications - **Fallback Support**: Try multiple sources if the first one doesn't have the value - **Consistency**: Same extraction logic across all middleware packages - **Source Tracking**: Know where values came from for security decisions ### Usage Examples #### Basic Usage `keyauth.New` requires a `Validator` and panics at startup without one. The `keyauth` snippets in this guide use this validator: ```go import ( "crypto/sha256" "crypto/subtle" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/keyauth" ) var apiKey = "correct-horse-battery-staple" func validateAPIKey(c fiber.Ctx, key string) (bool, error) { // Hash both values so the comparison takes the same time whatever their length hashedAPIKey := sha256.Sum256([]byte(apiKey)) hashedKey := sha256.Sum256([]byte(key)) if subtle.ConstantTimeCompare(hashedAPIKey[:], hashedKey[:]) == 1 { return true, nil } return false, keyauth.ErrMissingOrMalformedAPIKey } ``` ```go // KeyAuth middleware extracts key from header app.Use(keyauth.New(keyauth.Config{ Extractor: extractors.FromHeader("Middleware-Key"), Validator: validateAPIKey, })) ``` #### Fallback Chains ```go // Try multiple sources in order tokenExtractor := extractors.Chain( extractors.FromHeader("Middleware-Key"), // Try header first extractors.FromCookie("middleware_key"), // Then cookie extractors.FromQuery("middleware_key"), // Finally query param ) app.Use(keyauth.New(keyauth.Config{ Extractor: tokenExtractor, Validator: validateAPIKey, })) ``` ### Configuring Middleware That Uses Extractors #### Authentication Middleware ```go // KeyAuth middleware (default: FromAuthHeader) app.Use(keyauth.New(keyauth.Config{ // Default extracts from Authorization header // Extractor: extractors.FromAuthHeader("Bearer"), Validator: validateAPIKey, })) // Custom header extraction app.Use(keyauth.New(keyauth.Config{ Extractor: extractors.FromHeader("X-API-Key"), Validator: validateAPIKey, })) // Multiple sources with secure fallback app.Use(keyauth.New(keyauth.Config{ Extractor: extractors.Chain( extractors.FromAuthHeader("Bearer"), // Secure first extractors.FromHeader("X-API-Key"), // Then custom header extractors.FromQuery("api_key"), // Least secure last ), Validator: validateAPIKey, })) ``` #### Session Middleware ```go // Session middleware (default: FromCookie) app.Use(session.New(session.Config{ // Default extracts from session_id cookie // Extractor: extractors.FromCookie("session_id"), })) // Custom cookie name app.Use(session.New(session.Config{ Extractor: extractors.FromCookie("my_session"), })) ``` #### CSRF Middleware ```go // CSRF middleware (default: FromHeader) app.Use(csrf.New(csrf.Config{ // Default extracts from X-CSRF-Token header // Extractor: extractors.FromHeader("X-CSRF-Token"), })) // Form-based CSRF (less secure, use only if needed) app.Use(csrf.New(csrf.Config{ Extractor: extractors.Chain( extractors.FromHeader("X-CSRF-Token"), // Secure first extractors.FromForm("_csrf"), // Form fallback ), })) ``` ### Security Considerations #### Source Characteristics Different extraction sources have different security properties and use cases: ##### Headers (Generally Preferred) - **Authorization Header**: Standard for authentication tokens, widely supported - **Custom Headers**: Application-specific, less likely to be logged by default - **Considerations**: Can be intercepted without HTTPS, may be stripped by proxies ##### Cookies (Good for Sessions) - **Session Cookies**: Designed for secure client-side storage - **Considerations**: Require proper `Secure`, `HttpOnly`, and `SameSite` flags - **Best for**: Session management, remember-me tokens ##### Query Parameters (Use Sparingly) - **Query parameters**: Convenient for simple APIs and debugging - **Considerations**: Always visible in URLs, logged by servers/proxies, stored in browser history - **Best for**: Non-sensitive parameters, public identifiers ##### Form Data (Context Dependent) - **POST Bodies**: Suitable for form submissions and API requests - **Considerations**: Avoid putting sensitive data in query strings; ensure request bodies aren’t logged and use the correct content type - **Query string fallback**: `FromForm` also accepts the field from the query string, so a value in the URL satisfies it, and the query string wins when both carry the field - **Best for**: User-generated content, file uploads #### Security Best Practices 1. **Use HTTPS**: Encrypt all traffic to protect extracted values in transit 2. **Validate Input**: Always validate and sanitize extracted values 3. **Log Carefully**: Avoid logging sensitive values from any source 4. **Choose Appropriate Sources**: Match the source to your security requirements 5. **Test Thoroughly**: Verify extraction works in your environment 6. **Monitor Security**: Watch for extraction failures or unusual patterns #### Chain Ordering Strategy When using multiple sources, order them by your security preferences: ```go // Example: Prefer headers, fall back to cookies, then query extractors.Chain( extractors.FromAuthHeader("Bearer"), // Standard auth extractors.FromCookie("auth_token"), // Secure storage extractors.FromQuery("token"), // Public fallback ) ``` The "best" source depends on your specific use case, security requirements, and application architecture. #### Common Security Issues ##### Leaky URLs ```go // ❌ DON'T: API keys in URLs (visible in logs, history, bookmarks) app.Use(keyauth.New(keyauth.Config{ Extractor: extractors.FromQuery("api_key"), // PROBLEMATIC Validator: validateAPIKey, })) // ✅ DO: API keys in headers (not visible in URLs) app.Use(keyauth.New(keyauth.Config{ Extractor: extractors.FromHeader("X-API-Key"), // BETTER Validator: validateAPIKey, })) ``` ##### Session Tokens in Query Parameters ```go // ❌ DON'T: Session tokens in URLs (can be bookmarked, leaked) app.Use(session.New(session.Config{ Extractor: extractors.FromQuery("session"), // PROBLEMATIC })) // ✅ DO: Session tokens in cookies (designed for this purpose) app.Use(session.New(session.Config{ Extractor: extractors.FromCookie("session_id"), // BETTER })) ``` ##### Form-Only CSRF Tokens While the default extractor uses headers, some implementations use form fields, which is fine if you don't have AJAX or API clients: ```go // ❌ DON'T: CSRF tokens only in forms (breaks AJAX, API calls) app.Use(csrf.New(csrf.Config{ Extractor: extractors.FromForm("_csrf"), // LIMITED })) // ✅ DO: Header-first with form fallback (works everywhere) app.Use(csrf.New(csrf.Config{ Extractor: extractors.Chain( extractors.FromHeader("X-CSRF-Token"), // PREFERRED extractors.FromForm("_csrf"), // FALLBACK ), })) ``` #### Understanding Trade-offs **No extractor is universally "secure" - security depends on:** - Whether you're using HTTPS - How you configure cookies (Secure, HttpOnly, SameSite flags) - Your logging and monitoring setup - The sensitivity of the data being extracted - Your threat model and security requirements Choose extractors based on your specific use case and security needs, not blanket "secure" vs "insecure" labels. ### Standards Compliance #### Authorization Header (RFC 9110 & RFC 7235) The `FromAuthHeader` extractor validates the Authorization header format described below with strict security checks, and keeps to the RFCs except for one documented restriction: ##### RFC 9110 Compliance (Authorization Header Format) - **Section 11.6.2 Format**: Enforces `credentials = auth-scheme SP token68` structure - **Single space (intentional deviation)**: RFC 9110 allows `1*SP` between the auth-scheme and the token. The extractor requires exactly one space, which is what clients send, and rejects anything else, so the parse stays unambiguous - **Case-insensitive scheme matching**: `Bearer`, `bearer`, `BEARER` all work correctly - **Proper whitespace handling**: Rejects tabs between scheme and token (only a single space is allowed) - **token68 credentials only**: When a scheme is given, credentials made of `auth-param` lists (such as `Digest`) are rejected. Use `FromAuthHeader("")` to read the raw header value instead ##### RFC 7235 Token68 Validation The extractor implements strict token68 character validation per RFC 7235: - **Allowed characters**: `A-Z`, `a-z`, `0-9`, `-`, `.`, `_`, `~`, `+`, `/`, `=` - **Padding rules**: `=` characters only allowed at the end of tokens - **Security validation**: Prevents tokens starting with `=` or having non-padding characters after `=` - **Whitespace rejection**: Rejects tokens containing spaces, tabs, or any other whitespace ##### Security Features - **Header injection prevention**: Strict parsing prevents malformed authorization headers from bypassing authentication - **Token validation**: Ensures extracted tokens conform to standards, preventing authentication bypass - **Consistent error handling**: Returns `ErrNotFound` for all invalid cases ##### Examples ```go // Standard usage - strict validation extractor := extractors.FromAuthHeader("Bearer") // ✅ Valid cases: // "Bearer abc123" -> "abc123" // "bearer ABC123" -> "ABC123" (case-insensitive scheme) // "Bearer token123=" -> "token123=" (valid padding) // "Bearer token==" -> "token==" (valid multiple padding) // ❌ Invalid cases (all return ErrNotFound): // "Bearer abc def" -> rejected (space in token) // "Bearer abc\tdef" -> rejected (tab in token) // "Bearer =abc" -> rejected (padding at start) // "Bearer ab=cd" -> rejected (padding in middle) // "Bearer token" -> rejected (multiple spaces after scheme) // "Bearer\ttoken" -> rejected (tab after scheme) // "Bearertoken" -> rejected (no space after scheme) // Raw header extraction (no validation) rawExtractor := extractors.FromAuthHeader("") // "CustomAuth anything goes here" -> "CustomAuth anything goes here" ``` ##### Benefits - **Standards Compliance**: Follows RFC 9110 and RFC 7235 for the supported format, with the single-space restriction above as its one intentional deviation - **Security Hardening**: Prevents common authentication bypass vulnerabilities - **Consistent Behavior**: Reliable parsing across different client implementations - **Developer Confidence**: Clear validation rules reduce authentication bugs ### Troubleshooting #### Extraction Fails **Problem**: Middleware returns "value not found" or authentication fails **Solutions**: 1. Check if the expected header/cookie/query parameter is present 2. Verify the key name matches exactly (headers are case-insensitive; params/cookies/query keys are case-sensitive) 3. Ensure the request uses the correct HTTP method (GET vs POST) 4. Check if middleware is configured with the right extractor **Debug Example**: ```go // Add simple debug logging (avoid logging secrets in production) app.Use(func(c fiber.Ctx) error { hdr := c.Get("X-API-Key") cookie := c.Cookies("session_id") if hdr != "" || cookie != "" { log.Printf("debug: X-API-Key present=%t, session_id present=%t", hdr != "", cookie != "") } return c.Next() }) ``` #### Wrong Source Used **Problem**: Values extracted from unexpected sources **Solutions**: 1. Check middleware configuration order 2. Verify chain order (first successful extraction wins) 3. Use more specific extractors when needed #### Security Warnings **Problem**: Getting security warnings in logs **Solutions**: 1. Switch to more secure sources (headers/cookies) 2. Use HTTPS to encrypt traffic 3. Review if sensitive data should be in that source ### Advanced Usage #### Custom Extraction Logic Extractors support custom extractors for complex scenarios: ```go // Extract from custom logic (rarely needed) customExtractor := extractors.FromCustom("my-source", func(c fiber.Ctx) (string, error) { // Complex extraction logic if value := c.Locals("computed_token"); value != nil { return value.(string), nil } return "", extractors.ErrNotFound }) ``` :::warning **Custom extractors break source awareness.** When you use `FromCustom`, middleware cannot determine where the value came from, which means: - **No automatic security warnings** for potentially insecure sources - **No source-based logging** or monitoring capabilities - **Developer responsibility** for ensuring the extraction is secure and appropriate **Only use `FromCustom` when:** - Standard extractors don't meet your needs - You've carefully evaluated the security implications - You're confident in the security of your custom extraction logic - You understand that middleware cannot provide source-aware security guidance **Note:** If you pass `nil` as the function parameter, `FromCustom` will return an extractor that always fails with `ErrNotFound`. ::: #### Multiple Middleware Coordination When using multiple middleware that extract values, ensure they don't conflict: ```go // Good: Different sources for different purposes app.Use(keyauth.New(keyauth.Config{ Extractor: extractors.FromHeader("X-API-Key"), Validator: validateAPIKey, })) app.Use(session.New(session.Config{ Extractor: extractors.FromCookie("session_id"), })) // Avoid: Same source for different middleware app.Use(keyauth.New(keyauth.Config{ Extractor: extractors.FromCookie("token"), // API auth Validator: validateAPIKey, })) app.Use(session.New(session.Config{ Extractor: extractors.FromCookie("token"), // Session - CONFLICT! })) ``` --- ## ⚡ Make Fiber Faster ### Custom JSON Encoder/Decoder Fiber defaults to the standard `encoding/json` for stability and reliability. If you need more speed, consider these libraries: - [goccy/go-json](https://github.com/goccy/go-json) - [bytedance/sonic](https://github.com/bytedance/sonic) - [segmentio/encoding](https://github.com/segmentio/encoding) - [minio/simdjson-go](https://github.com/minio/simdjson-go) ```go title="Example" package main import "github.com/gofiber/fiber/v3" import "github.com/goccy/go-json" func main() { app := fiber.New(fiber.Config{ JSONEncoder: json.Marshal, JSONDecoder: json.Unmarshal, }) // ... } ``` #### References - [Set custom JSON encoder for client](../client/rest.md#setjsonmarshal) - [Set custom JSON decoder for client](../client/rest.md#setjsonunmarshal) - [Set custom JSON encoder for application](../api/fiber.md#jsonencoder) - [Set custom JSON decoder for application](../api/fiber.md#jsondecoder) ### Alternative Regex Engines for `regex()` Constraints Fiber route patterns still do not support general regex routes, but you can swap the compiler used by `regex()` parameter constraints through [`Config.RegexHandler`](../api/fiber.md#regexhandler). This lets you try high-performance engines such as [coregex](https://github.com/coregx/coregex) on the matching path. #### Configure `RegexHandler` Set `RegexHandler` to the compile function you want Fiber to use for `regex()` constraints. ```go title="Example" package main import ( "github.com/coregx/coregex" "github.com/gofiber/fiber/v3" ) func main() { app := fiber.New(fiber.Config{ RegexHandler: coregex.MustCompile, }) app.Get("/api/:id", func(c fiber.Ctx) error { return c.SendString("ID: " + c.Params("id")) }) } ``` You can also set it explicitly to the standard library default: ```go title="Example" package main import ( "regexp" "github.com/gofiber/fiber/v3" ) func main() { app := fiber.New(fiber.Config{ RegexHandler: regexp.MustCompile, }) _ = app } ``` #### Notes - `RegexHandler` only affects `regex()` parameter constraints - `RegexHandler` must be a non-nil function of the form `func(string) T`, where `T` has a `MatchString(string) bool` method; `fiber.New` panics for any other shape, so `regexp.Compile`, which also returns an error, does not fit, and registering a route panics when the handler returns a nil matcher - invalid patterns still panic during route registration because Fiber uses `MustCompile`-style semantics - Fiber may invoke `RegexHandler` more than once per route while parsing raw and normalized route patterns during registration - compiled matchers are reused across requests, so custom matchers must be safe for concurrent use ### Related Performance Reading - [Zero Allocation](../intro.md#zero-allocation): why `Ctx` values are reused across requests and how to copy them safely - [Benchmarks](../extra/benchmarks.md): how Fiber compares to other frameworks --- ## 🎭 Grouping :::info Grouping works like Express.js. Groups are virtual; routes are flattened with the group's prefix and executed in declaration order, mirroring Express.js. ::: ### Paths Groups can use path prefixes to organize related routes. ```go func main() { app := fiber.New() api := app.Group("/api", middleware) // /api v1 := api.Group("/v1", middleware) // /api/v1 v1.Get("/list", handler) // /api/v1/list v1.Get("/user", handler) // /api/v1/user v2 := api.Group("/v2", middleware) // /api/v2 v2.Get("/list", handler) // /api/v2/list v2.Get("/user", handler) // /api/v2/user log.Fatal(app.Listen(":3000")) } ``` :::note Group prefixes follow the same slash-boundary rule as `app.Use`. A prefix must either match the full path or stop at a `/`, so `/api` applies to `/api` and `/api/v1` but not `/apiv1`. Parameter markers (for example `:id`, `:id?`, `*`, and `+`) are processed before checking the boundary. ::: Groups can also include an optional handler. ```go func main() { app := fiber.New() api := app.Group("/api") // /api v1 := api.Group("/v1") // /api/v1 v1.Get("/list", handler) // /api/v1/list v1.Get("/user", handler) // /api/v1/user v2 := api.Group("/v2") // /api/v2 v2.Get("/list", handler) // /api/v2/list v2.Get("/user", handler) // /api/v2/user log.Fatal(app.Listen(":3000")) } ``` :::caution Accessing `/api`, `/v1`, or `/v2` directly returns a **404**, so add error handlers as needed. ::: ### Group Handlers Group handlers can act as routing paths but must call `Next` to continue the flow. ```go func main() { app := fiber.New() handler := func(c fiber.Ctx) error { return c.SendStatus(fiber.StatusOK) } api := app.Group("/api") // /api v1 := api.Group("/v1", func(c fiber.Ctx) error { // middleware for /api/v1 c.Set("Version", "v1") return c.Next() }) v1.Get("/list", handler) // /api/v1/list v1.Get("/user", handler) // /api/v1/user log.Fatal(app.Listen(":3000")) } ``` ### Route [`Route`](../api/app.md#route) groups routes under a common prefix declared inside a single callback, with an optional name prefix. It is shorthand for nesting with `Group`. ```go app.Route("/api/v1", func(r fiber.Router) { r.Get("/users", handler).Name("users") // /api/v1/users (name: v1.users) r.Post("/users", handler).Name("create") // /api/v1/users (name: v1.create) }, "v1.") ``` ### RouteChain When several HTTP methods share the **same path**, [`RouteChain`](../api/app.md#routechain) lets you declare the path once and chain the verb handlers. Handlers run in the order you declare them, so an `All` declared before the verb handlers runs ahead of them on that path, acting as route-specific middleware. ```go app.RouteChain("/events"). All(func(c fiber.Ctx) error { return c.Next() }). // route-local middleware Get(func(c fiber.Ctx) error { return c.SendString("GET /events") }). Post(func(c fiber.Ctx) error { return c.SendString("POST /events") }) ``` :::note Within a chain, `All` registers prefix-matched middleware (like [`app.Use`](../api/app.md#use)), not the exact-path `App.All`, so it also runs for sub-paths of the chain path. ::: ### Domain [`Domain`](../api/app.md#domain) scopes routes to a hostname. Called on a group, it keeps the group's prefix: only requests whose hostname matches the pattern reach the routes you register on the returned router. ```go api := app.Group("/api") api.Domain("api.example.com").Get("/users", handler) // GET api.example.com/api/users only ``` --- ## 🔄 Reverse Proxy ### Proxies Running Fiber behind a reverse proxy is a common production setup. Reverse proxies can handle: - **HTTPS/TLS termination** (offloading SSL certificates) - **Protocol upgrades** (HTTP/2, HTTP/3 support) - **Request routing & load balancing** - **Caching & compression** - **Security features** (rate limiting, WAF, DDoS mitigation) Some Fiber features (like [`SendEarlyHints`](../api/ctx.md#sendearlyhints)) work best with **HTTP/2 or newer**, which is easiest to enable using a reverse proxy. #### Popular Reverse Proxies - [Nginx](https://nginx.org/) - [Traefik](https://traefik.io/) - [HA PROXY](https://www.haproxy.com/) - [Caddy](https://caddyserver.com/) ### Getting the Real Client IP Address When your Fiber application is behind a reverse proxy, the TCP connection comes from the proxy server, not the actual client. To get the real client IP address, you need to configure Fiber to read it from proxy headers like `X-Forwarded-For`. :::warning Security Warning Proxy headers can be easily spoofed by malicious clients. **Always** configure `TrustProxyConfig` to validate the proxy IP address, otherwise attackers can forge headers to bypass IP-based access controls, rate limiting, or geolocation features. In addition, your reverse proxy should be configured to **set or overwrite** the forwarding header you choose (for example, `X-Forwarded-For`) based on the real client connection, or to use its real IP / PROXY protocol features. Do not simply pass through client-supplied forwarding headers, or `c.IP()` may still be controlled by an attacker even when `TrustProxyConfig` is correct. ::: :::info Chain parsing with `EnableIPValidation` `X-Forwarded-For` is a comma-separated chain that each proxy appends to. With `EnableIPValidation` enabled, `c.IP()` walks the chain from **right to left** and strips every IP that matches the configured `TrustProxyConfig`. The first non-trusted IP is returned as the client. Trust only your proxies: blanket `Private`, `Loopback` or `LinkLocal` trust is unsafe when clients can also connect from those ranges, because such a client's own entry is skipped and the address it supplied is returned instead. This matches the [MDN guidance](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-For#selecting_an_ip_address) and the convention used by Nginx (`set_real_ip_from` + `real_ip_recursive`), Apache `mod_remoteip`, Envoy (`xff_num_trusted_hops`), and most CDNs. If `TrustProxyConfig` names no proxy IPs or ranges (`Proxies`, `Loopback`, `Private` and `LinkLocal` are all unset), for example when only `UnixSocket` is set, there is nothing to skip: `c.IP()` returns the first valid IP from the left, which is the entry a client can supply itself unless your proxy overwrites the header. Without `EnableIPValidation`, `c.IP()` returns the raw header value (a comma-separated string), which is rarely what middleware like rate limiters or allowlists expect. Enable validation when you rely on `c.IP()` as a single client identifier. ::: #### Configuration To enable reading the client IP from proxy headers, you must configure **three settings**: 1. **`TrustProxy`** - Enable proxy header trust (must be `true`) 2. **`ProxyHeader`** - Specify which header contains the client IP 3. **`TrustProxyConfig`** - Define which proxy IPs to trust When the header is a chain your proxy appends to, such as `X-Forwarded-For`, also enable **`EnableIPValidation`** so that `c.IP()` resolves a single client IP instead of returning the raw chain. A proxy that overwrites the header with one address, or a single-IP header such as `X-Real-IP` or `CF-Connecting-IP`, does not need it. `TrustProxyConfig` defaults to an empty value (`fiber.DefaultTrustProxyConfig`), which trusts no peer. With `TrustProxy` enabled but no proxy listed, Fiber ignores every proxy header and `c.IP()` returns the address of the connecting proxy. ```go title="Example - App Behind Nginx" app := fiber.New(fiber.Config{ // Enable proxy support TrustProxy: true, // Read client IP from X-Forwarded-For header ProxyHeader: fiber.HeaderXForwardedFor, // Resolve a single client IP from the X-Forwarded-For chain EnableIPValidation: true, // Trust requests from your Nginx proxy TrustProxyConfig: fiber.TrustProxyConfig{ // Option 1: Trust specific proxy IPs Proxies: []string{"10.10.0.58", "192.168.1.0/24"}, // Option 2: Or trust all private IPs (useful for internal load balancers, // but only when clients never connect from private addresses) // Private: true, }, }) ``` #### Common Proxy Headers Different proxies use different headers: | Proxy/Service | Recommended Header | Config Value | |---------------|-------------------|--------------| | Nginx, HAProxy, Apache | X-Forwarded-For | `fiber.HeaderXForwardedFor` | | Cloudflare | CF-Connecting-IP | `"CF-Connecting-IP"` | | Fastly | Fastly-Client-IP | `"Fastly-Client-IP"` | | Generic | X-Real-IP | `"X-Real-IP"` | #### TrustProxyConfig Options The `TrustProxyConfig` struct provides multiple ways to specify trusted proxies: ```go TrustProxyConfig: fiber.TrustProxyConfig{ // Specific IPs or CIDR ranges Proxies: []string{ "10.10.0.58", // Single IP "192.168.0.0/24", // CIDR range "2001:db8::/32", // IPv6 range }, // Or use convenience flags: Loopback: true, // Trust 127.0.0.0/8, ::1/128 LinkLocal: true, // Trust 169.254.0.0/16, fe80::/10 Private: true, // Trust 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fc00::/7 UnixSocket: true, // Trust Unix domain socket connections }, ``` #### Complete Example with Nginx ```nginx title="nginx.conf" server { listen 443 ssl; http2 on; server_name example.com; ssl_certificate /etc/ssl/certs/example.crt; ssl_certificate_key /etc/ssl/private/example.key; location / { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_set_header Host $host; # Overwrite untrusted inbound forwarding headers at the edge. proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Proto $scheme; } } ``` ```go title="main.go" package main import ( "log" "github.com/gofiber/fiber/v3" ) func main() { app := fiber.New(fiber.Config{ TrustProxy: true, ProxyHeader: fiber.HeaderXForwardedFor, EnableIPValidation: true, TrustProxyConfig: fiber.TrustProxyConfig{ // Trust localhost since Nginx is on the same machine Loopback: true, }, }) app.Get("/", func(c fiber.Ctx) error { // This will now return the real client IP from X-Forwarded-For // instead of 127.0.0.1 return c.SendString("Your IP: " + c.IP()) }) log.Fatal(app.Listen(":3000")) } ``` #### Scheme and Host Behind a Proxy The same trust check guards the other values a proxy forwards. When the connecting peer is trusted: - `c.Scheme()` follows `X-Forwarded-Proto`, `X-Forwarded-Protocol`, `X-Forwarded-Ssl: on` or `X-Url-Scheme`, and accepts only `http` or `https`. - `c.Host()` and `c.Hostname()` use the first value of `X-Forwarded-Host`, and the `Host` header when it is absent. - `c.BaseURL()` is built from those two. For an untrusted peer Fiber ignores these headers and uses the connection itself, and a TLS connection always reports `https`. `c.Protocol()` never comes from a header: it reports the HTTP version spoken between the proxy and Fiber, usually `HTTP/1.1`, not the one the client used. #### Testing Your Configuration You can verify your configuration is working: ```go app.Get("/debug", func(c fiber.Ctx) error { return c.JSON(fiber.Map{ "c.IP()": c.IP(), // Should show real client IP "X-Forwarded-For": c.Get("X-Forwarded-For"), // Raw header value "IsProxyTrusted": c.IsProxyTrusted(), // Should be true "RemoteIP": c.RequestCtx().RemoteIP().String(), // Proxy IP }) }) ``` ### Enabling HTTP/2 Popular choices include Nginx and Traefik.
Nginx Example See the [Complete Example with Nginx](#complete-example-with-nginx) above for a full configuration with HTTP/2 enabled.
Traefik Example ```yaml title="traefik.yaml" entryPoints: websecure: address: ":443" http: routers: app: rule: "Host(`example.com`)" entryPoints: - websecure service: app tls: {} services: app: loadBalancer: servers: - url: "http://127.0.0.1:3000" ``` With this configuration, Traefik terminates TLS and serves your app over HTTP/2.
### HTTP/3 (QUIC) Support Early Hints (103 responses) are defined for HTTP and can be delivered over HTTP/1.1 and HTTP/2/3. In practice, browsers process 103 most reliably over HTTP/2/3. Many reverse proxies also support HTTP/3 (QUIC): - **Nginx** - **Traefik** Enabling HTTP/3 is optional but can provide lower latency and improved performance for clients that support it. If you enable HTTP/3, your Early Hints responses will still work as expected. For more details, see the official documentation: - [Nginx QUIC / HTTP/3](https://nginx.org/en/docs/quic.html) - [Traefik HTTP/3](https://doc.traefik.io/traefik/reference/install-configuration/entrypoints/#http3) --- ## 🔌 Routing ### Anatomy of a route A route ties together an HTTP method, a path, and one or more handlers. Hover or click any colored part to jump to the section that explains it: `Get` is the [routing method](#route-handlers), `"/users/:id"` is the [route path](#paths) (the resource, in REST terms) with `:id` a [route parameter](#parameters), and `func(c fiber.Ctx) error` is the [handler](#handler-types) (or [middleware](#middleware)) run when the route matches. Want to see matching happen? Fire requests at your own route table in the interactive [Route Matcher](../extra/route-matcher.md). ### Route Handlers Registers a route bound to a specific [HTTP method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods). The canonical handler is `func(fiber.Ctx) error`; Fiber also accepts `func(fiber.Ctx)` and runs it as if it returned `nil`. ```go title="Signatures" // HTTP methods func (app *App) Get(path string, handler any, handlers ...any) Router func (app *App) Head(path string, handler any, handlers ...any) Router func (app *App) Post(path string, handler any, handlers ...any) Router func (app *App) Put(path string, handler any, handlers ...any) Router func (app *App) Delete(path string, handler any, handlers ...any) Router func (app *App) Connect(path string, handler any, handlers ...any) Router func (app *App) Options(path string, handler any, handlers ...any) Router func (app *App) Trace(path string, handler any, handlers ...any) Router func (app *App) Patch(path string, handler any, handlers ...any) Router func (app *App) Query(path string, handler any, handlers ...any) Router // Add registers the same handlers on multiple methods at once. // The handlers run in order, starting with `handler` and then the variadic `handlers`. func (app *App) Add(methods []string, path string, handler any, handlers ...any) Router // All registers the route on every HTTP method at the EXACT path // (unlike Use, which is prefix-matched). func (app *App) All(path string, handler any, handlers ...any) Router ``` ```go title="Examples" // Simple GET handler app.Get("/api/list", func(c fiber.Ctx) error { return c.SendString("I'm a GET request!") }) // Simple POST handler app.Post("/api/register", func(c fiber.Ctx) error { return c.SendString("I'm a POST request!") }) ``` Here is a complete, runnable app for context: ```go title="A minimal Fiber app" package main import "github.com/gofiber/fiber/v3" func main() { app := fiber.New() app.Get("/", func(c fiber.Ctx) error { return c.SendString("Hello, World!") }) app.Listen(":3000") } ``` In the shorter examples throughout this guide, `app` is the `*fiber.App` returned by `fiber.New()`, and `handler`/`middleware` stand in for any `func(c fiber.Ctx) error`. Snippets that call `fmt.Println` or `fmt.Fprintf` also need `import "fmt"`. Beyond the native `func(fiber.Ctx)` forms, Fiber also adapts Express-style, `net/http`, and `fasthttp` handlers. See [Handler types](#handler-types) at the end of this guide for the full list of supported shapes. ### Get vs Use vs All `Get` (and the other method helpers like `Post` and `Put`) match a **single HTTP method** at an **exact path**. `All` matches an **exact path** across **every** HTTP method. `Use` registers **middleware** that matches by **prefix** and runs in **declaration order**, calling [`c.Next()`](../api/ctx.md#next) to continue the chain. **Get (one method)** ```go app.Get("/users", func(c fiber.Ctx) error { return c.SendString("GET /users") }) // GET /users -> "GET /users" // POST /users -> 405 Method Not Allowed // GET /users/42 -> 404 Not Found (exact match only) ``` **All (every method)** ```go app.All("/ping", func(c fiber.Ctx) error { return c.SendString(c.Method() + " /ping") }) // GET /ping -> "GET /ping" // POST /ping -> "POST /ping" // DELETE /ping -> "DELETE /ping" // GET /ping/extra -> 404 Not Found (still exact path) ``` **Use (prefix middleware)** ```go // Empty Use: no path -> matches every request, any method, any path app.Use(func(c fiber.Ctx) error { c.Set("X-Powered-By", "Fiber") return c.Next() }) // Prefixed Use: matches the prefix and anything below a slash boundary app.Use("/api", func(c fiber.Ctx) error { return c.Next() }) // The empty Use above runs for ALL of these. The notes below show which // requests ALSO match the prefixed "/api" Use: // /api -> also matches "/api" Use (exact prefix) // /api/users -> also matches "/api" Use (slash boundary) // /apiv2 -> empty Use only (no slash boundary) // /anything -> empty Use only ``` **Ordered chain** Multiple handlers that match the same request run in the order you declare them. Each must call `c.Next()` to pass control to the next; if one returns without calling it, the rest of the chain is skipped. ```go app.Use("/api", func(c fiber.Ctx) error { fmt.Println("1: auth check") return c.Next() }) app.Use("/api", func(c fiber.Ctx) error { fmt.Println("2: logging") return c.Next() }) app.Get("/api/users", func(c fiber.Ctx) error { fmt.Println("3: handler") return c.SendString("users") }) // GET /api/users prints, in order: // 1: auth check // 2: logging // 3: handler ``` **Multiple handlers in one call** Attach several handlers in a single registration: list the route-specific middleware before the business handler. ```go app.Get("/users/:id", func(c fiber.Ctx) error { // 1: require authentication if c.Get("Authorization") == "" { return c.SendStatus(fiber.StatusUnauthorized) // returns without c.Next(): stops here } return c.Next() }, func(c fiber.Ctx) error { // 2: stash data for downstream handlers c.Locals("userID", c.Params("id")) return c.Next() }, func(c fiber.Ctx) error { // 3: business handler reads the stashed value return c.SendString("user " + c.Locals("userID").(string)) }, ) // GET /users/42 (no Authorization header) -> 401, handlers 2 and 3 never run // GET /users/42 (with Authorization) -> "user 42" ``` | Helper | Methods matched | Path matching | Typical use | | -------------- | --------------- | ------------------------------------------ | ----------------------------- | | `Get`/`Post`/… | one | exact | a specific endpoint | | `All` | every method | exact | one path, any verb | | `Use` | every method | prefix (slash boundary); all paths if none given | middleware, mounting sub-apps | A path that exists only for a different method returns **405 Method Not Allowed** with an `Allow` header that lists the methods it does answer; a path that matches no route at all (including one rejected by a [constraint](#constraints)) returns **404 Not Found**. Middleware registered with `Use` runs for these requests too, unless [`SkipUnmatchedRoutes`](../api/fiber.md#skipunmatchedroutes) is enabled, which answers them before the middleware chain runs, except for CORS preflight requests, which still go through it. ### Paths A route path paired with an HTTP method defines an endpoint. It can be a plain **string** or a **pattern**. ```go // This route path will match requests to the root route, "/": app.Get("/", func(c fiber.Ctx) error { return c.SendString("root") }) // This route path will match requests to "/about": app.Get("/about", func(c fiber.Ctx) error { return c.SendString("about") }) // This route path will match requests to "/random.txt": app.Get("/random.txt", func(c fiber.Ctx) error { return c.SendString("random.txt") }) ``` The order in which you declare routes matters: like Express.js, routes are matched in registration order (first match wins), so declare more specific paths before those that contain parameters. Note that method helpers such as `Get` match the exact path only. :::info Place routes with variable parameters after fixed paths to avoid unintended matches. ::: By default the router ignores letter case and a trailing slash, so a route registered as `/about` also answers `/About` and `/about/`, and parameter values keep the case the client sent. Set [`CaseSensitive`](../api/fiber.md#casesensitive) and [`StrictRouting`](../api/fiber.md#strictrouting) in `fiber.Config` to tell these spellings apart. #### Path normalization Before a request is matched, Fiber normalizes its path as [RFC 3986 Section 6.2.2](https://www.rfc-editor.org/rfc/rfc3986#section-6.2.2) describes: - Percent-encoded unreserved characters (letters, digits, `-`, `.`, `_` and `~`) are decoded, so `/%70rivate` is matched as `/private`. Every other escape stays encoded with uppercase hex digits, so an encoded slash (`%2F`) never splits a segment and `/a%2fb` is matched as `/a%2Fb`. A stray `%` that begins no escape is kept as sent, so `/a%zzb` is matched as `/a%zzb` and `/files/./100%` as `/files/100%`, whatever else the path holds. The one exception is a stray `%` that an escape decoded after it would complete into a new escape: it is written as `%25`, so `/%%370rivate` is matched as `/%2570rivate`, never as `/%70rivate`. With [`UnescapePath`](../api/fiber.md#config) enabled, every escape is decoded instead and a malformed escape stays as sent. - `.` and `..` segments are resolved, so `/static/x/../private` and `/static/./private` are both matched as `/static/private`. A `..` never climbs above the root. Empty segments are kept: `/static//private` is a different path from `/static/private`. Middleware mounted on a prefix therefore sees these spellings of a path under it, and [`c.Path()`](../api/ctx.md#path) returns the normalized path. Decoding happens exactly once: `/%2570rivate` is matched as `/%2570rivate` by default and as `/%70rivate` with `UnescapePath`, and neither becomes `/private`. Nor can a stray `%` forge an escape out of the bytes after it, so by default `c.Path()` never holds an escape of an unreserved character: the router decoded the genuine ones, and `/%%370rivate` is matched as `/%2570rivate`, never as `/%70rivate` or `/private`. Since that `%25` appears only next to a decoded escape, a route parameter holding a stray `%` comes out the same however the rest of the path is spelled: `/files/100%` and `/files/./100%` both give `c.Params("name")` the value `100%`. It is still not a path to percent-decode again: an escape the router kept, such as `%2F`, is meant to stay one, and decoding it would make `/private%2Fsecret` into `/private/secret`. [`c.OriginalURL()`](../api/ctx.md#originalurl) still returns the request target as the client sent it. ### Parameters Route parameters are dynamic segments in a path, either named or unnamed, used to capture values from the URL. Retrieve them with the [Params](../api/ctx.md#params) function using the parameter name or, for unnamed parameters, the wildcard (`*`) or plus (`+`) symbol with an index. The characters `:`, `+`, and `*` introduce parameters. Append `?` to a named parameter to make it optional. `+` is a greedy, required wildcard (it must match at least one character); `*` is a greedy, optional wildcard (it can match nothing). An optional parameter makes only its value optional, never the text around it: literal characters after it must still be present. `/user/:name?/books` matches `/user/john/books` and `/user//books`, but not `/user/books`, so register a second route for the shorter form. At the end of a path the slash before the parameter can be left out as well: `/user/:name?` matches `/user`, `/user/` and `/user/john`. An optional parameter followed by a single character stays within its path segment: `/file/:name?.:ext` matches `/file/b.txt` but not `/file/a/b.txt`. If an optional parameter sits directly before another parameter and the value it consumed makes the rest of the pattern fail, Fiber retries it as empty: `/:a?:b` matches `/x` with `a` empty and `b` set to `x`. Matching allows at most 16 such retries per route to bound the work for adjacent optional parameters. A route that needs more retries does not match, even if a valid assignment exists. Greedy wildcards do not participate in these retries, and a value that fails its constraint is not retried as empty. **Named, optional, greedy** ```go // Named parameters app.Get("/user/:name/books/:title", func(c fiber.Ctx) error { fmt.Fprintf(c, "%s\n", c.Params("name")) fmt.Fprintf(c, "%s\n", c.Params("title")) return nil }) // Plus - greedy, required (matches at least one character) app.Get("/user/+", func(c fiber.Ctx) error { return c.SendString(c.Params("+")) }) // Optional named parameter app.Get("/user/:name?", func(c fiber.Ctx) error { return c.SendString(c.Params("name")) }) // Wildcard - greedy, optional (may match nothing) app.Get("/user/*", func(c fiber.Ctx) error { return c.SendString(c.Params("*")) }) ``` **Literal separators** The hyphen (`-`), dot (`.`), and colon (`:`) are treated literally between parameters, so you can combine them with route parameters. Fiber's router detects when these characters belong to the literal path. ```go // http://localhost:3000/plantae/prunus.persica app.Get("/plantae/:genus.:species", func(c fiber.Ctx) error { fmt.Fprintf(c, "%s.%s\n", c.Params("genus"), c.Params("species")) return nil // prunus.persica }) // http://localhost:3000/flights/LAX-SFO app.Get("/flights/:from-:to", func(c fiber.Ctx) error { fmt.Fprintf(c, "%s-%s\n", c.Params("from"), c.Params("to")) return nil // LAX-SFO }) // http://localhost:3000/shop/product/color:blue/size:xs app.Get("/shop/product/color::color/size::size", func(c fiber.Ctx) error { fmt.Fprintf(c, "%s:%s\n", c.Params("color"), c.Params("size")) return nil // blue:xs }) ``` **Escaped characters** Escape special parameter characters with `\\` to treat them literally. This is useful for custom methods like those in the [Google API Design Guide](https://cloud.google.com/apis/design/custom_methods). Wrap routes in backticks to keep escape sequences clear. ```go // Matches "/v1/some/resource/name:customVerb" because the colon is escaped app.Get(`/v1/some/resource/name\:customVerb`, func(c fiber.Ctx) error { return c.SendString("Hello, Community") }) // Matches only the literal path "/*"; an escaped star is not the catch-all route app.Get(`/\*`, func(c fiber.Ctx) error { return c.SendString("literal star") }) ``` **Multiple params per segment** You can chain multiple named or unnamed parameters, including wildcard and plus segments, within a single segment. ```go // GET /@v1 // Params: "sign" -> "@", "param" -> "v1" app.Get("/:sign:param", handler) // GET /api-v1 // Params: "name" -> "v1" app.Get("/api-:name", handler) // GET /customer/v1/cart/proxy // Params: "*1" -> "customer/", "*2" -> "/cart" app.Get("/*v1*/proxy", handler) // GET /v1/brand/4/shop/blue/xs // Params: "*1" -> "brand/4", "*2" -> "blue/xs" app.Get("/v1/*/shop/*", handler) ``` :::info Fiber lets multiple parameters share a single path segment, unlike routers such as Express, Gin, and Echo where `:param` always consumes a whole segment. When named parameters are adjacent **with no literal separator between them**, the router cannot know where one parameter ends and the next begins: there is no delimiter in the pattern to split on. To keep matching deterministic, Fiber assigns every adjacent named parameter except the last a fixed length of **one character**, and the **last** parameter takes the remainder of the segment (up to the next delimiter or end of path). This is why `/:sign:param` against `/@v1` yields `sign=@` and `param=v1`, not `sign=@v` and `param=1`. If you need a multi-character split, put a literal delimiter between parameters, for example `/:sign-:param` matching `/@v-1`. ::: When a route has several wildcard (`*`) or plus (`+`) segments, retrieve them positionally with a 1-based index matching the symbol: `c.Params("*1")` and `c.Params("*2")` for wildcards, `c.Params("+1")` and `c.Params("+2")` for plus segments. A single wildcard or plus is just `c.Params("*")` or `c.Params("+")`. Fiber's routing is inspired by Express but intentionally omits regex route patterns due to their performance cost. To validate a parameter against a regular expression, use the [`regex()` constraint](#constraints) described below. #### Constraints Route constraints execute when a match has occurred to the incoming URL and the URL path is tokenized into route values by parameters. The feature was introduced in `v2.37.0` and inspired by [.NET Core](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-6.0#route-constraints). :::caution Constraints are matching rules, not input validation: if a value fails a constraint, the route simply does not match and Fiber returns **404 Not Found**. ::: | Constraint | Example | Example matches | | ----------------- | -------------------------------- | ------------------------------------------------------------------------------------------- | | int | `:id` | 123456789, -123456789 | | bool | `:active` | true, false (and the other values `strconv.ParseBool` reads, such as 1 and 0) | | guid | `:id` | CD2C1638-1638-72D5-1638-DEADBEEF1638 | | float | `:weight` | 1.234, -1001.01e8, 3.14 | | minLen(value) | `:username` | Test (must be at least 4 bytes) | | maxLen(value) | `:filename` | MyFile (must be no more than 8 bytes) | | len(length) | `:filename` | somefile.txt (exactly 12 bytes) | | betweenLen(min,max) | `:name` | MyFile (must be at least 3 and no more than 8 bytes) | | min(value) | `:age` | 19 (Integer value must be at least 18) | | max(value) | `:age` | 91 (Integer value must be no more than 120) | | range(min,max) | `:age` | 91 (Integer value must be at least 18 but no more than 120) | | alpha | `:name` | Rick (String must consist of one or more letters: a-z in either case, and other Unicode letters when the path reaches the router as UTF-8, which a percent-encoded path does only with [`UnescapePath`](../api/fiber.md#unescapepath) enabled) | | datetime | `:dob` | 2005-11-01 | | regex(expression) | `:date` | 2022-08-27 (Must match regular expression) | The length constraints (`minLen`, `maxLen`, `len` and `betweenLen`) count bytes, not characters, of the value the router matched: a non-ASCII character counts as its UTF-8 bytes, or as its longer `%XX` form while the path keeps it percent-encoded, which is the default (see [`UnescapePath`](../api/fiber.md#unescapepath)). Constraint names and arguments are always taken from the pattern as written, even with the default case-insensitive routing: `:code` keeps matching uppercase codes only, a `datetime` layout keeps its `T` and `Z`, and a custom constraint registered under a mixed-case name is found. A built-in name matches in any letter case (`minLen(4)` and `MinLen(4)` are the same constraint), while a custom constraint name must match exactly. :::note A constraint with a missing or malformed argument (`:id` or `:id`) never matches, so the route answers **404** for every request. A name that is neither built in nor a registered [custom constraint](#custom-constraint) is ignored and leaves the parameter unconstrained, so a misspelled constraint does not reject anything. Only `regex(...)` expressions are checked when the route is registered: an invalid one panics. ::: ##### Examples **Single Constraint** ```go app.Get("/:test", func(c fiber.Ctx) error { return c.SendString(c.Params("test")) }) // curl -X GET http://localhost:3000/12 // 12 // curl -X GET http://localhost:3000/1 // Not Found ``` **Multiple Constraints** You can use `;` for multiple constraints. ```go app.Get("/:test", func(c fiber.Ctx) error { return c.SendString(c.Params("test")) }) // curl -X GET http://localhost:3000/120000 // Not Found // curl -X GET http://localhost:3000/1 // Not Found // curl -X GET http://localhost:3000/250 // 250 ``` **Regex Constraint** Fiber precompiles the regex when registering routes, so the pattern is matched (not recompiled) on each request. To compile patterns with another regex engine, set [`RegexHandler`](../api/fiber.md#regexhandler); see [Make Fiber Faster](./faster-fiber.md#alternative-regex-engines-for-regex-constraints). ```go app.Get(`/:date`, func(c fiber.Ctx) error { return c.SendString(c.Params("date")) }) // curl -X GET http://localhost:3000/125 // Not Found // curl -X GET http://localhost:3000/test // Not Found // curl -X GET http://localhost:3000/2022-08-27 // 2022-08-27 ``` :::caution When using the datetime constraint, prefix routing characters (`*`, `+`, `?`, `:`, `/`, `<`, `>`, `;`, `(`, `)`) with `\\` to avoid misparsing. ::: ##### Optional Parameter Example You can impose constraints on optional parameters as well. ```go app.Get("/:test?", func(c fiber.Ctx) error { return c.SendString(c.Params("test")) }) // curl -X GET http://localhost:3000/42 // 42 // curl -X GET http://localhost:3000/ // // curl -X GET http://localhost:3000/7.0 // Not Found ``` ##### Custom Constraint Custom constraints can be added to Fiber using the `app.RegisterCustomConstraint` method. Your constraints have to be compatible with the `CustomConstraint` interface. :::caution Attention, custom constraints can now override built-in constraints. If a custom constraint has the same name as a built-in constraint, the custom constraint will be used instead. This allows for more flexibility in defining route parameter constraints. The built-in names are available as constants, for example `fiber.ConstraintInt`, `fiber.ConstraintMinLen` and `fiber.ConstraintRegex`. ::: Add external constraints when you need stricter rules, such as verifying that a parameter is a valid ULID. Register a custom constraint before the routes that use it: a route is parsed when it is registered, so a route registered earlier is left without the constraint. Custom constraints registered on a sub-app keep applying to its routes once the sub-app is mounted, and the app that mounts it can use its own constraints in the mount prefix. A parameter in the prefix of a plain mount routes as usual, but the sub-app's own `ErrorHandler` and `Views` are found only under a literal prefix (see [Mounting](../api/app.md#mounting)). ```go // CustomConstraint is an interface for custom constraints type CustomConstraint interface { // Name returns the name of the constraint. // This name is used in the constraint matching. Name() string // Execute executes the constraint. // It returns true if the constraint is matched and right. // param is the parameter value to check. // args are the constraint arguments. Execute(param string, args ...string) bool } ``` You can check the example below: ```go type UlidConstraint struct { fiber.CustomConstraint } func (*UlidConstraint) Name() string { return "ulid" } func (*UlidConstraint) Execute(param string, args ...string) bool { _, err := ulid.Parse(param) return err == nil } func main() { app := fiber.New() app.RegisterCustomConstraint(&UlidConstraint{}) app.Get("/login/:id", func(c fiber.Ctx) error { return c.SendString("...") }) app.Listen(":3000") // /login/01HK7H9ZE5BFMK348CPYP14S0Z -> 200 // /login/12345 -> 404 } ``` ##### ConstraintHandler Interface Behind the scenes, Fiber evaluates every constraint through the `ConstraintHandler` interface, which separates the work done once at route registration from the check made on each request. All built-in constraints implement it. ```go // ConstraintHandler is the interface that all constraints must implement. type ConstraintHandler interface { // Name returns the constraint identifier used in route patterns. Name() string // Execute validates a request parameter value against the constraint. // data contains the pre-typed constraint data produced by Analyze(). Execute(param string, data []any) bool } ``` Built-in constraints that take arguments (`min`, `range`, `datetime`, `regex` and so on) also implement `ConstraintAnalyzer`, which parses the arguments once at registration time and avoids repeated parsing on every request: ```go // ConstraintAnalyzer is an optional interface for registration-time precomputation. type ConstraintAnalyzer interface { // Analyze preprocesses constraint data at route registration time. // Returns pre-typed values that will be stored in Constraint.Data. Analyze(args []string) ([]any, error) } ``` :::note `app.RegisterCustomConstraint` accepts `CustomConstraint` values only, so a `ConstraintHandler` cannot be registered directly. Fiber wraps each `CustomConstraint` in a `ConstraintHandler` and passes the arguments from the pattern to its `Execute` method as strings, split on unescaped commas. If your custom constraint also implements `ConstraintAnalyzer`, Fiber calls its `Analyze` method with those arguments when the route is registered, but the values it returns are not handed to `Execute`. ::: :::tip Try it live Test route patterns, registration order, and constraints against real requests in the interactive [Route Matcher](../extra/route-matcher.md) tool. ::: ### Middleware Functions that are designed to make changes to the request or response are called **middleware functions**. [`c.Next()`](../api/ctx.md#next) passes control to the next handler in the matched chain (middleware or route handler); if a handler returns without calling it, the remaining handlers are skipped. ```go title="Example of a middleware function" app.Use(func(c fiber.Ctx) error { // Set a custom header on all responses: c.Set("X-Custom-Header", "Hello, World") // Go to next middleware: return c.Next() }) app.Get("/", func(c fiber.Ctx) error { return c.SendString("Hello, World!") }) ``` Once a route matches, its handlers run as one chain in registration order; each handler decides with `c.Next()` whether the rest of the chain runs. Step through the chain yourself, including what happens when a middleware short-circuits instead of calling `c.Next()`: See [Get vs Use vs All](#get-vs-use-vs-all) for how `Use` prefix matching differs from exact route matching, and how multiple handlers run in order. #### Use `Use` mounts middleware on a **prefix** (or **mount**) path: it runs for every request whose path begins with that prefix, on any HTTP method. Prefixes require either an exact match or a slash boundary, so `/john` matches `/john` and `/john/doe` but not `/johnnnnn`. Parameter tokens like `:name`, `:name?`, `*`, and `+` are still expanded before the boundary check runs. Called without a path, `Use` matches every request. ```go title="Signature" func (app *App) Use(args ...any) Router // Fiber inspects args to support these common usage patterns: // - app.Use(handler, handlers ...any) // - app.Use(path string, handler, handlers ...any) // - app.Use(paths []string, handler, handlers ...any) // - app.Use(path string, subApp *App) // - app.Use(paths []string, subApp *App) ``` Each handler argument can independently be a Fiber handler (with or without an `error` return), an Express-style callback, a `net/http` handler, or any other supported shape including fasthttp callbacks that return errors. ```go title="Examples" // Match any request app.Use(func(c fiber.Ctx) error { return c.Next() }) // Match request starting with /api app.Use("/api", func(c fiber.Ctx) error { return c.Next() }) // Match requests starting with /api or /home (multiple-prefix support) app.Use([]string{"/api", "/home"}, func(c fiber.Ctx) error { return c.Next() }) // Attach multiple handlers (they run in order; each must call c.Next() to continue) app.Use("/api", func(c fiber.Ctx) error { c.Set("X-Custom-Header", "value") return c.Next() }, func(c fiber.Ctx) error { return c.Next() }) // Mount a sub-app app.Use("/api", api) ``` #### Adding or removing routes at runtime :::caution Defining all routes before the app starts is strongly recommended. You can still change them at runtime with [`RebuildTree`](../api/app.md#rebuildtree), [`RemoveRoute`](../api/app.md#removeroute), [`RemoveRouteByName`](../api/app.md#removeroutebyname), and [`RemoveRouteFunc`](../api/app.md#removeroutefunc), but these operations are not thread-safe and are performance-intensive, so use them sparingly and only in development. ::: ### Grouping If you have many endpoints, you can organize your routes using `Group`. ```go func main() { app := fiber.New() api := app.Group("/api", middleware) // /api v1 := api.Group("/v1", middleware) // /api/v1 v1.Get("/list", handler) // /api/v1/list v1.Get("/user", handler) // /api/v1/user v2 := api.Group("/v2", middleware) // /api/v2 v2.Get("/list", handler) // /api/v2/list v2.Get("/user", handler) // /api/v2/user log.Fatal(app.Listen(":3000")) } ``` More information about this in our [Grouping Guide](./grouping.md). #### Route [`Route`](../api/app.md#route) is shorthand for [`Group`](#grouping): it scopes a set of routes under a common prefix declared inside a single callback, with an optional name prefix. ```go app.Route("/api/v1", func(r fiber.Router) { r.Get("/users", handler).Name("users") // /api/v1/users (name: v1.users) r.Post("/users", handler).Name("create") // /api/v1/users (name: v1.create) }, "v1.") ``` #### RouteChain When several HTTP methods share the **same path**, [`RouteChain`](../api/app.md#routechain) lets you declare the path once and chain the verb handlers. Handlers run in the order you declare them, so an `All` declared before the verb handlers runs ahead of them on that path, acting as route-specific middleware. ```go app.RouteChain("/events"). All(func(c fiber.Ctx) error { return c.Next() }). // route-local middleware Get(func(c fiber.Ctx) error { return c.SendString("GET /events") }). Post(func(c fiber.Ctx) error { return c.SendString("POST /events") }) ``` :::note Within a chain, `All` registers prefix-matched middleware (like [`Use`](#use)), not the exact-path `App.All`, so it also runs for sub-paths of the chain path. ::: Pick the helper that fits: a single endpoint uses `Get`/`Post`/…; a fixed set of methods on one path uses [`Add`](#route-handlers); one path with many methods (fluently) uses `RouteChain`; many paths under a shared prefix use [`Group`](#grouping) or `Route`; routes that must answer only for certain hostnames use [`Domain`](../api/app.md#domain). ### Automatic HEAD routes Fiber automatically registers a `HEAD` route for every `GET` route you add. The generated handler chain mirrors the `GET` chain, so `HEAD` requests reuse middleware, status codes, and headers while the response body is suppressed. This covers the routes of mounted sub-apps at any nesting depth, and routes added at runtime get their `HEAD` companion when [`RebuildTree`](../api/app.md#rebuildtree) publishes them. ```go title="GET handlers automatically expose HEAD" app := fiber.New() app.Get("/users/:id", func(c fiber.Ctx) error { c.Set("X-User", c.Params("id")) return c.SendStatus(fiber.StatusOK) }) // HEAD /users/:id now returns the same headers and status without a body. ``` You can still register dedicated `HEAD` handlers, even with auto-registration enabled, and Fiber replaces the generated route so your implementation wins: ```go title="Override the generated HEAD handler" app.Head("/users/:id", func(c fiber.Ctx) error { return c.SendStatus(fiber.StatusNoContent) }) ``` To opt out globally, start the app with `DisableHeadAutoRegister`: ```go title="Disable automatic HEAD registration" handler := func(c fiber.Ctx) error { c.Set("X-User", c.Params("id")) return c.SendStatus(fiber.StatusOK) } app := fiber.New(fiber.Config{DisableHeadAutoRegister: true}) app.Get("/users/:id", handler) // HEAD /users/:id now returns 405 unless you add it manually. ``` Each app decides for its own routes. A mounted sub-app that sets `DisableHeadAutoRegister`, or that leaves `HEAD` out of its `RequestMethods`, gets no automatic `HEAD` routes when it is mounted, so a `HEAD` request for its routes answers **405** even though the app it is mounted in registers them for its own routes. A sub-app that does not serve `HEAD` registers its middleware only for the methods it serves, so a synthesized `HEAD` route would run its handlers without that middleware. A sub-app mounted on a [`Domain`](../api/app.md#domain) follows the same rule. Auto-generated `HEAD` routes participate in every router scope, including `Group` hierarchies, mounted sub-apps, parameterized and wildcard paths, and static file helpers. They also appear in route listings such as `app.Stack()` so tooling sees both the `GET` and `HEAD` entries. ### Handler types Fiber's adapter converts a variety of handler shapes into native `func(fiber.Ctx) error` callbacks. The 17 supported shapes are grouped below; any other signature, and a `nil` handler, makes registering the route panic. This lets you mix Fiber-style handlers with Express-style callbacks and even reuse `net/http` or `fasthttp` functions. #### Fiber-native handlers (cases 1-2) - **Case 1.** `fiber.Handler` - the canonical `func(fiber.Ctx) error` form. - **Case 2.** `func(fiber.Ctx)` - Fiber runs the function and treats it as if it returned `nil`. #### Express-style request handlers (cases 3-12) - **Case 3.** `func(fiber.Req, fiber.Res) error` - **Case 4.** `func(fiber.Req, fiber.Res)` - **Case 5.** `func(fiber.Req, fiber.Res, func() error) error` - **Case 6.** `func(fiber.Req, fiber.Res, func() error)` - **Case 7.** `func(fiber.Req, fiber.Res, func()) error` - **Case 8.** `func(fiber.Req, fiber.Res, func())` - **Case 9.** `func(fiber.Req, fiber.Res, func(error))` - **Case 10.** `func(fiber.Req, fiber.Res, func(error)) error` - **Case 11.** `func(fiber.Req, fiber.Res, func(error) error)` - **Case 12.** `func(fiber.Req, fiber.Res, func(error) error) error` The adapter injects a `next` callback when your signature accepts one. Fiber propagates downstream errors from `c.Next()` back through the wrapper, so returning those errors remains optional. If you never call the injected `next` function, the handler chain stops, matching Express semantics. When you accept `next` callbacks that take an `error`, calling `next(nil)` continues the chain and passing a non-nil error short-circuits with that error. If the handler itself returns an error, Fiber prioritizes that value over any recorded `next` error. Fiber has no Express-style four-argument error handler (`func(err, req, res, next)`); a non-nil error propagates to the app's central `ErrorHandler` instead. #### net/http handlers (cases 13-15) - **Case 13.** `http.HandlerFunc` - **Case 14.** `http.Handler` - **Case 15.** `func(http.ResponseWriter, *http.Request)` :::caution Compatibility overhead Fiber adapts these handlers through `fasthttpadaptor`. They do not receive `fiber.Ctx`, cannot call `c.Next()`, and therefore always terminate the handler chain. The compatibility layer also adds more overhead than running a native Fiber handler, so prefer the other forms when possible. ::: #### fasthttp handlers (cases 16-17) - **Case 16.** `fasthttp.RequestHandler` - the named fasthttp type; a plain `func(*fasthttp.RequestCtx)` has to be converted to it first. - **Case 17.** `func(*fasthttp.RequestCtx) error` fasthttp handlers run with full access to the underlying `fasthttp.RequestCtx`. They are expected to manage the response directly. Fiber will propagate any error returned by the `func(*fasthttp.RequestCtx) error` variant but otherwise does not inspect the context state. ```go title="Examples" // Reuse an existing net/http handler without manual adaptation httpHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) }) app.Get("/foo", httpHandler) // Align with Express-style handlers using fiber.Req and fiber.Res helpers (works // for middleware and routes alike) 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() }) app.Get("/express", func(req fiber.Req, res fiber.Res) error { return res.SendString("Hello from Express-style handlers!") }) // Mount a fasthttp.RequestHandler directly (case 16). A bare function // literal is not a fasthttp.RequestHandler, so Fiber rejects it: declare the // variable with that type, or convert the literal. var bar fasthttp.RequestHandler = func(ctx *fasthttp.RequestCtx) { ctx.SetStatusCode(fiber.StatusAccepted) } app.Get("/bar", bar) // ...or the error-returning variant (case 17) app.Get("/baz", func(ctx *fasthttp.RequestCtx) error { ctx.SetStatusCode(fiber.StatusAccepted) return nil }) ``` --- ## 📝 Templates Templates render dynamic content without requiring a separate frontend framework. ### Template Engines Fiber accepts a custom template engine during app initialization. ```go app := fiber.New(fiber.Config{ // Provide a template engine Views: engine, // Default layout for views, overridden by the layouts passed to Render() ViewsLayout: "layouts/main", // Enables/Disables access to `ctx.Locals()` entries in rendered views // (defaults to false) PassLocalsToViews: false, }) ``` #### Supported Engines Fiber maintains a [templates](https://docs.gofiber.io/template) package that wraps several engines: * [ace](https://docs.gofiber.io/template/ace/) * [amber](https://docs.gofiber.io/template/amber/) * [django](https://docs.gofiber.io/template/django/) * [handlebars](https://docs.gofiber.io/template/handlebars) * [html](https://docs.gofiber.io/template/html) * [jet](https://docs.gofiber.io/template/jet) * [mustache](https://docs.gofiber.io/template/mustache) * [pug](https://docs.gofiber.io/template/pug) * [slim](https://docs.gofiber.io/template/slim) :::info Custom engines implement the `Views` interface to work with Fiber. ::: ```go title="Views interface" type Views interface { // Fiber executes Load() in fiber.New() to load/parse the templates; // an error is logged as a warning, not returned Load() error // Outputs a template to the provided buffer using the provided template, // template name, and bound data Render(io.Writer, string, any, ...string) error } ``` :::note The `Render` method powers [**ctx.Render\(\)**](../api/ctx.md#render), which accepts a template name and data to bind. ::: ### Rendering Templates After configuring an engine, handlers call [**ctx.Render\(\)**](../api/ctx.md#render) with a template name and data to send the rendered output. ```go title="Signature" func (c fiber.Ctx) Render(name string, bind any, layouts ...string) error ``` :::info By default, [**ctx.Render\(\)**](../api/ctx.md#render) wraps the template in the layout named by `ViewsLayout`. Pass layout names in the `layouts` argument to use another layout for that call. ::: Without a `Views` engine, or with a nil engine pointer, `Render` reads the file at `name` and parses it with Go's `html/template`. **Example** ```go app.Get("/", func(c fiber.Ctx) error { return c.Render("index", fiber.Map{ "Title": "Hello, World!", }) }) ``` **layouts/index.html** ```html

{{.Title}}

``` :::caution When `PassLocalsToViews` is enabled, all values set using `ctx.Locals(key, value)` are passed to the template. Use unique keys to avoid collisions. ::: Locals, and the values you add with [`c.ViewBind`](../api/ctx.md#viewbind), are merged into the data only when you pass a `fiber.Map` or `nil` to `Render`. A key in the map you pass wins over a local or a `ViewBind` value with the same name. #### Rendering Without a Request [`app.Render`](../api/app.md#render) writes a template through the configured engine to any `io.Writer`, applying `ViewsLayout` when you pass no layout. [`app.ReloadViews`](../api/app.md#reloadviews) runs `Load` again on the engine of the app and of every mounted sub-app, for example after a template changed on disk, and returns `fiber.ErrNoViewEngineConfigured` only when none of them has an engine. `app.Render` returns it when the app itself has none. A nil engine pointer counts as none for both. #### Mounted Apps A sub-app mounted with [`app.Use`](../api/app.md#mounting) renders its templates with its own `Views` for requests under its mount path. A sub-app without an engine falls back to the nearest app above it that has one. The default layout is the `ViewsLayout` of the innermost app that sets one, and the search stops at the app whose engine renders the template. This needs a literal mount path, taken from the top-level app down: under a parameter, as in `app.Use("/t/:tenant", tenantApp)`, the routes still match and run but the `Views` and `ViewsLayout` of the sub-app, and of the apps mounted inside it, are not used. The nearest enclosing mount with a literal path supplies them, or the top-level app when there is none, and without an engine anywhere `Render` reads `name` as a file path. A sub-app mounted through [`Domain`](../api/app.md#domain) matches a parameterized mount path as a pattern and keeps its own `Views`. `PassLocalsToViews` is read from the top-level app, whatever the sub-app sets. ### Advanced Templating #### Custom Functions Fiber supports adding custom functions to templates. ##### AddFunc Adds a global function to all templates. ```go title="Signature" func (e *Engine) AddFunc(name string, fn any) IEngineCore ``` **AddFunc Example** ```go // Add `ToUpper` to engine engine := html.New("./views", ".html") engine.AddFunc("ToUpper", func(s string) string { return strings.ToUpper(s) }) // Initialize Fiber App app := fiber.New(fiber.Config{ Views: engine, }) app.Get("/", func (c fiber.Ctx) error { return c.Render("index", fiber.Map{ "Content": "hello, World!", }) }) ``` **views/index.html** ```html

This will be in {{ToUpper "all caps"}}:

{{ToUpper .Content}}

``` ##### AddFuncMap Adds a Map of functions (keyed by name) to all templates. ```go title="Signature" func (e *Engine) AddFuncMap(m map[string]any) IEngineCore ``` **AddFuncMap Example** ```go // Add `ToUpper` to engine engine := html.New("./views", ".html") engine.AddFuncMap(map[string]any{ "ToUpper": func(s string) string { return strings.ToUpper(s) }, }) // Initialize Fiber App app := fiber.New(fiber.Config{ Views: engine, }) app.Get("/", func (c fiber.Ctx) error { return c.Render("index", fiber.Map{ "Content": "hello, world!", }) }) ``` **views/index.html** ```html

This will be in {{ToUpper "all caps"}}:

{{ToUpper .Content}}

``` * For more advanced template documentation, please visit the [gofiber/template GitHub Repository](https://github.com/gofiber/template). ### Full Example **Example** ```go package main import ( "log" "github.com/gofiber/fiber/v3" "github.com/gofiber/template/html/v2" ) func main() { // Initialize standard Go html template engine engine := html.New("./views", ".html") // If you want to use another engine, // just replace with following: // Create a new engine with django // engine := django.New("./views", ".django") app := fiber.New(fiber.Config{ Views: engine, }) app.Get("/", func(c fiber.Ctx) error { // Render index template return c.Render("index", fiber.Map{ "Title": "Go Fiber Template Example", "Description": "An example template", "Greeting": "Hello, World!", }); }) log.Fatal(app.Listen(":3000")) } ``` **views/index.html** ```html {{.Title}}

{{.Title}}

{{.Greeting}}

``` --- ## 🧰 Utils ### Generics #### Convert Converts a string to a specific type while handling errors and optional defaults. It wraps conversion and fallback logic to keep your code clean and consistent. ```go title="Signature" func Convert[T any](value string, converter func(string) (T, error), defaultValue ...T) (T, error) ``` ```go title="Example" // GET http://example.com/id/bb70ab33-d455-4a03-8d78-d3c1dacae9ff app.Get("/id/:id", func(c fiber.Ctx) error { fiber.Convert(c.Params("id"), uuid.Parse) // UUID(bb70ab33-d455-4a03-8d78-d3c1dacae9ff), nil }) // GET http://example.com/search?page=3&id=not-a-uuid app.Get("/search", func(c fiber.Ctx) error { fiber.Convert(c.Query("page"), strconv.Atoi) // 3, nil fiber.Convert(c.Query("id"), uuid.Parse) // uuid.Nil, error(failed to convert: ...) fiber.Convert(c.Query("id"), uuid.Parse, uuid.Nil) // uuid.Nil, nil: the default replaces the error return nil }) // ... ``` #### GetReqHeader Retrieves an HTTP request header as a specific type using generics. ```go title="Signature" func GetReqHeader[V GenericType](c Ctx, key string, defaultValue ...V) V ``` ```go title="Example" app.Get("/search", func(c fiber.Ctx) error { // curl -X GET http://example.com/search -H "X-Request-ID: 12345" -H "X-Request-Name: John" fiber.GetReqHeader[int](c, "X-Request-ID") // => returns 12345 as integer. fiber.GetReqHeader[string](c, "X-Request-Name") // => returns "John" as string. fiber.GetReqHeader[string](c, "unknownParam", "default") // => returns "default" as string. // ... }) ``` #### Locals Reads or writes local values in the request context using generics. ```go title="Signature" func Locals[V any](c Ctx, key any, value ...V) V ``` Pass a value to store it for the rest of the request, or omit it to read the stored value back. A missing key, or a value of another type, gives the zero value of `V`. ```go title="Example" app.Use("/user/:user/:id", func(c fiber.Ctx) error { // set local values fiber.Locals[string](c, "user", "john") fiber.Locals[int](c, "id", 25) // ... return c.Next() }) app.Get("/user/*", func(c fiber.Ctx) error { // get local values name := fiber.Locals[string](c, "user") // john age := fiber.Locals[int](c, "id") // 25 // ... }) ``` #### Params Retrieves route parameters as a specific type. ```go title="Signature" func Params[V GenericType](c Ctx, key string, defaultValue ...V) V ``` ```go title="Example" app.Get("/user/:user/:id", func(c fiber.Ctx) error { // http://example.com/user/john/25 fiber.Params[int](c, "id") // => returns 25 as integer. fiber.Params[int](c, "unknownParam", 99) // => returns the default 99 as integer. // ... return c.SendString("Hello, " + fiber.Params[string](c, "user")) }) ``` #### Query Retrieves query parameters as a specific type. ```go title="Signature" func Query[V GenericType](c Ctx, key string, defaultValue ...V) V ``` ```go title="Example" app.Get("/search", func(c fiber.Ctx) error { // http://example.com/search?name=john&age=25 fiber.Query[string](c, "name") // => returns "john" fiber.Query[int](c, "age") // => returns 25 as integer. fiber.Query[string](c, "unknownParam", "default") // => returns "default" as string. // ... }) ``` #### RoutePatternMatch Checks whether a given path matches a Fiber route pattern. Useful for testing patterns without registering them. Patterns may contain parameters, wildcards and optional segments. An optional `Config` allows control over case sensitivity and strict routing; only `CaseSensitive`, `StrictRouting`, `UnescapePath` and `RegexHandler` are read from it. It knows no app, so a pattern that names a custom constraint is matched without that constraint. The path is normalized the way the router normalizes a request path before matching, so the answer agrees with what the app would actually do. In particular, with the default `StrictRouting: false` a trailing slash on the path is ignored, so `RoutePatternMatch("/a/", "/a")` reports `true`. Set `StrictRouting: true` if you need the two forms to be distinguished. It takes a path, not a request target. The empty string is read as `/`, and any other string without a leading slash matches no pattern, so `RoutePatternMatch("*", "/*")` reports `false`. The router instead roots a request target that has no leading slash before matching, so it routes `OPTIONS *` as `/*`. ```go title="Signature" func RoutePatternMatch(path, pattern string, cfg ...Config) bool ``` ```go title="Example" fiber.RoutePatternMatch("/user/john", "/user/:name") // true fiber.RoutePatternMatch( "/User/john", "/user/:name", fiber.Config{CaseSensitive: true}, ) // false ``` ### HTTP Methods #### IsMethodSafe Reports whether an HTTP method is safe in the sense of [RFC 9110 Section 9.2.1](https://www.rfc-editor.org/rfc/rfc9110#section-9.2.1) (`GET`, `HEAD`, `OPTIONS` and `TRACE`) and [RFC 10008](https://www.rfc-editor.org/rfc/rfc10008.html) (`QUERY`). Methods are compared exactly, so pass them in upper case, as `c.Method()` returns them. ```go title="Signature" func IsMethodSafe(m string) bool ``` #### IsMethodIdempotent Reports whether an HTTP method is idempotent in the sense of [RFC 9110 Section 9.2.2](https://www.rfc-editor.org/rfc/rfc9110#section-9.2.2): every safe method, plus `PUT` and `DELETE`. ```go title="Signature" func IsMethodIdempotent(m string) bool ``` ```go title="Example" fiber.IsMethodSafe(fiber.MethodGet) // true fiber.IsMethodSafe(fiber.MethodPost) // false fiber.IsMethodIdempotent(fiber.MethodPut) // true fiber.IsMethodIdempotent("put") // false: names are compared exactly // Log only the requests that may change state app.Use(func(c fiber.Ctx) error { if !fiber.IsMethodSafe(c.Method()) { log.Printf("%s %s", c.Method(), c.Path()) } return c.Next() }) ``` --- ## 🔎 Validation ### Validator package Fiber does not bundle a validation library. Instead, [`Bind`](../api/bind.md#validation) accepts any validator you plug into `fiber.Config.StructValidator` and runs it automatically whenever request data is bound onto a struct. The core stays dependency-free, and validation becomes a one-time setup instead of per-handler boilerplate. The steps below use [go-playground/validator](https://github.com/go-playground/validator), the most common choice in the Go ecosystem, but any library works as long as you wrap it in the small adapter from step 2. #### Step 1: Install a validator ```bash go get github.com/go-playground/validator/v10 ``` #### Step 2: Wire it into the app config `StructValidator` expects a single `Validate(out any) error` method, so wrap the library in a tiny adapter and register it once: ```go import "github.com/go-playground/validator/v10" type structValidator struct { validate *validator.Validate } // Validator needs to implement the Validate method func (v *structValidator) Validate(out any) error { return v.validate.Struct(out) } // Set up your validator in the config app := fiber.New(fiber.Config{ StructValidator: &structValidator{validate: validator.New()}, }) ``` :::note `StructValidator` runs only for struct destinations (or pointers to structs). Binding into maps and other non-struct types skips validation. ::: #### Step 3: Bind and validate in one call Tag your structs with the validator's rules. Every bind method (`Body`, `Query`, `Form`, and the rest) now triggers validation and returns its errors alongside binding errors: ```go type User struct { Name string `json:"name" form:"name" query:"name" validate:"required"` Age int `json:"age" form:"age" query:"age" validate:"gte=0,lte=100"` } app.Post("/", func(c fiber.Ctx) error { user := new(User) // Works with all bind methods: Body, Query, Form, ... if err := c.Bind().Body(user); err != nil { // validation errors are returned here return err } return c.JSON(user) }) ``` Returned as-is, a validation error surfaces through Fiber's [error handling](./error-handling.md), so you can also shape it globally in a custom error handler. It is not wrapped in a [`fiber.BindError`](../api/bind.md#binderror), which is reserved for binding failures, and a plain `error` carries no status, so the default error handler answers it with `500 Internal Server Error`. Step 4 shows how to answer with a client error instead. :::note Validation runs only after binding succeeds, on the value you passed to the bind method. When parsing fails, the validator is not called. `Bind().All` validates once, on the struct merged from all sources. To bypass the validator for part of a request, use [`SkipValidation`](../api/bind.md#skipvalidation). The setting stays in effect for the rest of the request until you turn validation back on with `SkipValidation(false)`. [`WithAutoHandling`](../api/bind.md#withautohandling) converts a validation error into `400 Bad Request` only for `Bind().All`. The other bind methods return the validator's error unchanged. ::: #### Step 4: Shape the error response For field-level feedback, unwrap `validator.ValidationErrors` and answer with a structured body instead of the default text: ```go type User struct { Name string `json:"name" validate:"required,min=3,max=32"` Email string `json:"email" validate:"required,email"` Age int `json:"age" validate:"gte=0,lte=100"` Password string `json:"password" validate:"required,min=8"` Website string `json:"website" validate:"url"` } app.Post("/user", func(c fiber.Ctx) error { user := new(User) if err := c.Bind().Body(user); err != nil { var validationErrors validator.ValidationErrors if errors.As(err, &validationErrors) { out := make([]fiber.Map, 0, len(validationErrors)) for _, e := range validationErrors { // e.Field() - field name, e.Tag() - failed rule, // e.Param() - rule parameter, e.Value() - invalid value out = append(out, fiber.Map{ "field": e.Field(), "rule": e.Tag(), }) } return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"errors": out}) } return err } return c.JSON(user) }) ``` #### Step 5: Add your own rules Because the adapter is yours, you can layer custom checks on top of the tag rules: ```go // Custom validator for password strength type PasswordValidator struct { validate *validator.Validate } func (v *PasswordValidator) Validate(out any) error { if err := v.validate.Struct(out); err != nil { return err } // Custom password validation logic if user, ok := out.(*User); ok { if len(user.Password) < 8 { return errors.New("password must be at least 8 characters") } // Add more password validation rules here } return nil } // Usage app := fiber.New(fiber.Config{ StructValidator: &PasswordValidator{validate: validator.New()}, }) ``` --- ## 👋 Welcome (Core) Welcome to Fiber's online API documentation, complete with examples to help you start building web applications right away! **Fiber** is an [Express](https://github.com/expressjs/express)-inspired **web framework** built on top of [Fasthttp](https://github.com/valyala/fasthttp), the **fastest** HTTP engine for [Go](https://go.dev/doc/). It is designed to facilitate rapid development with **zero memory allocations** and a strong focus on **performance**. Fiber also ships batteries included: built-in middleware, officially maintained integrations, storage drivers, and template engines cover most production needs (see [Explore the Ecosystem](#explore-the-ecosystem) below). These docs cover **Fiber v3**. :::tip Coming from Fiber v2? See [What's New in v3](./whats_new.md) for the migration guide and the CLI migration tool. ::: ### Installation First, [download](https://go.dev/dl/) and install Go. Version `1.26` or higher is required. Install Fiber using the [`go get`](https://pkg.go.dev/cmd/go/#hdr-Add_dependencies_to_current_module_and_install_them) command: ```bash go get github.com/gofiber/fiber/v3 ``` ### Hello, World Create a file named `server.go` with the simplest **Fiber** application you can write: ```go package main import ( "log" "github.com/gofiber/fiber/v3" ) func main() { app := fiber.New() app.Get("/", func(c fiber.Ctx) error { return c.SendString("Hello, World!") }) log.Fatal(app.Listen(":3000")) } ``` Run it: ```bash go run server.go ``` Browse to `http://localhost:3000` and you should see `Hello, World!` displayed on the page. Three calls carry the whole program: - `fiber.New()` creates the app, the central object that holds routes, middleware, and configuration - `app.Get("/", ...)` registers a handler: a function that receives the request context `c fiber.Ctx` and returns an `error` - `app.Listen(":3000")` starts the server ### Build Your First App, Step by Step The walkthrough below grows the Hello, World program into a small app with route parameters, static files, middleware, a JSON endpoint, and error handling. Step through it and fire the example requests to see exactly how the app responds at every stage: ### What Can You Build? Fiber covers the everyday shapes of web backends out of the box. Pick a use case and read the code; every tab is just complete enough to recognize your project in it: Want complete, runnable projects instead? The [Recipes collection](https://docs.gofiber.io/recipes/) has working examples for Docker, GORM, JWT auth, clean architecture, and much more. ### Basic Routing Routing determines how an application responds to a client request at a particular endpoint, a combination of path and HTTP request method (`GET`, `PUT`, `POST`, etc.). Route definitions follow the structure below: ```go // Function signature func (app *App) Get(path string, handler any, handlers ...any) Router ``` - `app` is an instance of **Fiber** - `Get` is an [HTTP request method](./api/app.md#route-handlers); `Post`, `Put`, `Delete`, and the other methods work the same way - `path` is a virtual path on the server - `handler` is a function executed when the route is matched; the canonical form is `func(fiber.Ctx) error`, and a route can register multiple handlers For an interactive breakdown of every part of a route definition, see the [anatomy of a route](./guide/routing.md#anatomy-of-a-route). A simple route and a route with a parameter: ```go // Respond with "Hello, World!" on root path "/" app.Get("/", func(c fiber.Ctx) error { return c.SendString("Hello, World!") }) ``` ```go // GET http://localhost:3000/hello%20world app.Get("/:value", func(c fiber.Ctx) error { return c.SendString("value: " + c.Params("value")) // => Response: "value: hello world" }) ``` See the [routing guide](./guide/routing.md) for optional parameters, wildcards, constraints, route groups, and the full list of supported handler types. Or skip ahead and fire requests at your own route table in the interactive [Route Matcher](./extra/route-matcher.md). ### Static Files To serve static files such as **images**, **CSS**, and **JavaScript** files, register the [static middleware](./middleware/static.md): ```go import "github.com/gofiber/fiber/v3/middleware/static" app.Use("/", static.New("./public")) ``` Files in the `./public` directory are now reachable in the browser, for example at `http://localhost:3000/css/style.css`. ### Using Middleware Middleware runs before or after your handlers and takes care of cross-cutting concerns. Registering one is a single `app.Use` call; here is the [logger](./middleware/logger.md) middleware printing every request: ```go import "github.com/gofiber/fiber/v3/middleware/logger" app.Use(logger.New()) ``` Middleware for logging, CORS, rate limiting, sessions, compression, panic [recovery](./middleware/recover.md), and much more ships with Fiber itself; the ecosystem section below shows where to find it all. ### Zero Allocation :::caution Fiber is optimized for **high performance**, so values returned from **fiber.Ctx** are **not** immutable by default and **will** be reused across requests. Use context values only within the handler, and do not keep any references after the handler returns. ::: If you need to persist a context value beyond the handler, make a copy of its **underlying buffer** using the [copy](https://pkg.go.dev/builtin/#copy) builtin: ```go func handler(c fiber.Ctx) error { // Variable is only valid within this handler result := c.Params("foo") // Make a copy buffer := make([]byte, len(result)) copy(buffer, result) resultCopy := string(buffer) // Variable is now valid indefinitely // ... } ``` Alternatively, you can enable the `Immutable` setting. This makes all values returned from the context immutable, allowing you to persist them anywhere, at the cost of some performance: ```go app := fiber.New(fiber.Config{ Immutable: true, }) ``` For details, see [Immutable](./api/fiber.md#immutable) in the configuration reference and the [GetString and GetBytes](./api/app.md#getstring) helpers. ### Explore the Ecosystem Fiber is more than the core module. When your application grows, these officially maintained building blocks are one import away: - **Built-in middleware**: 30+ middleware for logging, CORS, security headers, caching, rate limiting, and more live in the core module; browse the [middleware overview](https://docs.gofiber.io/category/-middleware). - **[Contrib packages](https://docs.gofiber.io/contrib/)**: officially maintained integrations with external dependencies, such as JWT, WebSocket, OpenTelemetry, Casbin, and structured logging adapters. - **[Storage drivers](https://docs.gofiber.io/storage/)**: a growing list of backends (Redis, Postgres, MongoDB, S3, and more) behind one interface, ready to plug into the session, limiter, cache, and idempotency middleware. - **[Template engines](https://docs.gofiber.io/template/)**: server-side rendering through the Views interface, with engines like html, django, handlebars, and pug. - **[HTTP client](./client/rest.md)**: a built-in client, also built on Fasthttp, for calling other services with the same performance philosophy. - **[Recipes](https://docs.gofiber.io/recipes/)**: runnable example projects (Docker, GORM, JWT auth, clean architecture, and more) to copy a working starting point from. For a visual map of how these repositories plug into each other, see the [Ecosystem overview](./ecosystem.md). ### Next Steps Work through these in order and you will have touched everything a typical production service needs: 1. [Routing](./guide/routing.md): parameters, wildcards, and constraints; try them live in the [Route Matcher](./extra/route-matcher.md) 2. [Grouping](./guide/grouping.md) and the [middleware catalog](https://docs.gofiber.io/category/-middleware): structure the app and its cross-cutting concerns 3. [Error handling](./guide/error-handling.md): central error handlers and status codes 4. [Request binding](./api/bind.md) and [validation](./guide/validation.md): map request data onto structs safely 5. [Minimal CRUD and health check](./guide/crud-and-health.md): a runnable starter with probes, for learning and CI smoke tests 6. [Templates](./guide/templates.md): render views with your favorite template engine 7. [HTTP client](./client/rest.md): call other services with the same performance philosophy 8. [Performance](./guide/faster-fiber.md): custom JSON encoders and the tricks behind the benchmarks 9. [Testing](./api/app.md#test): test handlers without a running server using `app.Test` The [configuration reference](./api/fiber.md) lists every option accepted by `fiber.New`, and the [learning resources](./extra/learning-resources.md) page collects tutorials and hands-on challenges. ### Community and Help Stuck or have questions? Join the [Discord](https://gofiber.io/discord) server or check the [FAQ](./extra/faq.md). Fiber is developed in the open on [GitHub](https://github.com/gofiber/fiber); issues, discussions, and contributions are welcome. --- ## Adaptor The `adaptor` package converts between Fiber and `net/http`, letting you reuse handlers, middleware, and requests across both frameworks. :::tip Fiber can register plain `net/http` handlers directly—just pass an `http.Handler`, `http.HandlerFunc`, or `func(http.ResponseWriter, *http.Request)` to any router method and it will be adapted automatically. The adaptor helpers remain valuable when you need to convert middleware, swap handler directions, or transform requests explicitly. ::: :::caution Fiber features are unavailable Even when you register them directly, adapted `net/http` handlers still run with standard library semantics. They don't have access to `fiber.Ctx`, and the compatibility layer comes with additional overhead compared to native Fiber handlers. Use them for interop and legacy scenarios, but prefer Fiber handlers when performance or Fiber-specific APIs matter. ::: ### Features - Convert `net/http` handlers and middleware to Fiber handlers - Convert Fiber handlers to `net/http` handlers - Convert a Fiber context (`fiber.Ctx`) into an `http.Request` - Copy values stored in a `context.Context` onto a `fasthttp.RequestCtx` :::note Body size limits when running Fiber from net/http When Fiber is executed from a `net/http` server through `FiberHandler`, `FiberHandlerFunc`, or `FiberApp`, the adaptor enforces the app's configured `BodyLimit`. The app's `BodyLimit` defaults to **4 MiB** if a non-positive value is provided during configuration. `FiberHandler` and `FiberHandlerFunc` run the handler on a fresh app with the default configuration, so they always apply the 4 MiB default; pass your own app to `FiberApp` to use a different `BodyLimit`. Requests exceeding the active limit receive `413 Request Entity Too Large`. ::: ### API Reference | Name | Signature | Description | |-------------------------------|-------------------------------------------------------------------------------|-------------------------------------------------------------------------------| | `HTTPHandler` | `HTTPHandler(h http.Handler) fiber.Handler` | Converts `http.Handler` to `fiber.Handler` | | `HTTPHandlerWithContext` | `HTTPHandlerWithContext(h http.Handler) fiber.Handler` | Converts `http.Handler` to `fiber.Handler`, propagating Fiber's local context | | `HTTPHandlerFunc` | `HTTPHandlerFunc(h http.HandlerFunc) fiber.Handler` | Converts `http.HandlerFunc` to `fiber.Handler` | | `HTTPMiddleware` | `HTTPMiddleware(mw func(http.Handler) http.Handler) fiber.Handler` | Converts `http.Handler` middleware to `fiber.Handler` middleware | | `FiberHandler` | `FiberHandler(h fiber.Handler) http.Handler` | Converts `fiber.Handler` to `http.Handler` | | `FiberHandlerFunc` | `FiberHandlerFunc(h fiber.Handler) http.HandlerFunc` | Converts `fiber.Handler` to `http.HandlerFunc` | | `FiberApp` | `FiberApp(app *fiber.App) http.HandlerFunc` | Converts an entire Fiber app to a `http.HandlerFunc` | | `ConvertRequest` | `ConvertRequest(c fiber.Ctx, forServer bool) (*http.Request, error)` | Converts `fiber.Ctx` into a `http.Request` | | `LocalContextFromHTTPRequest` | `LocalContextFromHTTPRequest(r *http.Request) (context.Context, bool)` | Extracts the propagated `context.Context` from an adapted `http.Request` | | `CopyContextToFiberContext` | `CopyContextToFiberContext(src any, requestContext *fasthttp.RequestCtx)` | Copies `context.Context` to `fasthttp.RequestCtx` | --- ### Examples #### 1. Using `net/http` handlers in Fiber (`HTTPHandler`, `HTTPHandlerFunc`) Run standard `net/http` handlers inside Fiber. Fiber can auto-adapt them, or you can explicitly convert them when you want to cache or share the converted handler. ```go package main import ( "fmt" "net/http" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/adaptor" ) func main() { app := fiber.New() // Fiber adapts net/http handlers for you during registration. app.Get("/", http.HandlerFunc(helloHandler)) // You can also convert and reuse the handler manually. cached := adaptor.HTTPHandler(http.HandlerFunc(helloHandler)) app.Get("/cached", cached) // When you already have an http.HandlerFunc, convert it directly. app.Get("/func", adaptor.HTTPHandlerFunc(helloHandler)) app.Listen(":3000") } func helloHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Hello from net/http!") } ``` #### 2. Using `net/http` middleware with Fiber (`HTTPMiddleware`) Middleware written for `net/http` can run inside Fiber: ```go package main import ( "log" "net/http" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/adaptor" ) func main() { app := fiber.New() // Apply an http middleware in Fiber app.Use(adaptor.HTTPMiddleware(loggingMiddleware)) app.Get("/", func(c fiber.Ctx) error { return c.SendString("Hello Fiber!") }) app.Listen(":3000") } func loggingMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Println("Request received") next.ServeHTTP(w, r) }) } ``` #### 3. Using Fiber handlers in `net/http` (`FiberHandler`) You can use Fiber handlers from `net/http`: ```go package main import ( "net/http" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/adaptor" ) func main() { // Convert a Fiber handler to an http.Handler http.Handle("/", adaptor.FiberHandler(helloFiber)) // Convert a Fiber handler to an http.HandlerFunc http.HandleFunc("/func", adaptor.FiberHandlerFunc(helloFiber)) http.ListenAndServe(":3000", nil) } func helloFiber(c fiber.Ctx) error { return c.SendString("Hello from Fiber!") } ``` #### 4. Converting Fiber handlers to `http.HandlerFunc` (`FiberHandlerFunc`) When you specifically need an `http.HandlerFunc`, wrap the Fiber handler directly: ```go package main import ( "net/http" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/adaptor" ) func main() { http.HandleFunc("/func-only", adaptor.FiberHandlerFunc(helloFiber)) http.ListenAndServe(":3000", nil) } func helloFiber(c fiber.Ctx) error { return c.SendString("Hello from Fiber!") } ``` #### 5. Running a full Fiber app inside `net/http` (`FiberApp`) You can wrap a full Fiber app inside `net/http`: ```go package main import ( "net/http" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/adaptor" ) func main() { app := fiber.New() app.Get("/", func(c fiber.Ctx) error { return c.SendString("Hello from Fiber!") }) // Run Fiber inside an http server http.ListenAndServe(":3000", adaptor.FiberApp(app)) } ``` :::caution Two parsers read one request The `net/http` server and the Fiber app behind it each parse the request's headers, and they do not read a repeated header alike. `Header.Get` returns the first field line even when it is empty, while `c.Get` returns the first line that holds a value. A request carrying `X-Internal:` on one line and `X-Internal: true` on the next therefore reads as empty in a `net/http` guard and as `true` in the Fiber handler behind it. A guard that must keep clients from setting a header should strip it with `r.Header.Del(name)` or test `len(r.Header.Values(name)) > 0`, never compare `r.Header.Get(name)` with the empty string. Fiber's `basicauth` and `csrf` middleware refuse a request that repeats the single-value fields they read, and so does `keyauth` with its default `Authorization` extractor, so a credential split across two `Authorization` lines is rejected rather than authenticated; a custom `extractors.FromHeader` extractor combines repeated lines into one value, which the validator then judges. ::: #### 6. Converting `fiber.Ctx` to `*http.Request` (`ConvertRequest`) Create an `*http.Request` from a `fiber.Ctx`. The `forServer` parameter decides whether the request carries a `RequestURI`; `RemoteAddr` and `TLS` are filled in either way: - Use `forServer = true` when the converted request will be passed into a `net/http` handler (sets `RequestURI`, which a server-side request carries) - Use `forServer = false` when creating a request for client-side use (e.g., making an outbound HTTP request with `http.Client`, which refuses a request that has `RequestURI` set) ```go package main import ( "net/http" "net/http/httptest" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/adaptor" ) func main() { app := fiber.New() app.Get("/request", handleRequest) app.Listen(":3000") } func handleRequest(c fiber.Ctx) error { // Use forServer = true when passing to a net/http handler httpReq, err := adaptor.ConvertRequest(c, true) if err != nil { return err } // Pass the request to a net/http handler. recorder := httptest.NewRecorder() http.DefaultServeMux.ServeHTTP(recorder, httpReq) return c.SendString("Converted Request URL: " + httpReq.URL.String()) } ``` #### 7. Passing Fiber user context into `net/http` This example shows a realistic flow: a Fiber middleware sets a request-scoped `context.Context` (with a `request_id`) on the Fiber context, then an adapted `net/http` handler retrieves it via `LocalContextFromHTTPRequest`. ```go package main import ( "context" "fmt" "net/http" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/adaptor" ) type ctxKey string const requestIDKey ctxKey = "request_id" func main() { app := fiber.New() // Create a request-scoped context in Fiber (e.g., request id, auth claims, trace span). app.Use(func(c fiber.Ctx) error { reqID := c.Get("X-Request-ID") ctx := context.WithValue(context.Background(), requestIDKey, reqID) // Fiber stores request-scoped context as "user context". c.SetContext(ctx) return c.Next() }) // 2) Run a standard net/http handler that includes Fiber's user context propagated. app.Get("/hello", adaptor.HTTPHandlerWithContext(http.HandlerFunc(handleRequest))) app.Listen(":3000") } func handleRequest(w http.ResponseWriter, r *http.Request) { ctx, ok := adaptor.LocalContextFromHTTPRequest(r) if !ok || ctx == nil { http.Error(w, "missing propagated context", http.StatusInternalServerError) return } reqID, _ := ctx.Value(requestIDKey).(string) fmt.Fprintf(w, "Hello from net/http (request_id=%s)\n", reqID) } ``` #### 8. Copying context values onto `fasthttp.RequestCtx` (`CopyContextToFiberContext`) `CopyContextToFiberContext` copies values stored in a `context.Context` onto a `fasthttp.RequestCtx`. The function is marked deprecated in code because it uses reflection and unsafe operations—prefer explicit parameter passing when possible. When you do need it, call it immediately after you add values to the `net/http` context so Fiber can read them via `c.Locals()`. Values are copied from the chain of derived contexts (`WithValue`, `WithCancel`, `WithTimeout`, `WithDeadline`, `WithoutCancel`), the most recently derived context winning for a key set twice, as `Value` reports it. The walk follows the chain to its end and stops only where it would repeat itself, so a deep middleware stack keeps its outermost values and a context pointing back at itself still terminates: ```go package main import ( "context" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/adaptor" ) type contextKey string func main() { app := fiber.New() app.Use(func(c fiber.Ctx) error { // Convert the Fiber context to an http.Request so we can attach context values. httpReq, err := adaptor.ConvertRequest(c, true) if err != nil { return err } // Add context data and push it back to the Fiber context. enriched := httpReq.WithContext(context.WithValue(httpReq.Context(), contextKey("requestID"), "req-123")) adaptor.CopyContextToFiberContext(enriched.Context(), c.RequestCtx()) return c.Next() }) app.Get("/", func(c fiber.Ctx) error { if id, ok := c.Locals(contextKey("requestID")).(string); ok { return c.SendString("Request ID: " + id) } return c.SendStatus(fiber.StatusNotFound) }) app.Listen(":3000") } ``` --- ### Notes and limitations - An adapted `net/http` handler, middleware or `ConvertRequest` request is built from the path the router matched, `c.Path()`, followed by the query as the client sent it, not from the request line as it arrived. The two differ for an escaped or non-canonical request, and `net/http` reads the raw line its own way: `/public/..%2Fadmin/x` decodes into a `URL.Path` that `http.FileServer` cleans to `/admin/x`, although no middleware mounted on `/admin` has run for it. Routed, `/a/../admin/x` reaches the handler as `/admin/x` (after the `/admin` middleware ran), `%41` as `A`, a stray `%` as `%25`, a byte a path may not carry raw, such as `|` or a byte of UTF-8, as its escape, and with `UnescapePath` the decoded path is escaped again segment by segment. The query keeps its bytes unless a Fiber handler changed the arguments through `QueryArgs()`. - The adapted request keeps the host Fiber saw. For a request in the absolute form, such as `GET http://public.example/x` with `Host: admin.internal`, that is the host the request line names ([RFC 9112 Section 3.2.2](https://www.rfc-editor.org/rfc/rfc9112#section-3.2.2)): `r.Host` is `public.example`, as are `c.Hostname()` and the host `app.Domain` matches. - `HTTPHandler`, `HTTPHandlerFunc` and `HTTPHandlerWithContext` answer `404 Not Found` for a routed path with an empty segment (`//admin/x`) or an escaped slash (`/public/..%2Fadmin/x`): `net/http` decodes `%2F` into a separator in `URL.Path` and its handlers clean `//` and `..` away, so the handler would serve a path the router never matched. They answer `404` as well for a backslash, raw or escaped as `%5C`, and for a `.` or `..` segment carrying parameters, such as `..;`, which a handler that passes the request on, such as `httputil.ReverseProxy`, would hand to an upstream that reads them as a separator (WHATWG URL parsers, IIS) or as a dot segment (Tomcat, Jetty). `HTTPMiddleware` and `ConvertRequest` hand all of these on, with an escaped slash kept in `URL.RawPath`; a Fiber route behind the middleware still receives its `%2F`. To serve names that hold one, such as a GitLab-style `group%2Fproject` id, wrap the handler with `HTTPMiddleware` as a middleware that does not call `next`, and read the name from `r.URL.EscapedPath()`. - `HTTPMiddleware` answers `400 Bad Request`, and `ConvertRequest` returns `fiber.ErrBadRequest`, for a routed path fasthttp would read as an authority rather than a path: one that begins with `//` and holds `://`, which `..` resolution builds out of `//u:pw@evil.example/a:/x/..//b`, or any path beginning with `//` on a request without a `Host` header. Set as the request line, it would become the host of both the `net/http` request and the Fiber request behind the middleware. - A routed path holding an escape of an unreserved character, such as `%2e` or `%41`, is answered with `404 Not Found` by `HTTPHandler`, `HTTPHandlerFunc` and `HTTPHandlerWithContext` and with `400 Bad Request` by `HTTPMiddleware` and `ConvertRequest`. The router decodes every such escape when it normalizes a path, so one still in `c.Path()` can only have been forged by a stray `%`, and `URL.Path` would read a forged `%2e%2e` as the `..` segment an `http.FileServer` cleans away. - `HTTPHandler`, `HTTPHandlerFunc` and `HTTPHandlerWithContext` put the original request line back once a buffered handler has returned, or panicked, so `c.OriginalURL()` in a middleware that runs afterwards is unchanged. A handler that flushed or hijacked is still running with a request whose URL aliases the request line, so the line is then left as the handler read it; `HTTPMiddleware` and `ConvertRequest` leave it as well. - A request that `net/http` served over TLS is seen as TLS by Fiber: `c.Scheme()` is `https`, `c.Secure()` is true and `c.RequestCtx().TLSConnectionState()` carries the state from `r.TLS`. - For a request that reaches Fiber through `FiberHandler`, `FiberHandlerFunc` or `FiberApp`, `c.StartTime()` marks the moment the adaptor took the request over, not the moment the `net/http` server began reading it, so `c.Elapsed()` leaves out the time spent before the handler was called. - Both parsers read the headers: `Header.Get` returns the first field line even when it is empty, `c.Get` the first line that holds a value. A `net/http` guard should strip a header or check every value rather than compare `Header.Get` with the empty string; see the caution in the `FiberApp` section above. - `HTTPMiddleware` routes the request the wrapped middleware hands to `next`, including a rewritten `r.URL` such as the one `http.StripPrefix` produces. The middleware must call `next` before it flushes or hijacks the response; after that the response has left Fiber's hands and the call is ignored. - `HTTPMiddleware` copies `r.Method`, `r.Host` and `r.Header`, as the wrapped middleware hands them to `next`, back onto the Fiber request, so a header it replaces or deletes (`r.Header.Set`, `r.Header.Del`) is replaced or gone for the handlers behind it, `c.IPs()` included. A changed `r.Method` reaches only the raw request (`c.Request().Header.Method()`): `c.Method()` and the route that is running keep the method the request was routed with, so a rewrite does not move it to another method's handlers. `Content-Length` and `Transfer-Encoding` are not copied back, so the body framing stays as received. A `Connection` field written as several entries is joined with commas, and a `close` token anywhere in it closes the connection once the response is sent. - `FiberHandler`, `FiberHandlerFunc` and `FiberApp` route `r.RequestURI`, the request line as `net/http` received it, so a raw `/über` or `/a|b` is routed and its params are read as a Fiber server would read them. When `r.URL` no longer says what that line says, or there is no such line, they route the request line `net/http` would write for `r.URL` instead: the path an `http.StripPrefix` in front of them rewrote, the URL of a request built in code, which has no `RequestURI` at all, and, for a `CONNECT` request in the authority form, the authority rather than the `/` that `r.URL.RequestURI()` reads for it: `r.URL.Opaque` when set, else `r.Host`, else `r.URL.Host`, the order in which `net/http` itself writes such a request. - `FiberHandler`, `FiberHandlerFunc` and `FiberApp` pass repeated lines of a header such as `X-Forwarded-For` on as repeated lines, and `c.Protocol()` reports the version the request arrived with (`HTTP/1.0`, `HTTP/1.1`, `HTTP/2` or `HTTP/3`). The client address comes from `r.RemoteAddr`: a host without a port gets port `80`, and behind a Unix socket listener it is the listener's own address, which holds no client IP. An address that is empty or cannot be resolved does not fail the request; `c.IP()` is then an empty string. `c.SendEarlyHints` does not panic, but its `103` interim response is discarded because there is no client connection to write it to; the `Link` headers still reach the final response. ### Summary The `adaptor` package lets Fiber and `net/http` interoperate so you can: - Convert handlers and middleware in both directions - Run Fiber apps inside `net/http` - Convert `fiber.Ctx` to `http.Request` - Propagate Fiber's user context into adapted `net/http` handlers This makes it straightforward to integrate Fiber with existing Go projects or migrate between frameworks. --- ## BasicAuth Basic Authentication middleware for [Fiber](https://github.com/gofiber/fiber) that provides HTTP basic auth. It calls the next handler for valid credentials and returns [`401 Unauthorized`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401) for missing or invalid credentials, [`400 Bad Request`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400) for malformed `Authorization` headers, among them a request that carries the field on more than one line, or [`431 Request Header Fields Too Large`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/431) when the header exceeds size limits. Credentials may omit Base64 padding as permitted by RFC 7235's `token68` syntax. The default unauthorized response includes the header `WWW-Authenticate: Basic realm="Restricted", charset="UTF-8"`, sets `Cache-Control: no-store`, and adds `Authorization` to the `Vary` header, keeping the fields an earlier middleware put there. Only the `UTF-8` charset is supported; any other value will panic. Generated realm and charset values are escaped as RFC 9110 HTTP quoted strings. With no `Users` and no `Authorizer`, which is what `New()` called without a config gives, no credentials are valid and no request is let through: well-formed credentials, or none, are answered `401 Unauthorized`, and a malformed `Authorization` header still gets `400 Bad Request`. ### Signatures ```go func New(config ...Config) fiber.Handler func UsernameFromContext(ctx any) string ``` `UsernameFromContext` accepts a `fiber.CustomCtx`, `fiber.Ctx`, a `*fasthttp.RequestCtx`, or a `context.Context`. ### Examples Import the middleware package: ```go import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/basicauth" ) ``` Once your Fiber app is initialized, choose one of the following approaches: ```go // Provide a minimal config app.Use(basicauth.New(basicauth.Config{ Users: map[string]string{ // "doe" hashed using SHA-256 "john": "{SHA256}eZ75KhGvkY4/t0HfQpNPO1aO0tk6wd908bjUGieTKm8=", // "123456" hashed using bcrypt "admin": "$2a$10$gTYwCN66/tBRoCr3.TXa1.v1iyvwIF7GRBqxzv7G.AHLMt/owXrp.", }, })) // Or extend your config for customization app.Use(basicauth.New(basicauth.Config{ Users: map[string]string{ // "doe" hashed using SHA-256 "john": "{SHA256}eZ75KhGvkY4/t0HfQpNPO1aO0tk6wd908bjUGieTKm8=", // "123456" hashed using bcrypt "admin": "$2a$10$gTYwCN66/tBRoCr3.TXa1.v1iyvwIF7GRBqxzv7G.AHLMt/owXrp.", }, Realm: "Forbidden", Unauthorized: func(c fiber.Ctx) error { return c.SendFile("./unauthorized.html") }, })) // Or check the credentials yourself. Users is not consulted when Authorizer is set, // so Authorizer has to verify the password as well. app.Use(basicauth.New(basicauth.Config{ Authorizer: func(user, pass string, c fiber.Ctx) bool { // custom validation logic, for example a lookup of a stored hash return checkCredentials(user, pass) }, })) ``` #### Password hashes Passwords must be supplied in pre-hashed form. The middleware detects the hashing algorithm from a prefix: - `"{SHA512}"` or `"{SHA256}"` followed by a base64-encoded digest - standard bcrypt strings beginning with `$2` If no prefix is present, the value is interpreted as a SHA-256 digest encoded in hex or base64. Plaintext passwords are rejected. The middleware decodes the credentials as UTF-8 and normalizes them to Unicode NFC before it compares them, so hash a password that contains non-ASCII characters in its NFC form. Credentials that are not valid UTF-8, or that contain control characters, are answered with `400 Bad Request`. The decoded digest must be exactly the size of the named algorithm — 32 bytes for SHA-256, 64 bytes for SHA-512. A digest of any other length could never match a password, so it is rejected at startup: `New()` panics with `ErrInvalidSHA256PasswordLength` or `ErrInvalidSHA512PasswordLength` rather than starting with an account that can never authenticate. The most common cause is a truncated copy/paste, or a SHA-256 digest stored under the `{SHA512}` prefix. Every attempt runs one check per configured hash tier, meaning each algorithm and each bcrypt cost in `Users`, whether the user exists or not, so response time reveals neither. A setup with a single tier pays for one check. A mixed one, for example while migrating from SHA-256 to bcrypt or to a higher bcrypt cost, pays for one check per tier on every attempt. ##### Generating SHA-256 and SHA-512 passwords Create a digest, encode it in base64, and prefix it with `{SHA256}` or `{SHA512}` before adding it to `Users`: ```bash # SHA-256 printf 'secret' | openssl dgst -binary -sha256 | base64 # SHA-512 printf 'secret' | openssl dgst -binary -sha512 | base64 ``` Include the prefix in your config: ```go Users: map[string]string{ "john": "{SHA256}K7gNU3sdo+OL0wNhqoVWhr3g6s1xYv72ol/pe/Unols=", "admin": "{SHA512}vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg==", } ``` ### Config | Property | Type | Description | Default | |:----------------|:----------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------------------| | Next | `func(fiber.Ctx) bool` | Next defines a function to skip this middleware when it returns true. | `nil` | | Users | `map[string]string` | Users maps usernames to **hashed** passwords (e.g. bcrypt, `{SHA256}`). | `map[string]string{}` | | Realm | `string` | Realm is a string to define the realm attribute of BasicAuth. The realm identifies the system to authenticate against and can be used by clients to save credentials. | `"Restricted"` | | Charset | `string` | Charset sent in the `WWW-Authenticate` header. Only `"UTF-8"` is supported (case-insensitive). | `"UTF-8"` | | HeaderLimit | `int` | Maximum allowed length of the `Authorization` header. Requests exceeding this limit are rejected. | `8192` | | Authorizer | `func(string, string, fiber.Ctx) bool` | Authorizer defines a function to check the credentials. It will be called with a username, password, and the current context and is expected to return true or false to indicate approval. When set, it replaces the check against `Users`, which is then neither consulted nor validated. | `nil` | | Unauthorized | `fiber.Handler` | Unauthorized defines the response body for unauthorized responses. | `nil` | | BadRequest | `fiber.Handler` | BadRequest defines the response for malformed `Authorization` headers, a request carrying the field on more than one line included. | `nil` | ### Default Config ```go var ConfigDefault = Config{ Next: nil, Users: map[string]string{}, Realm: "Restricted", Charset: "UTF-8", HeaderLimit: 8192, Authorizer: nil, Unauthorized: nil, BadRequest: nil, } ``` --- ## Cache Cache middleware for [Fiber](https://github.com/gofiber/fiber) that intercepts responses and stores the body, `Content-Type`, and status code under a deterministic key derived from request dimensions. Special thanks to [@codemicro](https://github.com/codemicro/fiber-cache) for contributing this middleware to Fiber core. By default, cached responses expire after five minutes and the middleware stores up to 1 MB of response bodies. That size limit is the default of `cache.New()` called without a `Config`: a `Config` that leaves `MaxBytes` unset stores bodies without a limit. ### Request directives - `Cache-Control: no-cache` (or `Pragma: no-cache`) returns the latest response while still caching it, so the status is `miss` unless the new response cannot be stored. - `Cache-Control: no-store` skips caching and always forwards a fresh response. - `Cache-Control: max-age=` serves the cached response only if it is no older than that, and `min-fresh=` only if it stays fresh for at least that much longer. Otherwise the middleware revalidates: the handler runs and its response replaces the entry. `max-age=0` always revalidates. - `Cache-Control: max-stale` accepts an expired entry, and `max-stale=` one that expired no more than that long ago. The hit carries `Warning: 110 - "Response is stale"`. An entry stored from a response with `must-revalidate` or `proxy-revalidate` is never served stale. The storage keeps an entry for the longer of its own lifetime and `Expiration`, counted from when it is stored, and `max-stale` can only reach an entry while the storage still holds it after it has stopped being fresh. That leaves a window when the lifetime is shorter than `Expiration`, and also when an upstream `Age` or an earlier `Date` has used up part of a lifetime that is `Expiration` or longer, because the entry stops being fresh that much before the storage drops it. A lifetime taken from `Expires` is not shortened by `Age` or `Date`, so it leaves a window only when it is shorter than `Expiration`. - `Cache-Control: only-if-cached` answers `504 Gateway Timeout`, with the `unreachable` status, instead of calling the handler when there is no entry to serve. That includes a request that also sends `no-cache` (or `Pragma: no-cache`) or `max-age=0`: a stored response cannot be served to it without validating it, which means calling the handler. The stored entry is kept for the requests that can use it. ### Entry lifetime The response decides how long its entry lives: `Cache-Control: s-maxage`, then `max-age`, then `Expires` set the expiration, and `Expiration` applies when the response says nothing. `ExpirationGenerator` overrides all of them. Time the response already spent in an upstream cache, reported by `Age` or implied by an earlier `Date` on the upstream response, counts against that lifetime, except a lifetime taken from `Expires`: that date is absolute, so it is not shortened. A response with no lifetime left is not stored and reports `unreachable`, for example one with `max-age=0`, an `Expires` in the past, or an `Age` as large as its lifetime. A hit carries `X-Cache: hit` and an `Age` header with the entry's age in seconds. Its `Date` is the server's current date, not the stored one: fasthttp writes `Date` itself and ignores the value the middleware sets (`DisableDefaultDate` leaves the header out), so the stored `Date` only decides the entry's age. A hit also carries a `Warning` header when it is served stale (`110`) or when an entry older than 24 hours has no `max-age`, `s-maxage` or `Expires` of its own (`113 - "Heuristic expiration"`). ### Cacheable status codes The middleware caches these status codes: - `200: OK` - `203: Non-Authoritative Information` - `204: No Content` - `300: Multiple Choices` - `301: Moved Permanently` - `308: Permanent Redirect` - `404: Not Found` - `405: Method Not Allowed` - `410: Gone` - `414: URI Too Long` - `501: Not Implemented` Responses with other status codes result in an `unreachable` cache status. A `206 Partial Content` response is never stored either: the middleware does not understand range requests, so a stored partial body would be replayed to clients asking for the whole representation (RFC 9111 §3.3). For more about cacheable status codes and RFC 7231, see: - [Cacheable - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Glossary/Cacheable) - [RFC7231 - Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content](https://datatracker.ietf.org/doc/html/rfc7231) ### Signatures ```go func New(config ...Config) fiber.Handler ``` ### Examples Import the middleware package: ```go import ( "strconv" "time" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/cache" "github.com/gofiber/utils/v2" ) ``` Once your Fiber app is initialized, register the middleware: ```go // Initialize default config app.Use(cache.New()) // Or extend the config for customization app.Use(cache.New(cache.Config{ Next: func(c fiber.Ctx) bool { return fiber.Query[bool](c, "noCache") }, Expiration: 30 * time.Minute, DisableCacheControl: true, })) ``` Customize expiration and cache key behavior: ```go app.Use(cache.New(cache.Config{ ExpirationGenerator: func(c fiber.Ctx, cfg *cache.Config) time.Duration { newCacheTime, _ := strconv.Atoi(c.GetRespHeader("Cache-Time", "600")) return time.Second * time.Duration(newCacheTime) }, // Optional: fully custom key KeyGenerator: func(c fiber.Ctx) string { return utils.CopyString(c.Path()) + "|tenant=" + c.Get("X-Tenant-ID") }, })) app.Get("/", func(c fiber.Ctx) error { c.Response().Header.Add("Cache-Time", "6000") return c.SendString("hi") }) ``` Use `CacheInvalidator` to invalidate entries programmatically: ```go app.Use(cache.New(cache.Config{ CacheInvalidator: func(c fiber.Ctx) bool { return fiber.Query[bool](c, "invalidateCache") }, })) ``` `CacheInvalidator` defines custom invalidation rules. Return `true` to bypass the cache. In the example above, setting the `invalidateCache` query parameter to `true` invalidates the entry. Cache keys are masked in logs and error messages by default. Set `DisableValueRedaction` to `true` if you explicitly need the raw key for debugging. #### Default cache key behavior (safe by default) By default, cache keys include: - request method (partitioned internally by the middleware), - request scheme and host, as `c.Scheme()` and `c.Host()` report them (so `X-Forwarded-Proto` and `X-Forwarded-Host` apply behind a trusted proxy), - request path, - canonicalized query string (enabled unless `DisableQueryKeys` is `true`), - representation-driving request headers (`accept`, `accept-encoding`, `accept-language`), - a digest of the `Authorization` header, when the request carries one. The middleware adds it to every key, a custom `KeyGenerator`'s included, so a response stored for one credential is never served to another. This prevents common collisions from path-only keys (for example, `/?id=1` vs `/?id=2`, or `/dash` on two hosts routed with `app.Domain()`) while keeping fragmentation bounded. Two requests that differ only in their authority never share an entry, so a page routed by host or a redirect built from `c.BaseURL()` is served only to the host it was generated for. If you supply a custom `KeyGenerator` and your responses depend on the host, include `c.Host()` (and `c.Scheme()`) in the key yourself. The middleware **does not include request body/form values in the default cache key**, except for `QUERY` requests: per [RFC 10008](https://www.rfc-editor.org/rfc/rfc10008.html), when `QUERY` is enabled via `Methods` the default key generator incorporates the request body so different bodies on the same URL get distinct keys. Cache lookup/storage is applied only for `GET` and `HEAD` requests by default. Other HTTP methods bypass the cache middleware. You can change this via the `Methods` config field (for example, adding `fiber.MethodQuery`). If you supply a custom `KeyGenerator` and enable a body-bearing method such as `QUERY`, make sure it incorporates `c.Request().Body()`, otherwise requests with the same URL but different bodies will collide. If a response sets `Vary`, request lookup/storage is also partitioned by those header values unless `DisableVaryHeaders` is `true`. Responses with `Vary: *` remain uncacheable, and so does a response whose `Vary` names more than 32 fields or is longer than 2048 bytes. That holds when `DisableVaryHeaders` is `true` as well. #### Cached redirects `300`, `301` and `308` are cacheable statuses, so a redirect can be served from the cache. Its `Location` is kept with the entry even when `StoreResponseHeaders` is off, since the status means nothing without it. `Location` joins a set the entry always carries, each in a field of its own rather than in the stored header list: `Content-Type`, `Content-Encoding`, `Cache-Control`, `Expires`, `ETag` and `Vary`, which a hit sends back, and `Date` and `Age`, which only decide the `Age` a hit reports. Those are what the entry needs to be replayed and revalidated at all. `StoreResponseHeaders` is about every other response header, none of which is kept without it. #### Header names Response field names are matched case-insensitively, as [RFC 9110 §5.1](https://www.rfc-editor.org/rfc/rfc9110.html#section-5.1) requires, so a handler is free to write `cache-control` or `expires` in lower case under [`DisableHeaderNormalizing`](../api/fiber.md#config). Those values decide what they would decide either way, and the fields this middleware writes itself — `Cache-Control`, `Age`, `Date`, `ETag`, `Expires`, `Vary` — replace whatever spelling the response already carries rather than being added beside it. For `Date`, fasthttp writes one header line with the server's current date. #### Responses that are never stored One entry is served to every client whose request matches its key, so a response that identifies a single client is not stored at all: - **A response that sets a cookie.** `Set-Cookie` means the response has been personalized for the client that caused the miss, and the body — not just the header — is what would be replayed to everyone else. The response still reaches that client normally; only the entry is skipped, and `X-Cache` reads `unreachable`. - **A response to a request carrying `Authorization`,** unless the response permits shared caching, per [RFC 9111 §3.5](https://www.rfc-editor.org/rfc/rfc9111.html#section-3.5). - **`Cache-Control: no-store`, `private`, `no-cache`, or `Vary: *`.** When revalidation returns `private`, `no-cache`, `no-store`, or `Vary: *`, the middleware removes the previous cached entry and its Vary manifest. A concurrently replaced entry keeps its manifest. Cleanup errors are logged and the origin response is still returned; if storage fails during cleanup, the old entry can still be served until it expires, and it keeps counting toward `MaxBytes` until a later eviction retries the delete. A plain miss answered with `no-store` preserves the shared Vary manifest, so existing sibling variants remain reachable. This cleanup applies to those four directives on a response with a [cacheable status code](#cacheable-status-codes). Any other status, such as `503`, `429`, `401`, or `403`, leaves the stored entry and its Vary manifest in place whether or not it carries one of them, so an error page does not evict a healthy entry. The separate storage rules for `Set-Cookie` and requests carrying `Authorization` still apply. A route that genuinely wants both a cookie and a shared entry can say so with `Cache-Control: public` or an `s-maxage` — directives only a shared cache acts on, so neither is written by accident. `must-revalidate` and `proxy-revalidate` do **not** lift the cookie restriction, even though RFC 9111 §3.5 accepts `must-revalidate` for the `Authorization` case: that allowance holds because a revalidating cache returns to the origin and the origin re-checks the credential, and this middleware does not check back with the origin while an entry is fresh — it serves the stored body until the entry expires. Applications that refresh a session cookie on every response will find that most routes stop being cached — that is the point, since those responses are per-client. Set the cookie only where it changes, or mark the genuinely public routes `public`. :::caution Register the cache outside any middleware that writes cookies The check above reads the response as it stands when the cache decides to store it, which is the moment `c.Next()` returns to the cache middleware. A middleware registered **outside** the cache does its post-`c.Next()` work later, so a cookie it writes then is not there to be seen — the entry is stored and the next client reads the first one's body. Fiber's own [session](./session.md) middleware writes its cookie exactly that way, so the order matters: ```go app.Use(cache.New()) // correct: the cookie is written before the cache decides app.Use(session.New()) app.Use(session.New()) // leaks: the cookie is written after the cache stored app.Use(cache.New()) ``` The same applies to any handler wrapper of the form `err := c.Next(); c.Cookie(...); return err`. Note also that a response personalized from a **request** cookie without setting one is cached and shared by design — use `KeyCookies` or `Vary: Cookie` to key those apart. A hit on an entry keyed that way is sent with `Cache-Control: private`, as described below. ::: #### Per-user entries and a cache in front of the app A hit is sent with the stored response's `Vary`, whether or not `StoreResponseHeaders` is set, so a cache in front of the app that honors `Vary` keys the hit the way this middleware did. When the entry carries no `Cache-Control` of its own and `DisableCacheControl` is `false`, a hit is sent with `Cache-Control: public, max-age=`. An entry keyed by a cookie or credential holds one user's response, so such a hit is sent with `Cache-Control: private, max-age=` instead: a shared cache in front of the app does not store it, while the user's own browser still may. The entry counts as keyed by a credential when the response's `Vary` lists `Cookie` or `Authorization`, when `KeyCookies` is set, or when `KeyHeaders` names `Cookie` or `Authorization`. `DisableVaryHeaders` does not change this: a response whose `Vary` lists `Cookie` is one user's whether or not this middleware partitions on it. A handler that sends its own `Cache-Control` keeps it on hits. On a per-user route that says `max-age` without `private`, add `private` yourself if a cache in front of the app ignores `Vary`. A custom `KeyGenerator` that keys on a cookie or credential is not visible to this middleware either: send `Cache-Control: private` from those handlers, or set `DisableCacheControl`. #### Vary and `Content-Type` A request `Content-Type` naming a form is folded to lower case before it is used in the cache key, so the key is the same whether it is built before or after the handler runs — the form accessors fold that header in place, and without this the entry would be stored under a key no lookup produces. A handler on such a route therefore sees the folded media type even if it never reads a form value: on every request when `KeyHeaders` names `Content-Type`, and from the second request on when only `Vary` does. The boundary keeps its case, and a non-form `Content-Type` is left alone. ### Config | Property | Type | Description | Default | | :------------------- | :--------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------- | | Next | `func(fiber.Ctx) bool` | Next defines a function that is executed before creating the cache entry and can be used to execute the request without cache creation. If an entry already exists, it will be used. If you want to completely bypass the cache functionality in certain cases, you should use the [skip middleware](skip.md). | `nil` | | Expiration | `time.Duration` | Expiration is the time that a cached response will live when neither the response nor `ExpirationGenerator` sets a lifetime. A value shorter than one second in magnitude, zero included, selects the default, and `-1 * time.Second` or lower turns the middleware into a pass-through. | `5 * time.Minute` | | CacheHeader | `string` | CacheHeader is the header on the response header that indicates the cache status, with the possible return values "hit," "miss," or "unreachable." | `X-Cache` | | DisableCacheControl | `bool` | DisableCacheControl omits the `Cache-Control` header when set to `true`. When `false`, a hit whose entry has no `Cache-Control` of its own is sent with `public, max-age=`, or `private, max-age=` when the entry is keyed by a cookie or credential. | `false` | | CacheInvalidator | `func(fiber.Ctx) bool` | CacheInvalidator defines a function that is executed before checking the cache entry. It can be used to invalidate the existing cache manually by returning true. | `nil` | | DisableValueRedaction | `bool` | Turns off cache key redaction in logs and error messages when set to `true`. | `false` | | KeyGenerator | `func(fiber.Ctx) string` | KeyGenerator allows you to generate custom keys. The HTTP method, a key-format version and, when the request carries `Authorization`, a digest of that header are partitioned internally by the middleware. | structured key from scheme + host + path + canonical query + selected headers/cookies | | DisableQueryKeys | `bool` | Disables canonicalized query params in keys. | `false` | | KeyHeaders | `[]string` | Header allow-list used for key partitioning. Names are normalized case-insensitively and sorted. Use `[]string{}` to disable header-based partitioning. | `[]string{"accept","accept-encoding","accept-language"}` | | KeyCookies | `[]string` | Optional cookie allow-list for key partitioning. Explicit opt-in only; names remain case-sensitive. Hits on entries keyed this way are sent with `Cache-Control: private`. | `nil` | | Methods | `[]string` | HTTP methods eligible for caching. Requests whose method is not in this list bypass the cache and are answered with `unreachable` in the cache header. Names are normalized to uppercase. `nil` selects the default, and an empty slice disables caching for every method. | `[]string{fiber.MethodGet, fiber.MethodHead}` | | DisableVaryHeaders | `bool` | Disables response `Vary` dimensions in cache lookup/storage partitioning. | `false` | | ExpirationGenerator | `func(fiber.Ctx, *cache.Config) time.Duration` | ExpirationGenerator allows you to generate a custom expiration per request. It overrides `Expiration` and any lifetime the response sets with `Cache-Control` or `Expires`, Lifetimes are counted in whole seconds: a duration of zero or less leaves the response uncached, and one under a second never produces a hit. | `nil` | | Storage | `fiber.Storage` | Storage is used to store the state of the middleware. Entries are namespaced by a key-format version, so an external store that survives an upgrade starts cold rather than serving entries an older version partitioned by different rules. | In-memory store | | StoreResponseHeaders | `bool` | StoreResponseHeaders allows you to store additional headers generated by next middlewares & handler. Connection-scoped headers and `Set-Cookie` are never stored, since a cache entry is replayed to every client that matches its key. `Vary` is kept with every entry and replayed on hits regardless of this setting. | `false` | | MaxBytes | `uint` | MaxBytes is the maximum number of bytes of response bodies simultaneously stored in cache. When the limit is reached, the entries closest to expiring are evicted to make room, and a response body larger than the limit is never stored. `0` means no limit. | `1 * 1024 * 1024` (~1 MB) when `New` receives no `Config`, `0` when a `Config` leaves it unset | ### Default Config ```go var ConfigDefault = Config{ Next: nil, Expiration: 5 * time.Minute, CacheHeader: "X-Cache", DisableCacheControl: false, CacheInvalidator: nil, DisableValueRedaction: false, KeyGenerator: nil, // uses structured default key generator DisableQueryKeys: false, KeyHeaders: []string{ fiber.HeaderAccept, fiber.HeaderAcceptEncoding, fiber.HeaderAcceptLanguage, }, KeyCookies: nil, Methods: []string{fiber.MethodGet, fiber.MethodHead}, DisableVaryHeaders: false, ExpirationGenerator: nil, StoreResponseHeaders: false, Storage: nil, MaxBytes: 1 * 1024 * 1024, // 0 (no limit) once you pass your own Config and leave it unset } ``` --- ## Compress Compression middleware for [Fiber](https://github.com/gofiber/fiber) that automatically compresses responses with `gzip`, `deflate`, `brotli`, or `zstd` based on the client's [Accept-Encoding](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding) header. :::note Bodies smaller than 200 bytes remain uncompressed because compression would likely increase their size and waste CPU cycles. [See the fasthttp source](https://github.com/valyala/fasthttp/blob/497922a21ef4b314f393887e9c6147b8c3e3eda4/http.go#L1713-L1715). ::: ### Behavior - Skips compression for responses that already define `Content-Encoding`, for range requests, `206` responses, status codes without bodies, or when either side sends `Cache-Control: no-transform`. A request that repeats `Cache-Control` on several lines is read as one list (RFC 9110 §5.3), so the directive counts on any of them. - The encoding is negotiated from the `Accept-Encoding` header with its weights, wildcard and list syntax honored (RFC 9110 §12.5.3): the coding the client weighs highest wins, and among equal weights the middleware prefers `br`, then `zstd`, `gzip` and `deflate`. An element with no `q` parameter, or an unparsable one, carries the default weight of `1`. A request that repeats `Accept-Encoding` on several lines is read as one comma-separated list (RFC 9110 §5.3). A request without the header, or one that gives every supported coding a weight of `0`, is not compressed. The client's header is left untouched for later handlers. - `HEAD` requests are not compressed; only `Accept-Encoding` is merged into `Vary`. - When compression runs, strong `ETag` values are recomputed from the compressed bytes; a streamed body cannot be hashed without buffering it, so its strong `ETag` becomes a weak one (`W/`). When compression is skipped, `Accept-Encoding` is still merged into `Vary` unless the header is `*` or already present. - Request-body decompression is still handled by Fiber's request APIs (for example `c.Body()`), and those decoders enforce the app `BodyLimit` for compressed payloads. ### Signatures ```go func New(config ...Config) fiber.Handler ``` ### Examples Import the middleware package: ```go import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/compress" ) ``` Once your Fiber app is initialized, use the middleware like this: ```go // Initialize default config app.Use(compress.New()) // Or extend your config for customization app.Use(compress.New(compress.Config{ Level: compress.LevelBestSpeed, // 1 })) // Skip middleware for specific routes app.Use(compress.New(compress.Config{ Next: func(c fiber.Ctx) bool { return c.Path() == "/dont_compress" }, Level: compress.LevelBestSpeed, // 1 })) ``` ### Config | Property | Type | Description | Default | |:-------- |:-----------------------|:------------------------------------------------------------|:-------------------| | Next | `func(fiber.Ctx) bool` | Skips this middleware when the function returns `true`. | `nil` | | Level | `Level` | Compression level to use. | `LevelDefault (0)` | Possible values for the "Level" field are: - `LevelDisabled (-1)`: Compression is disabled. - `LevelDefault (0)`: Default compression level. - `LevelBestSpeed (1)`: Best compression speed. - `LevelBestCompression (2)`: Best compression. ### Default Config ```go var ConfigDefault = Config{ Next: nil, Level: LevelDefault, } ``` ### Constants ```go // Compression levels const ( LevelDisabled Level = -1 LevelDefault Level = 0 LevelBestSpeed Level = 1 LevelBestCompression Level = 2 ) ``` --- ## CORS CORS (Cross-Origin Resource Sharing) middleware for [Fiber](https://github.com/gofiber/fiber) lets servers control who can access resources and how. It isn't a security feature; it merely relaxes the browser's same-origin policy so cross-origin requests can succeed. Learn more on [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). It adds CORS headers to responses, listing allowed origins, methods, and headers, and handles preflight checks. Use the `AllowOrigins` option to define which origins may send cross-origin requests. It accepts single origins, lists, subdomain patterns, wildcards, and supports dynamic validation with `AllowOriginsFunc`. The middleware normalizes `AllowOrigins` (the scheme and host are lowercased and a trailing slash is stripped) and panics on an entry that is not a bare origin: the entry needs a host and may carry no userinfo, path, query, or fragment. Any scheme is accepted, not only HTTP and HTTPS, but the check does not require one: `example.com` panics, while `//example.com` is accepted and stored as `://example.com`, which no browser sends as an `Origin`, so it never matches a request. Always write the scheme out. The panic message redacts the misconfigured origin by default; set `DisableValueRedaction` to `true` if you need the raw value for troubleshooting. Avoid [common pitfalls](#common-pitfalls) such as using wildcard origins with credentials, overly permissive origin lists, or skipping validation with `AllowOriginsFunc`, as misconfiguration can create security risks. ### Signatures ```go func New(config ...Config) fiber.Handler ``` ### Examples Import the middleware package: ```go import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/cors" ) ``` Once your Fiber app is initialized, apply the middleware in one of the following ways: #### Basic usage To use the default configuration, simply use `cors.New()`. This will allow wildcard origins '*', the methods `GET, POST, HEAD, PUT, DELETE, PATCH, QUERY`, no credentials, and no exposed headers. Because `AllowHeaders` is empty, a preflight response echoes the headers listed in the request's `Access-Control-Request-Headers`. ```go app.Use(cors.New()) ``` #### Custom configuration (specific origins, headers, etc.) ```go // Initialize default config app.Use(cors.New()) // Or extend your config for customization app.Use(cors.New(cors.Config{ AllowOrigins: []string{"https://gofiber.io", "https://gofiber.net"}, AllowHeaders: []string{"Origin", "Content-Type", "Accept"}, })) ``` #### Dynamic origin validation You can use `AllowOriginsFunc` to programmatically determine whether to allow a request based on its origin. This is useful when you need to validate origins against a database or other dynamic sources. The function should return `true` if the origin is allowed, and `false` otherwise. Be sure to review the [security considerations](#security-considerations) when using `AllowOriginsFunc`. :::caution Never allow `AllowOriginsFunc` to return `true` for all origins. This is particularly crucial when `AllowCredentials` is set to `true`. Doing so can bypass the restriction of using a wildcard origin with credentials, exposing your application to serious security threats. If you need to allow wildcard origins, use `AllowOrigins` with a wildcard `"*"` instead of `AllowOriginsFunc`. ::: ```go // dbCheckOrigin checks if the origin is in the list of allowed origins in the database. func dbCheckOrigin(db *sql.DB, origin string) bool { // Placeholder query - adjust according to your database schema and query needs query := "SELECT COUNT(*) FROM allowed_origins WHERE origin = $1" var count int err := db.QueryRow(query, origin).Scan(&count) if err != nil { // Handle error (e.g., log it); for simplicity, we return false here return false } return count > 0 } // ... app.Use(cors.New(cors.Config{ AllowOriginsFunc: func(origin string) bool { return dbCheckOrigin(db, origin) }, })) ``` #### Prohibited usage The following example is prohibited because it can expose your application to security risks. It sets `AllowOrigins` to `"*"` (a wildcard) and `AllowCredentials` to `true`. ```go app.Use(cors.New(cors.Config{ AllowOrigins: []string{"*"}, AllowCredentials: true, })) ``` This will result in the following panic: ```text panic: [CORS] Configuration error: When 'AllowCredentials' is set to true, 'AllowOrigins' cannot contain a wildcard origin '*'. Please specify allowed origins explicitly or adjust 'AllowCredentials' setting. ``` ### Config | Property | Type | Description | Default | |:---------------------|:----------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------------------------------------| | AllowCredentials | `bool` | AllowCredentials indicates whether or not the response to the request can be exposed when the credentials flag is true. When used as part of a response to a preflight request, this indicates whether or not the actual request can be made using credentials. Note: If true, AllowOrigins cannot be set to a wildcard (`"*"`) to prevent security vulnerabilities. | `false` | | AllowHeaders | `[]string` | AllowHeaders defines a list of request headers that can be used when making the actual request. This is in response to a preflight request. If the list is empty, the preflight response echoes the request's `Access-Control-Request-Headers` instead, which allows every header the request names. A list whose only entry is an empty string (`[]string{""}`) counts as configured and allows no headers. | `[]` | | AllowMethods | `[]string` | AllowMethods defines a list of methods allowed when accessing the resource. This is used in response to a preflight request. | `"GET, POST, HEAD, PUT, DELETE, PATCH, QUERY"` | | AllowOrigins | `[]string` | AllowOrigins defines a list of origins that may access the resource. This supports subdomain matching, so you can use a value like "https://*.example.com" to allow any subdomain of example.com to submit requests. If the special wildcard `"*"` is present in the list, all origins will be allowed. | `["*"]` | | AllowOriginsFunc | `func(origin string) bool` | `AllowOriginsFunc` is a function that dynamically determines whether to allow a request based on its origin. If this function returns `true`, the 'Access-Control-Allow-Origin' response header will be set to the request's 'origin' header, provided that header is a serialized origin (scheme and host, without userinfo, path, query, or fragment) or the literal `null`. This function is only used if the request's origin doesn't match any origin in `AllowOrigins`. | `nil` | | AllowPrivateNetwork | `bool` | Indicates whether the `Access-Control-Allow-Private-Network` response header should be set to `true` on a preflight request that carries `Access-Control-Request-Private-Network: true`, allowing requests from private networks. This aligns with modern security practices for web applications interacting with private networks. | `false` | | DisableValueRedaction | `bool` | Disables redaction of the misconfigured origin in the panic message. | `false` | | ExposeHeaders | `[]string` | ExposeHeaders defines an allowlist of headers that clients are allowed to access. | `[]` | | MaxAge | `int` | MaxAge indicates how long (in seconds) the results of a preflight request can be cached. If you pass MaxAge 0, the Access-Control-Max-Age header will not be added and the browser will use 5 seconds by default. To disable caching completely, pass MaxAge value negative. It will set the Access-Control-Max-Age header to 0. | `0` | | Next | `func(fiber.Ctx) bool` | Next defines a function to skip this middleware when it returns true. | `nil` | :::note If AllowOrigins is a zero value `[]string{}`, and AllowOriginsFunc is provided, the middleware will not default to allowing all origins with the wildcard value "*". Instead, it will rely on the AllowOriginsFunc to dynamically determine whether to allow a request based on its origin. This provides more flexibility and control over which origins are allowed. ::: ### Default Config ```go var ConfigDefault = Config{ Next: nil, AllowOriginsFunc: nil, AllowOrigins: []string{"*"}, DisableValueRedaction: false, AllowMethods: []string{ fiber.MethodGet, fiber.MethodPost, fiber.MethodHead, fiber.MethodPut, fiber.MethodDelete, fiber.MethodPatch, fiber.MethodQuery, }, AllowHeaders: []string{}, AllowCredentials: false, ExposeHeaders: []string{}, MaxAge: 0, AllowPrivateNetwork: false, } ``` ### Subdomain Matching The `AllowOrigins` configuration supports matching subdomains at any level. This means you can use a value like `"https://*.example.com"` to allow any subdomain of `example.com` to submit requests, including multiple subdomain levels such as `"https://sub.sub.example.com"`. #### Example If you want to allow CORS requests from any subdomain of `example.com`, including nested subdomains, you can configure the `AllowOrigins` like so: ```go app.Use(cors.New(cors.Config{ AllowOrigins: []string{"https://*.example.com"}, })) ``` ### How It Works The CORS middleware works by adding the necessary CORS headers to responses from your Fiber application. These headers tell browsers what origins, methods, and headers are allowed for cross-origin requests. When a request arrives, the middleware first checks whether it is a preflight request—a CORS mechanism that determines if the actual request is safe to send. Preflight requests are HTTP OPTIONS requests with specific CORS headers. If the request is preflight, the middleware responds with the appropriate CORS headers and ends the request. :::note Preflight requests are typically sent by browsers before making actual cross-origin requests, especially for methods other than GET or POST, or when custom headers are used. A preflight request is an HTTP OPTIONS request that includes the `Origin`, `Access-Control-Request-Method`, and optionally `Access-Control-Request-Headers` headers. The browser sends this request to check if the server allows the actual request method and headers. ::: If the request is not preflight, the middleware adds the CORS headers to the response and passes the request to the next handler. The actual CORS headers added depend on the configuration of the middleware. The `AllowOrigins` option controls which origins can make cross-origin requests. The middleware handles different `AllowOrigins` configurations as follows: - **Single origin:** If `AllowOrigins` is set to a single origin like `"http://www.example.com"`, and that origin matches the origin of the incoming request, the middleware adds the header `Access-Control-Allow-Origin: http://www.example.com` to the response. - **Multiple origins:** If `AllowOrigins` is set to multiple origins like `[]string{"https://example.com", "https://www.example.com"}`, the middleware picks the origin that matches the origin of the incoming request. - **Subdomain matching:** If `AllowOrigins` includes `"https://*.example.com"`, a subdomain like `https://sub.example.com` will be matched and `"https://sub.example.com"` will be the header. This will also match `https://sub.sub.example.com` and so on, but not `https://example.com`. - **Wildcard origin:** If `AllowOrigins` is set to `"*"`, the middleware uses that and adds the header `Access-Control-Allow-Origin: *` to the response. In all cases above, except the **Wildcard origin**, the middleware will either add the `Access-Control-Allow-Origin` header to the response matching the origin of the incoming request, or it will not add the header at all if the origin is not allowed. - **Programmatic origin validation:**: The middleware also handles the `AllowOriginsFunc` option, which allows you to programmatically determine if an origin is allowed. If `AllowOriginsFunc` returns `true` for an origin, the middleware sets the `Access-Control-Allow-Origin` header to that origin. - **Null origin handling:** The middleware accepts the special literal value `"null"` as a valid origin. According to the [CORS specification](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS#origin), browsers send `"null"` as the origin for certain privacy-sensitive contexts, such as: - Requests from sandboxed iframes - Requests from `file://` URLs - Requests from `data:` URLs - Cross-origin redirects When using `AllowOriginsFunc`, if the function returns `true` for the literal string `"null"`, the middleware will set `Access-Control-Allow-Origin: null` in the response. The `"null"` origin is case-sensitive and must be lowercase. The `AllowMethods` option controls which HTTP methods are allowed. For example, if `AllowMethods` is set to `"GET, POST"`, the middleware adds the header `Access-Control-Allow-Methods: GET, POST` to the response. The `AllowHeaders` option specifies which headers are allowed in the actual request. The middleware sets the Access-Control-Allow-Headers response header to the value of `AllowHeaders`. This informs the client which headers it can use in the actual request. If `AllowHeaders` is empty, the middleware sets Access-Control-Allow-Headers to the value of the request's `Access-Control-Request-Headers` header (a header repeated on several lines is combined into one comma-separated list) and omits it when the request names no headers. List the headers you accept in `AllowHeaders` to avoid allowing every header a client asks for. The `AllowCredentials` option indicates whether the response to the request can be exposed when the credentials flag is true. If `AllowCredentials` is set to `true`, the middleware adds the header `Access-Control-Allow-Credentials: true` to the response. To prevent security vulnerabilities, `AllowCredentials` cannot be set to `true` if `AllowOrigins` is set to a wildcard (`*`). The `ExposeHeaders` option defines an allowlist of headers that clients are allowed to access. If `ExposeHeaders` is set to `"X-Custom-Header"`, the middleware adds the header `Access-Control-Expose-Headers: X-Custom-Header` to the response. The `MaxAge` option indicates how long the results of a preflight request can be cached. If `MaxAge` is set to `3600`, the middleware adds the header `Access-Control-Max-Age: 3600` to the response. The `Vary` header helps caches store the correct response. For simple requests the middleware sets `Vary: Origin` unless all origins are allowed. Preflight responses add `Vary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers` (and `Access-Control-Request-Private-Network` when enabled and requested). This ensures caches know when to reuse a response and when to revalidate with the server. ### Infrastructure Considerations When deploying Fiber applications behind infrastructure components like CDNs, API gateways, load balancers, or reverse proxies, you have two main options for handling CORS: #### Option 1: Use Infrastructure-Level CORS (Recommended) **For most production deployments, it is often preferable to handle CORS at the infrastructure level** rather than in your Fiber application. This approach offers several advantages: - **Better Performance**: CORS headers are added at the edge, closer to the client - **Reduced Server Load**: Preflight requests are handled without reaching your application - **Centralized Configuration**: Manage CORS policies alongside other infrastructure settings - **Built-in Caching**: Infrastructure providers optimize CORS response caching **Common infrastructure CORS solutions:** - **CDNs**: CloudFront, CloudFlare, Azure CDN - handle CORS at edge locations - **API Gateways**: AWS API Gateway, Google Cloud API Gateway - centralized CORS management - **Load Balancers**: Application Load Balancers with CORS rules - **Reverse Proxies**: Nginx, Apache with CORS modules If using infrastructure-level CORS, **disable Fiber's CORS middleware** to avoid conflicts: ```go // Don't use both - choose one approach // app.Use(cors.New()) // Remove this line when using infrastructure CORS ``` #### Option 2: Application-Level CORS (Fiber Middleware) Use Fiber's CORS middleware when you need: - **Dynamic origin validation** based on application logic - **Fine-grained control** over CORS policies per route - **Integration with application state** (database-driven origins, etc.) - **Development environments** where infrastructure CORS isn't available If choosing this approach, ensure that **all CORS headers reach your Fiber application unchanged**. #### Required Headers for CORS Preflight Requests For CORS preflight requests to work correctly, these headers **must not be stripped or modified by caching layers**: - `Origin` - Required to identify the requesting origin - `Access-Control-Request-Method` - Required to identify the HTTP method for the actual request - `Access-Control-Request-Headers` - Optional, contains custom headers the actual request will use - `Access-Control-Request-Private-Network` - Optional, for private network access requests The middleware matches these header names case-insensitively, so lowercase names such as the ones HTTP/2 clients send also work when `DisableHeaderNormalizing` is set. :::warning Critical Preflight Requirement If the `Access-Control-Request-Method` header is missing from an OPTIONS request, Fiber will not recognize them as CORS preflight requests. Instead, they'll be treated as regular OPTIONS requests, which typically return `405 Method Not Allowed` since most applications don't define explicit OPTIONS handlers. ::: #### CORS Response Headers (Set by Fiber) The middleware sets these response headers based on your configuration: **For all CORS requests:** - `Access-Control-Allow-Origin` - Set to the allowed origin or "*" - `Access-Control-Allow-Credentials` - Set to "true" when `AllowCredentials: true` - `Access-Control-Expose-Headers` - Lists headers the client can access - `Vary` - Set to "Origin" (unless wildcard origins are used) **For preflight responses only:** - `Access-Control-Allow-Methods` - Lists allowed HTTP methods - `Access-Control-Allow-Headers` - Lists allowed request headers (or echoes the request) - `Access-Control-Max-Age` - Cache duration for preflight results (if MaxAge > 0) - `Access-Control-Allow-Private-Network` - Set to "true" when `AllowPrivateNetwork` is enabled and the preflight carries `Access-Control-Request-Private-Network: true` - `Vary` - Set to "Access-Control-Request-Method, Access-Control-Request-Headers, Origin" #### Common Infrastructure Issues **CDNs (CloudFront, CloudFlare, etc.)**: - Configure cache policies to forward all CORS headers - Ensure OPTIONS requests are not cached inappropriately or cache them correctly with proper Vary headers - Don't strip or modify CORS request headers **API Gateways**: - Choose either gateway-level CORS OR application-level CORS, not both - If using gateway CORS, disable Fiber's CORS middleware - If forwarding to Fiber, ensure all headers pass through unchanged **Load Balancers/Reverse Proxies**: - Preserve all HTTP headers, especially CORS-related ones - Don't modify or strip `Origin`, `Access-Control-Request-*` headers **WAFs/Security Services**: - Whitelist CORS headers in security rules - Ensure OPTIONS requests with CORS headers aren't blocked #### Debugging CORS Issues Add this middleware **before** your CORS configuration to debug what headers Fiber receives: ```go // Debug middleware to log CORS preflight requests // Only use in development or testing environments app.Use(func(c fiber.Ctx) error { if c.Method() == "OPTIONS" { fmt.Printf("OPTIONS %s\n", c.Path()) fmt.Printf(" Origin: %s\n", c.Get("Origin")) fmt.Printf(" Access-Control-Request-Method: %s\n", c.Get("Access-Control-Request-Method")) fmt.Printf(" Access-Control-Request-Headers: %s\n", c.Get("Access-Control-Request-Headers")) } return c.Next() }) app.Use(cors.New(cors.Config{ AllowOrigins: []string{"https://yourdomain.com"}, AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, })) ``` Test CORS preflight directly with curl: ```bash # Test preflight request curl -X OPTIONS https://your-app.com/api/test \ -H "Origin: https://yourdomain.com" \ -H "Access-Control-Request-Method: POST" \ -H "Access-Control-Request-Headers: Content-Type" \ -v # Test simple CORS request curl -X GET https://your-app.com/api/test \ -H "Origin: https://yourdomain.com" \ -v ``` #### Caching Considerations The middleware sets appropriate `Vary` headers to ensure proper caching: - **Non-wildcard origins**: `Vary: Origin` is set to cache responses per origin - **Preflight requests**: `Vary: Access-Control-Request-Method, Access-Control-Request-Headers, Origin` - **OPTIONS without preflight headers**: `Vary: Origin` to avoid cache poisoning Ensure your infrastructure respects these `Vary` headers for correct caching behavior. #### Choosing the Right Approach | Scenario | Recommended Approach | |----------|---------------------| | Production with CDN/API Gateway | Infrastructure-level CORS | | Dynamic origin validation needed | Application-level CORS | | Microservices with different CORS policies | Application-level CORS | | Simple static origins | Infrastructure-level CORS | | Development/testing | Application-level CORS | | High traffic applications | Infrastructure-level CORS | :::tip Infrastructure CORS Configuration Most cloud providers offer comprehensive CORS documentation: - [AWS CloudFront CORS](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/header-caching.html#header-caching-web-cors) - [Google Cloud CORS](https://cloud.google.com/storage/docs/cross-origin) - [Azure CDN CORS](https://docs.microsoft.com/en-us/azure/cdn/cdn-cors) - [CloudFlare CORS](https://developers.cloudflare.com/fundamentals/get-started/reference/http-request-headers/#cf-connecting-ip) Configure CORS at the infrastructure level when possible for optimal performance and reduced complexity. ::: ### Security Considerations When configuring CORS, misconfiguration can potentially expose your application to various security risks. Here are some secure configurations and common pitfalls to avoid: #### Secure Configurations - **Specify Allowed Origins**: Instead of using a wildcard (`"*"`), specify the exact domains allowed to make requests. For example, `AllowOrigins: []string{"https://www.example.com", "https://api.example.com"}` ensures only these domains can make cross-origin requests to your application. - **Use Credentials Carefully**: If your application needs to support credentials in cross-origin requests, ensure `AllowCredentials` is set to `true` and specify exact origins in `AllowOrigins`. Do not use a wildcard origin in this case. - **Limit Exposed Headers**: Only allowlist headers that are necessary for the client-side application by setting `ExposeHeaders` appropriately. This minimizes the risk of exposing sensitive information. #### Common Pitfalls - **Wildcard Origin with Credentials**: Setting `AllowOrigins` to `"*"` (a wildcard) and `AllowCredentials` to `true` is a common misconfiguration. This combination is prohibited because it can expose your application to security risks. - **Overly Permissive Origins**: Specifying too many origins or using overly broad patterns (e.g., `https://*.example.com`) can inadvertently allow malicious sites to interact with your application. Be as specific as possible with allowed origins. - **Inadequate `AllowOriginsFunc` Validation**: When using `AllowOriginsFunc` for dynamic origin validation, ensure the function includes robust checks to prevent unauthorized origins from being accepted. Overly permissive validation can lead to security vulnerabilities. Never allow `AllowOriginsFunc` to return `true` for all origins. This is particularly crucial when `AllowCredentials` is set to `true`. Doing so can bypass the restriction of using a wildcard origin with credentials, exposing your application to serious security threats. If you need to allow wildcard origins, use `AllowOrigins` with a wildcard `"*"` instead of `AllowOriginsFunc`. Remember, the key to secure CORS configuration is specificity and caution. By carefully selecting which origins, methods, and headers are allowed, you can help protect your application from cross-origin attacks. --- ## CSRF The CSRF middleware protects against [Cross-Site Request Forgery](https://en.wikipedia.org/wiki/Cross-site_request_forgery) attacks by validating tokens on unsafe HTTP methods such as POST, PUT, and DELETE. It responds with 403 Forbidden when validation fails. Safe methods (`GET`, `HEAD`, `OPTIONS`, `TRACE`, `QUERY`) are not validated; note that `QUERY` is classified as safe per RFC 10008, so do not perform state changes in `QUERY` handlers. For protection without tokens, see [Cross-Origin Protection Without Tokens](#cross-origin-protection-without-tokens). ### Table of Contents - [Quick Start](#quick-start) - [Best Practices & Production Requirements](#best-practices--production-requirements) - [Configuration by Application Type](#configuration-by-application-type) - [Recipes for Common Use Cases](#recipes-for-common-use-cases) - [Using CSRF Tokens](#using-csrf-tokens) - [Security Model](#security-model) - [Token Extractors](#token-extractors) - [Advanced Configuration](#advanced-configuration) - [API Reference](#api-reference) - [Config Properties](#config-properties) - [Error Types](#error-types) - [Constants](#constants) ### Quick Start ```go import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/extractors" "github.com/gofiber/fiber/v3/middleware/csrf" ) // Default config (development only) app.Use(csrf.New()) // Production config app.Use(csrf.New(csrf.Config{ CookieName: "__Host-csrf_", CookieSecure: true, CookieHTTPOnly: true, // false for SPAs CookieSameSite: "Lax", CookieSessionOnly: true, Extractor: extractors.FromHeader("X-Csrf-Token"), Session: sessionStore, // Redaction is enabled by default. Set DisableValueRedaction when you must expose tokens or storage keys in diagnostics. // DisableValueRedaction: true, })) ``` ### Best Practices & Production Requirements :::danger Production Requirements - `CookieSecure: true` (HTTPS only) - `CookieSameSite: "Lax"` or `"Strict"` - Use `Session` store for better security ::: 1. **Always use HTTPS** in production 2. **Use sessions** for authenticated applications 3. **Set `CookieSecure: true`** and appropriate SameSite values 4. **Implement XSS protection** alongside CSRF 5. **Regenerate tokens** after auth changes 6. **Use `__Host-` cookie prefix** when possible :::warning BREACH Protection To mitigate BREACH attacks, ensure your pages are served over HTTPS, disable HTTP compression, and implement rate limiting for requests. The CSRF token is sent as a header on every request, so if you include the token in a page that is vulnerable to BREACH, an attacker may be able to extract the token. ::: ### Configuration by Application Type #### Server-Side Rendered Apps ```go app.Use(csrf.New(csrf.Config{ CookieName: "__Host-csrf_", CookieSecure: true, CookieHTTPOnly: true, // Secure - blocks JavaScript CookieSameSite: "Lax", CookieSessionOnly: true, Extractor: extractors.FromForm("_csrf"), Session: sessionStore, })) ``` #### Single Page Applications (SPAs) ```go app.Use(csrf.New(csrf.Config{ CookieName: "__Host-csrf_", CookieSecure: true, CookieHTTPOnly: false, // Required for JavaScript access to tokens CookieSameSite: "Lax", CookieSessionOnly: true, Extractor: extractors.FromHeader("X-Csrf-Token"), Session: sessionStore, })) ``` :::warning SPA Security Trade-off SPAs require `CookieHTTPOnly: false` to access tokens via JavaScript. This slightly increases XSS risk but is necessary for SPA functionality. ::: #### Cross-Origin Protection Without Tokens `csrf.NewCrossOriginProtection` is a separate, tokenless middleware. It rejects unsafe cross-origin browser requests by their [`Sec-Fetch-Site`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-Fetch-Site) and `Origin` headers, following Go's [`net/http.CrossOriginProtection`](https://pkg.go.dev/net/http#CrossOriginProtection). It sets no cookie, creates no token, and needs no storage or session, so the client has nothing to send back. `csrf.TokenFromContext` returns an empty string and `csrf.HandlerFromContext` returns `nil`, since this middleware creates neither. ```go app.Use(csrf.NewCrossOriginProtection(csrf.CrossOriginConfig{ TrustedOrigins: []string{"https://admin.example.com"}, // Exempt a route that must accept cross-origin posts, such as an SSO callback Next: func(c fiber.Ctx) bool { return c.Path() == "/sso/callback" }, })) ``` A request is allowed when one of these holds: 1. The method is `GET`, `HEAD`, or `OPTIONS`. 2. `Sec-Fetch-Site` is `same-origin` or `none`. 3. `Sec-Fetch-Site` and `Origin` are both absent. Such a request is same-origin or does not come from a browser. 4. `Sec-Fetch-Site` is absent and the host of `Origin` equals the `Host` header. 5. `Origin` is listed in `TrustedOrigins`. Every other request is passed to `ErrorHandler` with `ErrCrossOriginRequest`, which answers `403 Forbidden` by default. That includes `Sec-Fetch-Site: same-site`: a page on a sibling subdomain, such as `app.example.com` calling `api.example.com`, is cross-origin here, so list its origin in `TrustedOrigins`. :::caution Requests that carry neither header are allowed, so this middleware does not replace authentication or authorization. Do not perform state changes in `GET`, `HEAD`, or `OPTIONS` handlers. ::: :::warning Trusted origins are trusted completely With `csrf.New`, a request from a trusted origin still needs a valid token. Here a match is the entire check. A wildcard such as `https://*.example.com` extends it to every subdomain, including one that serves user content or one an attacker takes over through a dangling DNS record. Prefer exact origins. ::: Differences from `net/http.CrossOriginProtection`: - `TrustedOrigins` takes the same values as in `csrf.Config`: entries are normalized (case, trailing slash) but a default port is kept as written and matched exactly, subdomain wildcards are supported, and only `http` and `https` origins are accepted. An entry such as `capacitor://localhost` panics at startup. - There is no `AddInsecureBypassPattern`. Use `Next` to exempt routes. - A request that repeats the `Sec-Fetch-Site` header, or the `Origin` header where the decision depends on it, is denied. - Rule 4 never applies to a request without a `Host`. Shared with `net/http`, but different from `csrf.New`: - `TRACE` and `QUERY` are checked like any other unsafe method. - `Sec-Fetch-Site` and `Origin` are compared as sent. Browsers send both in lower case, so `Same-Origin` or `https://EXAMPLE.com` is denied. - Rule 4 compares hosts only. It ignores the scheme and does not fold default ports, so `Origin: http://example.com` passes on an HTTPS request to `example.com`. Current browsers send `Sec-Fetch-Site` on HTTPS requests and never get to rule 4 there; for older ones, enable HSTS, for example with the [Helmet](./helmet.md) middleware. - Rule 4 reads the `Host` header as sent. `X-Forwarded-Host` is not consulted, even behind a trusted proxy. If your proxy rewrites `Host`, add your public origin to `TrustedOrigins`. - The `Referer` header is not consulted. ### Recipes for Common Use Cases - **Without Sessions**: [CSRF Recipe](https://github.com/gofiber/recipes/tree/master/csrf) - Simple Double Submit Cookie pattern - **With Sessions**: [CSRF with Session Recipe](https://github.com/gofiber/recipes/tree/master/csrf-with-session) - More secure Synchronizer Token pattern ### Using CSRF Tokens #### Server-Side Forms ```go func formHandler(c fiber.Ctx) error { token := csrf.TokenFromContext(c) return c.SendString(fmt.Sprintf(`
`, token)) } ``` #### Single Page Applications ```go func apiHandler(c fiber.Ctx) error { token := csrf.TokenFromContext(c) return c.JSON(fiber.Map{ "csrf_token": token, "data": "your data", }) } ``` ```javascript // Get CSRF token from cookie function getCsrfToken() { const value = `; ${document.cookie}`; const parts = value.split(`; __Host-csrf_=`); if (parts.length === 2) return parts.pop().split(';').shift(); } // Use with fetch API async function makeRequest(url, data) { const csrfToken = getCsrfToken(); const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Csrf-Token': csrfToken }, body: JSON.stringify(data) }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } return response.json(); } ``` ### Security Model The middleware employs a robust, defense-in-depth strategy to protect against CSRF attacks. The primary defense is token-based validation, which operates in one of two modes depending on your configuration. This is supplemented by a mandatory secondary check on the request's origin. This section describes `csrf.New`; `csrf.NewCrossOriginProtection` validates neither tokens nor the `Referer` header, see [Cross-Origin Protection Without Tokens](#cross-origin-protection-without-tokens). #### Fetch Metadata Guardrails - **Sec-Fetch-Site**: For unsafe methods, the middleware inspects the [`Sec-Fetch-Site`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-Fetch-Site) header when present. If the header value (compared case-insensitively, ignoring surrounding spaces) is not one of "same-origin", "none", "same-site", or "cross-site", the request is rejected with `ErrFetchSiteInvalid`. If the header is valid or absent, the request proceeds to the standard origin and token validation checks. This provides an early check to block requests with invalid `Sec-Fetch-Site` values, while allowing legitimate same-site and cross-site requests to be validated by the existing mechanisms. #### 1. Token Validation Patterns ##### Double Submit Cookie (Default Mode) This is the default pattern, used when a `Session` store is **not** configured. It is a "semi-stateless" approach; while it doesn't tie tokens to a specific user session, the server still maintains a record of all validly issued tokens. - **How it Works:** 1. On a user's first visit (or a safe request like `GET`), the middleware generates a unique token. 2. This token is sent to the client in a `Set-Cookie` header. 3. The server also stores this token (in memory by default or in the configured `Storage`). It confirms the token is server-generated and still valid, but it is not tied to a specific user. 4. For subsequent unsafe requests (e.g., `POST`, `PUT`), the client must read the token from the cookie and echo it in a different location, such as the `X-Csrf-Token` header. - **Validation:** The middleware validates three things: that the token from the header/form **exactly matches** the token from the cookie, that the token **exists** in the server-side storage, and that it **has not expired**. - **Why it is secure:** Attackers on a malicious domain cannot read the victim's cookie to forge a matching header. They also cannot invent a token because it wouldn't exist in the server's storage registry. ##### Synchronizer Token (Session-Based Mode) This is a more secure, stateful pattern that is **automatically enabled** when you provide a `Session` store in the configuration. - **How it Works:** 1. A unique token is generated and stored directly within the user's session data on the server. 2. The token is also sent to the client as a cookie. 3. For unsafe requests, the client sends the token back in a header or form field. - **Validation:** The middleware performs a multi-step validation: 1. It first performs the standard **Double Submit Cookie check**: the token from the header/form must exactly match the token from the cookie. This is a fast and efficient first line of defense, and there is little benefit of skipping it. 2. It then validates that this token exists and is valid within the user's **server-side session**. This is the authoritative check that ties the token to the authenticated user. - **Why it is more secure:** Tying the token to the server-side session provides the strongest CSRF protection, as the token is then guaranteed to have been generated for the specific user. While browsers automatically send the required cookie, custom API clients must remember to include the cookie with their requests for validation to succeed. ```go // Enable the more secure Synchronizer Token pattern app.Use(csrf.New(csrf.Config{ Session: sessionStore, // Providing a session store activates this mode })) ``` #### 2. Origin & Referer Validation As a crucial second layer of defense, the middleware checks the origin of every unsafe request before it looks at the token. - If the request has an `Origin` header other than `null`, its scheme and host **must** match the scheme and host of the request or be explicitly allowed in the `TrustedOrigins` list. Default ports are treated as equal only in the comparison with the request (`example.com` and `example.com:443` match on HTTPS). A `TrustedOrigins` entry is matched as written: `https://a.example` does not match `Origin: https://a.example:443`, and `https://a.example:443` does not match an `Origin` without the port. Browsers send `Origin` without a default port, so write entries without one. This applies to HTTP and HTTPS requests alike. - If `Origin` is absent or `null` and the connection is HTTPS, the `Referer` header takes its place: it **must** be present and match in the same way. Over plain HTTP, a request with no `Origin` (or `Origin: null`) has nothing to compare and goes on to token validation. - A repeated `Origin` or `Sec-Fetch-Site` header, and a repeated `Referer` header when it is consulted, is rejected instead of being resolved to one of its lines. - The scheme and host of the request come from `c.Scheme()` and `c.Host()`. Behind a reverse proxy, `X-Forwarded-Proto` and `X-Forwarded-Host` are used only for requests from a proxy that `TrustProxy` and `TrustProxyConfig` trust. - This check is performed *in addition* to token validation and provides strong protection because these headers are reliably set by browsers and cannot be programmatically controlled by an attacker from a malicious site. ### Token Extractors This middleware uses the shared `extractors` package for token extraction. For full details on extractor types, chaining, security, and advanced usage, see the [Extractors Guide](../guide/extractors). **Extractor Source Constants:** Extractor source constants (such as `SourceHeader`, `SourceForm`, etc.) are defined in the shared extractors package, not in the CSRF middleware itself. Refer to the Extractors Guide for their definitions and usage. #### CSRF-Specific Extractor Notes For CSRF protection, prefer secure extraction methods: - **Headers** (`extractors.FromHeader("X-Csrf-Token")`) – Most secure, not logged in URLs - **Form data** (`extractors.FromForm("_csrf")`) – Secure for form submissions. It also accepts a value in the query string (see [Extractor Behavior](../guide/extractors#extractor-behavior)), so submit the token in the form body - **Avoid URL parameters** – Query/param extractors expose tokens in logs and browser history :::note What about cookies? **Cookies are generally not a secure source for CSRF tokens.** The middleware will panic if you configure an extractor that reads from cookies with the same name as your CSRF cookie. This is because reading the CSRF token from a cookie with the same name as the CSRF cookie defeats CSRF protection entirely, as the extracted token will always match the cookie value, allowing any CSRF attack to succeed. **Advanced usage:** In rare cases, you may securely extract a CSRF token from a cookie if: - You read from a different cookie (not the CSRF cookie itself) - You use multiple cookies for custom validation - You implement custom logic across different cookie sources If you do this, set the extractor’s `Source` to `SourceCookie` and use a cookie name that differs from your CSRF cookie. The middleware accepts a different cookie and panics only when an extractor with `Source` set to `SourceCookie` has a `Key` equal to the CSRF cookie name. A name that differs only in letter case is accepted too, with a logged warning. **Warning:** Cookie-based extraction is strongly discouraged, as it is easy to misconfigure and creates security risks. Prefer extracting tokens from headers or form fields for robust CSRF protection. See the [Extractors Guide](../guide/extractors#security-considerations) for more details. ::: #### Route-Specific Configuration You can configure different extraction methods for different routes: ```go // API routes - header extraction for AJAX/fetch requests api := app.Group("/api") api.Use(csrf.New(csrf.Config{ Extractor: extractors.FromHeader("X-Csrf-Token"), })) // Form routes - form field extraction for traditional forms forms := app.Group("/forms") forms.Use(csrf.New(csrf.Config{ Extractor: extractors.FromForm("_csrf"), })) ``` #### Custom CSRF Extractors For specialized CSRF token extraction needs, you can create custom extractors. See the [Extractors Guide](../guide/extractors#custom-extraction-logic) for advanced patterns and security notes. :::danger Never Extract from Cookies **NEVER create custom extractors that read from cookies using the same `CookieName` as your CSRF configuration.** This completely defeats CSRF protection by making the extracted token always match the cookie value, allowing any CSRF attack to succeed. ```go // ❌ NEVER DO THIS - Completely defeats CSRF protection badExtractor := extractors.Extractor{ Extract: func(c fiber.Ctx) (string, error) { return c.Cookies("csrf_"), nil // Always passes validation! }, Source: extractors.SourceCustom, Key: "csrf_", } // ✅ DO THIS - Extract from different source than cookie app.Use(csrf.New(csrf.Config{ CookieName: "csrf_", Extractor: extractors.FromHeader("X-Csrf-Token"), // Header vs cookie comparison })) ``` The middleware uses the **Double Submit Cookie** pattern – it compares the extracted token against the cookie value. If you configure an extractor that reads from the same cookie, it will panic because they will always match and provide zero CSRF protection. The panic is based on the extractor's declared `Source` and `Key`, so it only catches an extractor with `Source` set to `extractors.SourceCookie` and `Key` equal to the CSRF cookie name, such as `extractors.FromCookie("csrf_")`, also when it is nested in a `Chain`. The middleware cannot look inside a custom `Extract` function, so `badExtractor` above is accepted without a panic and offers no protection. ::: ##### Bearer Token Embedding & Custom Extractors You can create advanced extractors for use cases like JWT embedding or JSON body parsing. See the [Extractors Guide](../guide/extractors#custom-extraction-logic) for secure implementation patterns and more examples. #### Fallback Extraction For applications that need to support both AJAX and form submissions: ```go // Try header first (AJAX), fall back to form (traditional forms) app.Use(csrf.New(csrf.Config{ Extractor: extractors.Chain( extractors.FromHeader("X-Csrf-Token"), extractors.FromForm("_csrf"), ), })) ``` :::warning Chaining extractors increases complexity. Use only when you need to support multiple client types. See the [Extractors Guide](../guide/extractors#chain-ordering-strategy) for details and security notes. ::: ### Advanced Configuration #### Trusted Origins ```go app.Use(csrf.New(csrf.Config{ TrustedOrigins: []string{ "https://trusted.example.com", "https://*.example.com", // Wildcard subdomains }, })) ``` #### Custom Error Handler ```go app.Use(csrf.New(csrf.Config{ ErrorHandler: func(c fiber.Ctx, err error) error { accepts := c.Accepts("html", "json") path := c.Path() if accepts == "json" || strings.HasPrefix(path, "/api/") { return c.Status(fiber.StatusForbidden).JSON(fiber.Map{ "error": "Forbidden", }) } return c.Status(fiber.StatusForbidden).Render("error", fiber.Map{ "Title": "Forbidden", "Status": fiber.StatusForbidden, }, "layouts/main") }, })) ``` #### Custom Storage/Database You can use any storage from our [storage](https://github.com/gofiber/storage/) package. ```go storage := sqlite3.New() // From github.com/gofiber/storage/sqlite3/v2 app.Use(csrf.New(csrf.Config{ Storage: storage, })) ``` #### Token Management ```go // Delete token (e.g., on logout) handler := csrf.HandlerFromContext(c) if handler != nil { if err := handler.DeleteToken(c); err != nil { // handle error, e.g. log it } } // With session middleware // Destroying the session will also remove the CSRF token if using session-based CSRF. if s := session.FromContext(c); s != nil { if err := s.Destroy(); err != nil { // handle error, e.g. log it } } ``` ### API Reference ```go // Create middleware func New(config ...csrf.Config) fiber.Handler // Create tokenless middleware func NewCrossOriginProtection(config ...csrf.CrossOriginConfig) fiber.Handler // Get token from context func TokenFromContext(ctx any) string // Get handler from context func HandlerFromContext(ctx any) *csrf.Handler // Delete token func (h *csrf.Handler) DeleteToken(c fiber.Ctx) error ``` `TokenFromContext` and `HandlerFromContext` accept a `fiber.CustomCtx`, `fiber.Ctx`, a `*fasthttp.RequestCtx`, or a `context.Context`. ### Config Properties | Property | Type | Description | Default | |:------------------|:-----------------------------------|:------------------------------------------------------------------------------------------------------------------------------|:-----------------------------| | Next | `func(fiber.Ctx) bool` | Skip middleware when returns true | `nil` | | CookieName | `string` | CSRF cookie name | `"csrf_"` | | CookieDomain | `string` | CSRF cookie domain | `""` | | CookiePath | `string` | CSRF cookie path | `""` | | CookieSecure | `bool` | HTTPS only cookie (**required for production**) | `false` | | CookieHTTPOnly | `bool` | Prevent JavaScript access (**use `false` for SPAs**) | `false` | | CookieSameSite | `string` | SameSite attribute (**use "Lax" or "Strict"**) | `"Lax"` | | CookieSessionOnly | `bool` | Session-only cookie (expires on browser close) | `false` | | IdleTimeout | `time.Duration` | Token expiration time | `30 * time.Minute` | | KeyGenerator | `func() string` | Token generation function | `utils.SecureToken` | | ErrorHandler | `fiber.ErrorHandler` | Custom error handler | `defaultErrorHandler` | | Extractor | `extractors.Extractor` | Token extraction method with metadata | `extractors.FromHeader("X-Csrf-Token")` | | DisableValueRedaction | `bool` | Disables redaction of tokens and storage keys in logs and error messages. | `false` | | Session | `*session.Store` | Session store (**recommended for production**) | `nil` | | Storage | `fiber.Storage` | Token storage (ignored if Session is set) | In-memory store | | TrustedOrigins | `[]string` | Trusted origins for cross-origin requests. Entries must be `http` or `https` origins without a path, such as `https://app.example.com`. `https://*.example.com` matches subdomains at any depth, but not `example.com` itself. An entry is matched as written, so a default port (`https://app.example.com:443`) matches only an `Origin` that carries it. An invalid entry panics at startup. | `[]` | | SingleUseToken | `bool` | Generate new token after each use | `false` | #### Default Config ```go var ConfigDefault = Config{ CookieName: "csrf_", CookieSameSite: "Lax", IdleTimeout: 30 * time.Minute, KeyGenerator: utils.SecureToken, ErrorHandler: defaultErrorHandler, // returns fiber.ErrForbidden Extractor: extractors.FromHeader(HeaderName), DisableValueRedaction: false, } ``` When you pass a `Config`, empty or zero values for `CookieName`, `CookieSameSite`, `IdleTimeout`, `KeyGenerator`, `ErrorHandler`, and `Extractor` are replaced with these defaults. #### CrossOriginConfig Properties | Property | Type | Description | Default | |:---------------|:-----------------------|:-----------------------------------------------------|:----------------------| | Next | `func(fiber.Ctx) bool` | Skip middleware when returns true | `nil` | | ErrorHandler | `fiber.ErrorHandler` | Custom error handler | `defaultErrorHandler` | | TrustedOrigins | `[]string` | Origins allowed to send unsafe cross-origin requests | `[]` | ### Error Types ```go var ( ErrTokenNotFound = errors.New("csrf: token not found") ErrTokenInvalid = errors.New("csrf: token invalid") ErrFetchSiteInvalid = errors.New("csrf: sec-fetch-site header invalid") ErrRefererNotFound = errors.New("csrf: referer header missing") ErrRefererInvalid = errors.New("csrf: referer header invalid") ErrRefererNoMatch = errors.New("csrf: referer does not match host or trusted origins") ErrOriginInvalid = errors.New("csrf: origin header invalid") ErrOriginNoMatch = errors.New("csrf: origin does not match host or trusted origins") ) // Returned by NewCrossOriginProtection var ErrCrossOriginRequest = errors.New("csrf: cross-origin request denied") ``` ### Constants ```go const ( HeaderName = "X-Csrf-Token" ) ``` --- ## EarlyData The Early Data middleware adds TLS 1.3 "0-RTT" support to [Fiber](https://github.com/gofiber/fiber). When the client and server share a PSK, TLS 1.3 lets the client send data with the first flight and skip the initial round trip. Enable Fiber's `TrustProxy` option before using this middleware to avoid spoofed client headers. When `TrustProxy` is disabled (the default) or the remote address is not trusted by your proxy configuration, requests carrying the `Early-Data` header are rejected with the configured `Error` (`425 Too Early` by default) to prevent 0-RTT spoofing from direct clients. Enabling early data in a reverse proxy (for example, `ssl_early_data on;` in nginx) makes requests replayable. Review these resources before proceeding: - [datatracker](https://datatracker.ietf.org/doc/html/rfc8446#section-8) - [trailofbits](https://blog.trailofbits.com/2019/03/25/what-application-developers-need-to-know-about-tls-early-data-0rtt) By default, the middleware permits early data only for safe methods (`GET`, `HEAD`, `OPTIONS`, `TRACE`, `QUERY`) and rejects other requests before your handler runs. Override this behavior with the `AllowEarlyData` option. ### Signatures ```go func New(config ...Config) fiber.Handler func IsEarly(c fiber.Ctx) bool ``` `IsEarly` returns `true` when a request used early data and the middleware allowed it to proceed. ### Examples Import the middleware package: ```go import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/earlydata" ) ``` Once your Fiber app is initialized, use the middleware like this: ```go // Initialize default config app.Use(earlydata.New()) // Or extend your config for customization app.Use(earlydata.New(earlydata.Config{ Error: fiber.ErrTooEarly, // ... })) ``` ### Config | Property | Type | Description | Default | |:---------------|:------------------------|:-----------|:-------------------------------------------------------| | Next | `func(fiber.Ctx) bool` | Skip this middleware when the function returns true. | `nil` | | IsEarlyData | `func(fiber.Ctx) bool` | Reports whether the request used early data. | Function checking if "Early-Data" header equals "1" | | AllowEarlyData | `func(fiber.Ctx) bool` | Decides if an early-data request should be allowed. | Function rejecting on unsafe and allowing safe methods | | Error | `error` | Returned when an early-data request is rejected. | `fiber.ErrTooEarly` | ### Default Config ```go var ConfigDefault = Config{ IsEarlyData: func(c fiber.Ctx) bool { return c.Get(DefaultHeaderName) == DefaultHeaderTrueValue }, AllowEarlyData: func(c fiber.Ctx) bool { return c.IsSafe() }, Error: fiber.ErrTooEarly, } ``` ### Constants ```go const ( DefaultHeaderName = "Early-Data" DefaultHeaderTrueValue = "1" ) ``` --- ## Encrypt Cookie The Encrypt Cookie middleware for [Fiber](https://github.com/gofiber/fiber) encrypts cookie values for secure storage. :::note This middleware encrypts cookie values but not cookie names. ::: ### Signatures ```go // Initializes the middleware func New(config ...Config) fiber.Handler // GenerateKey returns a random string of 16, 24, or 32 bytes. // The length of the key determines the AES encryption algorithm used: // 16 bytes for AES-128, 24 bytes for AES-192, and 32 bytes for AES-256-GCM. func GenerateKey(length int) string // EncryptCookie and DecryptCookie are the default Encryptor and Decryptor. func EncryptCookie(name, value, key string) (string, error) func DecryptCookie(name, value, key string) (string, error) ``` `GenerateKey` returns the base64 encoding of `length` random bytes, which is the form `Config.Key` expects, and panics with `ErrInvalidKeyLength` for any other length. `New` panics when `Key` is empty, is not valid standard base64 (padding included), or does not decode to 16, 24, or 32 bytes (`ErrInvalidKeyLength`). It checks the key even when you supply your own `Encryptor` and `Decryptor`. ### Examples Import the middleware package: ```go import ( "os" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/encryptcookie" ) ``` Once your Fiber app is initialized, register the middleware: ```go // Provide a minimal configuration app.Use(encryptcookie.New(encryptcookie.Config{ // A base64-encoded key of 16, 24, or 32 bytes, for example from `openssl rand -base64 32` Key: os.Getenv("COOKIE_KEY"), })) // Retrieve the encrypted cookie value app.Get("/", func(c fiber.Ctx) error { return c.SendString("value=" + c.Cookies("test")) }) // Create an encrypted cookie app.Post("/", func(c fiber.Ctx) error { c.Cookie(&fiber.Cookie{ Name: "test", Value: "SomeThing", }) return nil }) ``` :::note Use an encoded key of 16, 24, or 32 bytes to select AES‑128, AES‑192, or AES‑256‑GCM. Generate a stable key with `openssl rand -base64 32` or `encryptcookie.GenerateKey(32)` and store it securely. Generating a new key on each startup renders existing cookies unreadable. ::: ### Config | Property | Type | Description | Default | |:----------|:----------------------------------------------------|:------------------------------------------------------------------------------------------------------|:-----------------------------| | Next | `func(fiber.Ctx) bool` | A function to skip this middleware when it returns true. | `nil` | | Except | `[]string` | Array of cookie keys that should not be encrypted. | `[]` | | Key | `string` | A base64-encoded unique key to encode & decode cookies. Required. The decoded key must be 16, 24, or 32 bytes long; `New` panics otherwise. | (No default, required field) | | Encryptor | `func(name, decryptedString, key string) (string, error)` | A custom function to encrypt cookies. | `EncryptCookie` | | Decryptor | `func(name, encryptedString, key string) (string, error)` | A custom function to decrypt cookies. | `DecryptCookie` | #### Encryptor and Decryptor parameters Custom encryptor and decryptor functions receive three arguments: - `name`: The cookie name. The default helpers bind this value as additional authenticated data (AAD) so encrypted values can only be decrypted for the same cookie. - `string`: The cookie payload. `EncryptCookie` accepts the decrypted value and returns ciphertext, while `DecryptCookie` receives ciphertext and must return the decrypted value. - `key`: The base64-encoded key pulled from the middleware configuration. Use it to derive or validate any encryption keys your implementation requires. A request cookie for which the `Decryptor` returns an error, such as a tampered value, a cookie that was never encrypted, or one sealed with another key, is removed from the request, so handlers see it as absent. When a request carries the same cookie name more than once, for example set under two paths or domains, the handler sees a single entry for that name holding the first value that decrypts; the other entries, including any that do not decrypt, are dropped. Names listed in `Except` keep every entry as it arrived. When the `Encryptor` returns an error for a response cookie, the middleware removes every response cookie that is not listed in `Except` and returns that error, joined with any error from the rest of the chain. The default helpers return `ErrInvalidEncryptedValue` for a payload too short to hold the nonce and authentication tag, and `ErrInvalidKeyLength` for a key that does not decode to 16, 24, or 32 bytes. ### Default Config ```go var ConfigDefault = Config{ Next: nil, Except: []string{}, Key: "", Encryptor: EncryptCookie, Decryptor: DecryptCookie, } ``` ### Use with Other Middleware That Reads or Modifies Cookies Place `encryptcookie` before middleware that reads or writes cookies. If you use the CSRF middleware, register `encryptcookie` first so it can read the token. Exclude cookies from encryption by listing them in `Except`. If a frontend framework such as Angular reads the CSRF token from a cookie, add that name to the `Except` array: ```go import ( "os" "github.com/gofiber/fiber/v3/extractors" "github.com/gofiber/fiber/v3/middleware/csrf" "github.com/gofiber/fiber/v3/middleware/encryptcookie" ) app.Use(encryptcookie.New(encryptcookie.Config{ Key: os.Getenv("COOKIE_KEY"), Except: []string{csrf.ConfigDefault.CookieName}, // exclude CSRF cookie })) app.Use(csrf.New(csrf.Config{ Extractor: extractors.FromHeader(csrf.HeaderName), CookieSameSite: "Lax", CookieSecure: true, CookieHTTPOnly: false, })) ``` ### Encryption Algorithms The default Encryptor and Decryptor functions use `AES-256-GCM` for encryption and decryption. If you need to use `AES-128` or `AES-192` instead, you can do so by changing the length of the key when calling `encryptcookie.GenerateKey(length)` or by providing a key of one of the following lengths: - AES-128 requires a 16-byte key. - AES-192 requires a 24-byte key. - AES-256 requires a 32-byte key. For example, to generate a key for AES-128: ```go key := encryptcookie.GenerateKey(16) ``` And for AES-192: ```go key := encryptcookie.GenerateKey(24) ``` --- ## EnvVar EnvVar middleware for [Fiber](https://github.com/gofiber/fiber) exposes environment variables with configurable options. ### Signatures ```go func New(config ...Config) fiber.Handler ``` ### Examples Import the middleware package: ```go import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/envvar" ) ``` Once your Fiber app is initialized, configure the middleware as shown: ```go // Initialize default config (exports no variables) app.Use("/expose/envvars", envvar.New()) // Or extend your config for customization app.Use("/expose/envvars", envvar.New( envvar.Config{ ExportVars: map[string]string{"testKey": "", "testDefaultKey": "testDefaultVal"}, }), ) ``` :::note Mount the middleware on a path; it cannot be used without one. ::: Only `GET` and `HEAD` requests are served. Any other method receives `405 Method Not Allowed` with an `Allow: GET, HEAD` header. ### Response Sample response: ```json { "vars": { "someEnvVariable": "someValue", "anotherEnvVariable": "anotherValue" } } ``` ### Config | Property | Type | Description | Default | |:------------|:--------------------|:-----------------------------------------------------------------------------|:--------| | ExportVars | `map[string]string` | ExportVars lists the environment variables to expose. Each key is a variable name and each value is the default reported when that variable is not set. | `map[string]string{}` | ### Default Config ```go var ConfigDefault = Config{ ExportVars: map[string]string{}, } // Exports no environment variables ``` --- ## ETag ETag middleware for [Fiber](https://github.com/gofiber/fiber) that helps caches validate responses and saves bandwidth by avoiding full retransmits when content is unchanged. ### Signatures ```go func New(config ...Config) fiber.Handler func Generate(body []byte) []byte func GenerateWeak(body []byte) []byte ``` `Generate` and `GenerateWeak` build the validator the middleware sets, quotes included (`"13-1831710635"` and `W/"13-1831710635"` for `Hello, World!`), from the body length and a CRC-32 checksum of the body, so a handler can compute one itself. Both return `nil` for a body longer than `math.MaxUint32` bytes. ### Examples Import the middleware package: ```go import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/etag" ) ``` Once your Fiber app is initialized, use the middleware like this: ```go // Initialize default config app.Use(etag.New()) // GET / -> ETag: "13-1831710635" app.Get("/", func(c fiber.Ctx) error { return c.SendString("Hello, World!") }) // Or extend your config for customization app.Use(etag.New(etag.Config{ Weak: true, })) // GET / -> ETag: W/"13-1831710635" app.Get("/", func(c fiber.Ctx) error { return c.SendString("Hello, World!") }) ``` The middleware sets an `ETag` only on a `200` response with a non-empty body. It skips the response, without comparing `If-None-Match`, when the handler already set an `ETag` (in any letter case) and for Server-Sent Events (`text/event-stream`) responses, whose body it would otherwise have to buffer. A matching `If-None-Match` yields `304 Not Modified` only for `GET` and `HEAD` requests, the methods RFC 9110 defines it for. Other methods pass through unchanged, with the `ETag` header still set. They are not answered with `412 Precondition Failed`: the middleware runs after the handler, over the response it produced, so the method has already been applied and the status whose purpose is to prevent that would misreport what happened. Entity tags in requests must be quoted per RFC 9110. For example: ```text If-None-Match: "example-etag" ``` ### Config | Property | Type | Description | Default | |:---------|:------------------------|:-------------------------------------------------------------------------------------------------------------------|:--------| | Weak | `bool` | Enables weak validators. Weak ETags are easier to generate but less reliable for comparisons. | `false` | | Next | `func(fiber.Ctx) bool` | Next defines a function to skip this middleware when it returns true. | `nil` | ### Default Config ```go var ConfigDefault = Config{ Next: nil, Weak: false, } ``` --- ## ExpVar The ExpVar middleware exposes runtime variables over HTTP in JSON. Using it (e.g., `app.Use(expvarmw.New())`) registers handlers on `/debug/vars`. ### Signatures ```go func New(config ...Config) fiber.Handler ``` ### Examples Import the middleware package: ```go import ( "expvar" "fmt" "github.com/gofiber/fiber/v3" expvarmw "github.com/gofiber/fiber/v3/middleware/expvar" ) ``` Once your Fiber app is initialized, use the middleware as shown: ```go var count = expvar.NewInt("count") app.Use(expvarmw.New()) app.Get("/", func(c fiber.Ctx) error { count.Add(1) return c.SendString(fmt.Sprintf("hello expvar count %d", count.Value())) }) ``` Visit `/debug/vars` to see all variables, and append `?r=` to keep only the variables whose names match the regular expression; an invalid expression is answered with `400 Bad Request`. Any path beneath `/debug/vars/` is redirected to `/debug/vars` with `303 See Other`, and every other path continues down the handler chain. ```bash curl 127.0.0.1:3000 hello expvar count 1 curl 127.0.0.1:3000/debug/vars { "cmdline": ["xxx"], "count": 1, "expvarHandlerCalls": 33, "expvarRegexpErrors": 0, "memstats": {...} } curl 127.0.0.1:3000/debug/vars?r=c { "cmdline": ["xxx"], "count": 1 } ``` ### Config | Property | Type | Description | Default | |:---------|:------------------------|:--------------------------------------------------------------------|:--------| | Next | `func(fiber.Ctx) bool` | Next defines a function to skip this middleware when it returns true. | `nil` | ### Default Config ```go var ConfigDefault = Config{ Next: nil, } ``` --- ## Favicon Favicon middleware for [Fiber](https://github.com/gofiber/fiber) that drops repeated `/favicon.ico` requests or serves a cached icon from memory. Mount it before your logger to suppress noisy requests and avoid disk reads. It handles only `GET`, `HEAD`, and `OPTIONS` to the configured URL; other methods return `405 Method Not Allowed`. A configured icon is always served as `image/x-icon` with the configured `Cache-Control`; without `File` or `Data`, requests for the URL are answered with `204 No Content`. :::note This middleware only serves the default `/favicon.ico` (or a [custom URL](#config)). For multiple icons, use the Static middleware. ::: ### Signatures ```go func New(config ...Config) fiber.Handler ``` ### Examples Import the middleware package: ```go import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/favicon" ) ``` Once your Fiber app is initialized, use the middleware like this: ```go // Initialize default config app.Use(favicon.New()) // Or extend your config for customization app.Use(favicon.New(favicon.Config{ File: "./favicon.ico", URL: "/favicon.ico", })) ``` ### Config | Property | Type | Description | Default | |:-------------|:------------------------|:---------------------------------------------------------------------------------|:---------------------------| | Next | `func(fiber.Ctx) bool` | Next defines a function to skip this middleware when it returns true. | `nil` | | Data | `[]byte` | Raw data of the favicon file. This can be used instead of `File`. | `nil` | | File | `string` | File holds the path to an actual favicon that will be cached. `New` panics if the file cannot be read. | "" | | URL | `string` | URL for favicon handler. | "/favicon.ico" | | FileSystem | `fs.FS` | FileSystem is an optional alternate filesystem from which to load the favicon file (e.g. using `os.DirFS` or an `embed.FS`). | `nil` | | CacheControl | `string` | CacheControl defines how the Cache-Control header in the response should be set. | "public, max-age=31536000" | | MaxBytes | `int64` | MaxBytes limits the maximum size of the cached favicon asset. `New` panics if the file at `File` is larger; the limit does not apply to `Data`. | `1048576` | ### Default Config ```go var ConfigDefault = Config{ Next: nil, File: "", URL: fPath, CacheControl: "public, max-age=31536000", MaxBytes: 1024 * 1024, } ``` --- ## Health Check Middleware that adds liveness, readiness, and startup probes to [Fiber](https://github.com/gofiber/fiber) apps. It provides a generic handler you can mount on any route, with constants for the conventional `/livez`, `/readyz`, and `/startupz` endpoints. ### Overview Register the middleware on any endpoint you want to expose a probe on. The package exports constants for the conventional liveness, readiness, and startup endpoints: ```go app.Get(healthcheck.LivenessEndpoint, healthcheck.New()) app.Get(healthcheck.ReadinessEndpoint, healthcheck.New()) app.Get(healthcheck.StartupEndpoint, healthcheck.New()) ``` By default the probe returns `true`, so each endpoint responds with `200 OK`; returning `false` yields `503 Service Unavailable`. The default response format is plain text, but you can configure the middleware to return responses in JSON, XML, MessagePack, or CBOR formats. - **Liveness**: Checks if the server is running. - **Readiness**: Checks if the application is ready to handle requests. - **Startup**: Checks if the application has completed its startup sequence. ### Signatures ```go func New(config ...Config) fiber.Handler ``` ### Examples Import the middleware package: ```go import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/healthcheck" ) ``` After your app is initialized, register the middleware on the endpoints you want to expose: ```go // Use the default probe on the conventional endpoints app.Get(healthcheck.LivenessEndpoint, healthcheck.New()) app.Get(healthcheck.ReadinessEndpoint, healthcheck.New(healthcheck.Config{ Probe: func(c fiber.Ctx) bool { return serviceA.Ready() && serviceB.Ready() }, })) app.Get(healthcheck.StartupEndpoint, healthcheck.New()) // Register a custom endpoint app.Get("/healthz", healthcheck.New()) ``` The middleware responds to GET and HEAD, where HEAD returns the same status code without a body. Use `app.All` to expose a probe on every method; other methods fall through to the next handler: ```go app.All("/healthz", healthcheck.New()) ``` #### Response Formats You can configure the response format using the `ResponseFormat` field in the config: ```go import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/healthcheck" ) // JSON format app.Get(healthcheck.LivenessEndpoint, healthcheck.New(healthcheck.Config{ ResponseFormat: healthcheck.FormatJSON, })) // Response: {"status":"OK"} // XML format app.Get(healthcheck.ReadinessEndpoint, healthcheck.New(healthcheck.Config{ ResponseFormat: healthcheck.FormatXML, })) // Response: OK ``` When the probe returns `false`, the status code is `503` and the body says `Service Unavailable` instead of `OK` in the chosen format, for example `{"status":"Service Unavailable"}` for JSON or the plain text `Service Unavailable`. **Note:** MessagePack and CBOR formats require configuring the appropriate encoders in your Fiber app: ```go import ( "github.com/fxamacker/cbor/v2" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/healthcheck" "github.com/shamaton/msgpack/v3" ) app := fiber.New(fiber.Config{ MsgPackEncoder: msgpack.Marshal, CBOREncoder: cbor.Marshal, }) app.Get(healthcheck.LivenessEndpoint, healthcheck.New(healthcheck.Config{ ResponseFormat: healthcheck.FormatMsgPack, })) ``` ### Config ```go type Config struct { // Next defines a function to skip this middleware when it returns true. If this function returns true // and no other handlers are defined for the route, Fiber will return a status 404 Not Found, since // no other handlers were defined to return a different status. // // Optional. Default: nil Next func(fiber.Ctx) bool // Probe is executed to determine the current health state. It can be used for // liveness, readiness or startup checks. Returning true indicates the application // is healthy. // // Optional. Default: func(c fiber.Ctx) bool { return true } Probe func(fiber.Ctx) bool // ResponseFormat specifies the format of the healthcheck response. // Supported formats: Text (default), JSON, XML, MsgPack, CBOR. // // Optional. Default: FormatText ResponseFormat ResponseFormat } ``` #### Response Format Constants ```go type ResponseFormat int const ( FormatText ResponseFormat = iota // Plain text response (default) FormatJSON // JSON response FormatXML // XML response FormatMsgPack // MessagePack response FormatCBOR // CBOR response ) ``` ### Default Config The default configuration used by this middleware is defined as follows: ```go func defaultProbe(_ fiber.Ctx) bool { return true } var ConfigDefault = Config{ Next: nil, Probe: defaultProbe, } ``` --- ## Helmet Helmet secures your app by adding common security headers. ### Signatures ```go func New(config ...Config) fiber.Handler ``` ### Examples Once your Fiber app is initialized, add the middleware: ```go package main import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/helmet" ) func main() { app := fiber.New() app.Use(helmet.New()) app.Get("/", func(c fiber.Ctx) error { return c.SendString("Welcome!") }) app.Listen(":3000") } ``` ### Test ```bash curl -I http://localhost:3000 ``` ### Config | Property | Type | Description | Default | |:--------------------------|:------------------------|:--------------------------------------------|:-----------------| | Next | `func(fiber.Ctx) bool` | Skips the middleware when the function returns `true`. | `nil` | | XSSProtection | `string` | Value for the `X-XSS-Protection` header. | "0" | | ContentTypeNosniff | `string` | Value for the `X-Content-Type-Options` header. | "nosniff" | | XFrameOptions | `string` | Value for the `X-Frame-Options` header. | "SAMEORIGIN" | | HSTSMaxAge | `int` | `max-age` value for `Strict-Transport-Security`. | 0 | | HSTSExcludeSubdomains | `bool` | Disables HSTS on subdomains when `true`. | false | | ContentSecurityPolicy | `string` | Value for the `Content-Security-Policy` header. | "" | | CSPReportOnly | `bool` | Enables report-only mode for CSP. | false | | HSTSPreloadEnabled | `bool` | Adds the `preload` directive to HSTS. | false | | ReferrerPolicy | `string` | Value for the `Referrer-Policy` header. | "no-referrer" | | PermissionPolicy | `string` | Value for the `Permissions-Policy` header. | "" | | CrossOriginEmbedderPolicy | `string` | Value for the `Cross-Origin-Embedder-Policy` header. | "require-corp" | | CrossOriginOpenerPolicy | `string` | Value for the `Cross-Origin-Opener-Policy` header. | "same-origin" | | CrossOriginResourcePolicy | `string` | Value for the `Cross-Origin-Resource-Policy` header. | "same-origin" | | OriginAgentCluster | `string` | Value for the `Origin-Agent-Cluster` header. | "?1" | | XDNSPrefetchControl | `string` | Value for the `X-DNS-Prefetch-Control` header. | "off" | | XDownloadOptions | `string` | Value for the `X-Download-Options` header. | "noopen" | | XPermittedCrossDomain | `string` | Value for the `X-Permitted-Cross-Domain-Policies` header. | "none" | `Strict-Transport-Security` is set only when `HSTSMaxAge` is greater than `0` and the request is secure (`c.Secure()`): a TLS connection, or a request from a trusted proxy that reports `https` in `X-Forwarded-Proto`, `X-Forwarded-Protocol`, `X-Forwarded-Ssl: on` or `X-Url-Scheme`. Any other request gets no header. Behind a TLS-terminating proxy, enable Fiber's `TrustProxy` and list the proxy in `TrustProxyConfig` (`Proxies`, `Loopback`, `Private`, `LinkLocal` or `UnixSocket`): `TrustProxy` alone trusts no peer, so the forwarded scheme is ignored and no header is sent. The value carries `includeSubDomains` unless `HSTSExcludeSubdomains` is `true`. `New` panics when `HSTSMaxAge` is negative, or when `HSTSPreloadEnabled` is combined with `HSTSExcludeSubdomains`. A header option left empty falls back to its default, so a header with a non-empty default cannot be switched off by clearing its value. Use `Next` to skip the middleware for a request. ### Default Config ```go var ConfigDefault = Config{ XSSProtection: "0", ContentTypeNosniff: "nosniff", XFrameOptions: "SAMEORIGIN", ReferrerPolicy: "no-referrer", CrossOriginEmbedderPolicy: "require-corp", CrossOriginOpenerPolicy: "same-origin", CrossOriginResourcePolicy: "same-origin", OriginAgentCluster: "?1", XDNSPrefetchControl: "off", XDownloadOptions: "noopen", XPermittedCrossDomain: "none", } ``` --- ## Host Authorization Host authorization middleware for [Fiber](https://github.com/gofiber/fiber) that validates the incoming `Host` header against a configurable allowlist. Protects against [DNS rebinding attacks](https://en.wikipedia.org/wiki/DNS_rebinding) where an attacker-controlled domain resolves to the application's internal IP, causing browsers to send requests with a malicious Host header. ### Signatures ```go func New(config ...Config) fiber.Handler ``` ### Examples Import the middleware package: ```go import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/hostauthorization" ) ``` Once your Fiber app is initialized, choose one of the following approaches: #### Basic Usage ```go app.Use(hostauthorization.New(hostauthorization.Config{ AllowedHosts: []string{"api.myapp.com"}, })) app.Get("/users", func(c fiber.Ctx) error { return c.JSON(getUsers()) }) // Host: api.myapp.com → 200 OK // Host: evil.com → 403 Forbidden ``` #### Subdomain Wildcards A `*.` prefix matches any subdomain but **not** the bare domain itself: ```go app.Use(hostauthorization.New(hostauthorization.Config{ AllowedHosts: []string{"*.myapp.com"}, })) // Host: api.myapp.com → 200 OK // Host: www.myapp.com → 200 OK // Host: myapp.com → 403 Forbidden ``` To allow both the bare domain and all subdomains, include both: ```go AllowedHosts: []string{"myapp.com", "*.myapp.com"}, ``` #### Internationalized Domain Names (IDN) Browsers always transmit the `Host` header in ASCII (Punycode) form, so IDN entries in `AllowedHosts` are converted to Punycode at startup. You can configure entries in either form — they are equivalent: ```go AllowedHosts: []string{"münchen.example.com"} // Unicode AllowedHosts: []string{"xn--mnchen-3ya.example.com"} // Punycode (what the browser sends) ``` Both match an incoming request whose Host header is `xn--mnchen-3ya.example.com`. #### Skipping Health Checks Use `Next` to bypass host validation for specific paths: ```go app.Use(hostauthorization.New(hostauthorization.Config{ AllowedHosts: []string{"myapp.com", "*.myapp.com"}, Next: func(c fiber.Ctx) bool { return c.Path() == "/healthz" }, })) // Host: evil.com GET /healthz → 200 OK (skipped) // Host: evil.com GET /users → 403 Forbidden ``` #### Dynamic Validation Use `AllowedHostsFunc` for hosts that can't be known at startup: ```go app.Use(hostauthorization.New(hostauthorization.Config{ AllowedHostsFunc: func(host string) bool { // Look up tenant domains from database, cache, etc. return isRegisteredTenant(host) }, })) ``` `AllowedHostsFunc` is only called when static `AllowedHosts` don't match, so you can combine both: ```go app.Use(hostauthorization.New(hostauthorization.Config{ AllowedHosts: []string{"myapp.com", "*.myapp.com"}, AllowedHostsFunc: func(host string) bool { return isRegisteredCustomDomain(host) }, })) ``` #### Custom Error Response The default response is **403 Forbidden**. **421 Misdirected Request** ([RFC 9110 §15.5.20](https://www.rfc-editor.org/rfc/rfc9110#section-15.5.20)) is a semantically closer choice for "wrong host for this server" — CDNs like Cloudflare and Fastly use it for this case. Either is reasonable; pick one via `ErrorHandler`: ```go // 403 with a JSON body app.Use(hostauthorization.New(hostauthorization.Config{ AllowedHosts: []string{"myapp.com"}, ErrorHandler: func(c fiber.Ctx, err error) error { return c.Status(fiber.StatusForbidden).JSON(fiber.Map{ "error": "unauthorized host", }) }, })) // 421 Misdirected Request — closer to the RFC-defined semantics app.Use(hostauthorization.New(hostauthorization.Config{ AllowedHosts: []string{"myapp.com"}, ErrorHandler: func(c fiber.Ctx, _ error) error { return c.SendStatus(fiber.StatusMisdirectedRequest) // 421 }, })) ``` #### Combined with Domain() Router `hostauthorization` acts as a security gate; [`Domain()`](https://docs.gofiber.io/api/app#domain) handles routing: ```go // Security layer — reject anything not from our hosts app.Use(hostauthorization.New(hostauthorization.Config{ AllowedHosts: []string{"myapp.com", "*.myapp.com"}, Next: func(c fiber.Ctx) bool { return c.Path() == "/healthz" }, })) // Routing layer — direct allowed hosts to the right handlers app.Domain("api.myapp.com").Get("/users", listUsers) app.Domain(":tenant.myapp.com").Get("/dashboard", tenantDashboard) app.Get("/healthz", healthCheck) ``` ### Config | Property | Type | Description | Default | |:-----------------|:------------------------------|:--------------------------------------------------------------------------------------------------|:--------| | Next | `func(fiber.Ctx) bool` | Defines a function to skip this middleware when returned true. | `nil` | | AllowedHosts | `[]string` | List of permitted hosts. Supports exact match and subdomain wildcard (`*.example.com`). | `nil` | | AllowedHostsFunc | `func(string) bool` | Dynamic validator called only when no static AllowedHosts rule matches. Receives the normalized hostname: port stripped, trailing dot removed, IPv6 brackets removed, lowercased, IDN converted to Punycode. | `nil` | | ErrorHandler | `fiber.ErrorHandler` | Called when a request is rejected. Receives `ErrForbiddenHost` as the error. | 403 | Either `AllowedHosts` or `AllowedHostsFunc` (or both) must be provided. The middleware panics at startup if neither is set. ### Default Config ```go var ConfigDefault = Config{} ``` There is no useful default — you must provide at least `AllowedHosts` or `AllowedHostsFunc`. ### Host Matching The middleware matches hosts in this order: 1. **Exact match** — case-insensitive, port and trailing dot stripped, IDN labels in Punycode form 2. **Subdomain wildcard** — `"*.myapp.com"` matches `api.myapp.com` but not `myapp.com` 3. **AllowedHostsFunc** — called only if no static rule matched The first match wins. If nothing matches, `ErrorHandler` is called. ### Host Normalization Before matching, the host of each request is normalized, and the `AllowedHosts` entries are normalized once at startup: - Port is stripped (`example.com:8080` → `example.com`) - Trailing dot removed (`example.com.` → `example.com`) - IPv6 brackets removed (`[::1]` → `::1`) - Lowercased - IDN labels converted to ASCII/Punycode (`münchen.example.com` → `xn--mnchen-3ya.example.com`) - RFC 1035 length limits enforced at startup: ≤253 chars total, ≤63 chars per label (panic on violation) An entry in the leading-dot form some other tools use (`.myapp.com`) also panics at startup; write `*.myapp.com` instead. A request host that is not a well-formed authority is rejected with `ErrorHandler` before matching, so `AllowedHostsFunc` never sees it. That covers an empty or missing `Host`, userinfo in `X-Forwarded-Host` (`allowed.com:443@attacker.example`), a port that is empty, not numeric or above 65535, malformed IPv6 brackets or an IPv6 literal without brackets, a zoned IPv6 address (`fe80::1%eth0`), and a hostname with an empty label (`a..b`), a label that starts or ends with a hyphen or is longer than 63 characters, or any character besides letters, digits, hyphens and dots (underscores included). fasthttp answers some of these requests itself, before routing: an HTTP/1.1 request with a missing or empty `Host`, and any request whose `Host` it cannot parse (for example a space or `/` in the name, a port that is not numeric, unbalanced IPv6 brackets, an IPv6 literal without brackets, an invalid IPv6 address or a zone not written as `%25`), gets `400 Bad Request`. Fiber still runs the `Use` middleware for such a request, so `ErrorHandler` is called, but the app's `ErrorHandler` then writes the final response over it: with the default one the client receives the `400`. The `ErrorHandler` response reaches the client for an HTTP/1.0 request without `Host`, for a `Host` that fasthttp accepts and the middleware rejects (`a..b`, an empty port, a port above 65535, an underscore) and for a malformed `X-Forwarded-Host`. fasthttp removes userinfo from the `Host` header itself, everything up to the last `@`, before the middleware reads it. For that header the middleware matches only what follows the `@`: `Host: allowed.com:443@attacker.example` is checked as `attacker.example`, and `Host: attacker.example@allowed.com` as `allowed.com`. ### Filtering by Client IP This middleware filters by the `Host` *header*, not by the client's source IP. [`TrustProxy` / `TrustProxyConfig`](https://docs.gofiber.io/whats_new#trusted-proxies) do not filter clients either: they only decide which peers may supply forwarded headers such as `X-Forwarded-For` and `X-Forwarded-Host`. To restrict access by client IP, check `c.IP()` in your own middleware or filter in the proxy or firewall. ### Proxy Support The middleware reads the host with `c.Host()`, which uses the first value of `X-Forwarded-Host` when [`TrustProxy`](https://docs.gofiber.io/api/fiber#trustproxy) is enabled and the request comes from a proxy listed in `TrustProxyConfig`. When `TrustProxy` is disabled (the default), `X-Forwarded-Host` is ignored and the raw `Host` header is used. Either value goes through the same validation, so a malformed `X-Forwarded-Host` is rejected too. fasthttp itself is HTTP/1.x only. HTTP/2 support requires an external library (e.g. `fasthttp2`) plugged in via `Server.NextProto`. Those libraries are responsible for mapping the HTTP/2 `:authority` pseudo-header to a Host value before the request reaches Fiber handlers, so the middleware should work transparently once H2 is wired up — but this is the H2 library's responsibility, not fasthttp's or this middleware's. ### RFC Compliance - **RFC 9110 Section 7.2** — Host and port are separate components; port is stripped before matching - **RFC 9110 Section 17.1** — Origin servers should reject misdirected requests - **RFC 9112 Section 3.2** — An HTTP/1.1 request without a Host header is answered with `400 Bad Request`; fasthttp does this before the middleware's response is sent - **RFC 1035** — `AllowedHosts` entries are validated against the 253-char total / 63-char per-label limits - Returns **403 Forbidden** (not 400) for a host that is not allowed, because the request is syntactically valid but semantically unauthorized. The same `403` is the default for an HTTP/1.0 request without `Host` and for a malformed host that fasthttp accepts. An HTTP/1.1 request with a missing or empty `Host`, or with a `Host` fasthttp cannot parse, gets `400 Bad Request` from fasthttp before routing, which is what RFC 9112 Section 3.2 asks for. The middleware's `ErrorHandler` runs for it, but the app's `ErrorHandler` decides the final response (see [Host Normalization](#host-normalization)) :::note **RFC 9110 §15.5.20** defines **421 Misdirected Request** as a semantically closer response for host mismatches ("the request was directed at a server unable or unwilling to produce an authoritative response for the target URI"). CDNs like Cloudflare and Fastly use 421 for this case. To use 421 instead of 403, set a custom `ErrorHandler`: ```go ErrorHandler: func(c fiber.Ctx, err error) error { return c.SendStatus(fiber.StatusMisdirectedRequest) // 421 }, ``` ::: --- ## Idempotency The Idempotency middleware helps build fault-tolerant APIs. Duplicate requests—such as retries after network issues—won't trigger the same action twice on the server. Refer to [IETF RFC 7231 §4.2.2](https://tools.ietf.org/html/rfc7231#section-4.2.2) for definitions of safe and idempotent HTTP methods. ### HTTP Method Categories * **Safe Methods** (do not modify server state): `GET`, `HEAD`, `OPTIONS`, `TRACE`, `QUERY` * **Idempotent Methods** (identical requests have the same effect as a single one): all safe methods **plus** `PUT` and `DELETE` > According to the RFC, safe methods never change server state, while idempotent methods may change state but remain safe to repeat. ### Signatures ```go func New(config ...Config) fiber.Handler func IsFromCache(c fiber.Ctx) bool func WasPutToCache(c fiber.Ctx) bool func NewMemoryLock() *MemoryLock type Locker interface { Lock(key string) error Unlock(key string) error } ``` `IsFromCache` reports whether the middleware replayed a stored response for the current request, and `WasPutToCache` whether it stored the response of the current request. Both are set as the middleware returns, so read them from a middleware registered before it, after `c.Next()`. `NewMemoryLock` returns the in-memory `Locker` the middleware uses by default. It only locks within the current process; implement `Locker` yourself to coordinate several instances. The middleware calls `Lock` before it runs the handler and `Unlock` once the request is done, so `Lock` has to block until the caller holds the key. ### Behavior * A request passes through untouched when `Next` returns `true` or when it has no `KeyHeader` value. The header is optional: nothing rejects a request that omits it. * The first request with a key runs the handler. If the handler returns without an error, its status, body and headers are stored under the key for `Lifetime`, whatever the status code. A handler that returns an error is not stored, so the next request with that key runs the handler again. * Later requests with the same key receive the stored response and the handler does not run. A request that arrives while the first is still running waits for `Lock` and then receives the stored response. * The key alone identifies a request. The method, path and body are not compared, so a retry with the same key but a different payload, or on another route, gets the original response. * A key that fails `KeyHeaderValidate` is returned as an error to the error handler, which answers `500` unless the validator returns a `*fiber.Error`. ### Examples Import the middleware package: ```go import ( "time" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/idempotency" ) ``` Once your Fiber app is initialized, configure the middleware: #### Default Config (Skip **Safe** Methods) By default, the `Next` function skips middleware for safe methods only: ```go app.Use(idempotency.New()) ``` #### Skip **Idempotent** Methods Instead Skip all idempotent methods (including `PUT` and `DELETE`) by overriding `Next`: ```go app.Use(idempotency.New(idempotency.Config{ Next: func(c fiber.Ctx) bool { // Skip middleware for idempotent methods (safe + PUT, DELETE) return fiber.IsMethodIdempotent(c.Method()) }, })) ``` #### Custom Config ```go app.Use(idempotency.New(idempotency.Config{ Lifetime: 42 * time.Minute, // ... })) ``` ### Config Idempotency keys are hidden in logs and error messages by default. Set `DisableValueRedaction` to `true` only when you need to expose them for debugging. | Property | Type | Description | Default | |:--------------------|:-----------------------|:----------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------| | Next | `func(fiber.Ctx) bool` | Function to skip this middleware when it returns `true`; use `IsMethodSafe` or `IsMethodIdempotent`. | `func(c fiber.Ctx) bool { return fiber.IsMethodSafe(c.Method()) }` | | Lifetime | `time.Duration` | Maximum lifetime of an idempotency key. | `30 * time.Minute` | | KeyHeader | `string` | Header name containing the idempotency key. | `"X-Idempotency-Key"` | | KeyHeaderValidate | `func(string) error` | Function to validate idempotency header syntax (e.g., UUID). The error it returns is passed to the error handler as is; the default one wraps `ErrInvalidIdempotencyKey`. | UUID length check (`36` characters) | | KeepResponseHeaders | `[]string` | List of headers to preserve from original response, matched case-insensitively. An empty list keeps all headers, like `nil`. | `nil` (keep all headers) | | DisableValueRedaction | `bool` | Disables idempotency key redaction in logs and error messages. | `false` | | Lock | `Locker` | Locks an idempotency key to prevent race conditions. The default only locks within this process. | `NewMemoryLock()` (in-memory locker) | | Storage | `fiber.Storage` | Stores response data by idempotency key. The default store is private to this process. | In-memory storage | ### Default Config Values ```go var ConfigDefault = Config{ Next: func(c fiber.Ctx) bool { // Skip middleware for safe methods per RFC 7231 §4.2.2 return fiber.IsMethodSafe(c.Method()) }, Lifetime: 30 * time.Minute, KeyHeader: "X-Idempotency-Key", KeyHeaderValidate: func(k string) error { if l, wl := len(k), 36; l != wl { // UUID length is 36 chars return fmt.Errorf("%w: invalid length: %d != %d", ErrInvalidIdempotencyKey, l, wl) } return nil }, KeepResponseHeaders: nil, Lock: nil, // Set in configDefault so we don't allocate data here. Storage: nil, // Set in configDefault so we don't allocate data here. DisableValueRedaction: false, } ``` --- ## KeyAuth The KeyAuth middleware implements API key authentication. ### Signatures ```go func New(config ...Config) fiber.Handler func TokenFromContext(ctx any) string ``` `TokenFromContext` accepts a `fiber.CustomCtx`, `fiber.Ctx`, a `*fasthttp.RequestCtx`, or a `context.Context`. The token points into the request's buffer unless [`Immutable`](../api/fiber.md#immutable) is set: copy it with `strings.Clone` before a goroutine keeps it. ### Examples #### Basic example This example registers KeyAuth with an API key stored in a cookie. ```go package main import ( "crypto/sha256" "crypto/subtle" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/extractors" "github.com/gofiber/fiber/v3/middleware/keyauth" ) var ( apiKey = "correct horse battery staple" ) func validateAPIKey(c fiber.Ctx, key string) (bool, error) { hashedAPIKey := sha256.Sum256([]byte(apiKey)) hashedKey := sha256.Sum256([]byte(key)) if subtle.ConstantTimeCompare(hashedAPIKey[:], hashedKey[:]) == 1 { return true, nil } return false, keyauth.ErrMissingOrMalformedAPIKey } func main() { app := fiber.New() // Register middleware before the routes that need it app.Use(keyauth.New(keyauth.Config{ Extractor: extractors.FromCookie("access_token"), Validator: validateAPIKey, })) app.Get("/", func(c fiber.Ctx) error { return c.SendString("Successfully authenticated!") }) app.Listen(":3000") } ``` **Test:** ```bash # No API key specified -> 401 missing or invalid API Key curl http://localhost:3000 #> missing or invalid API Key # Correct API key -> 200 OK curl --cookie "access_token=correct horse battery staple" http://localhost:3000 #> Successfully authenticated! # Incorrect API key -> 401 missing or invalid API Key curl --cookie "access_token=Clearly A Wrong Key" http://localhost:3000 #> missing or invalid API Key ``` For a more detailed example, see the [`fiber-envoy-extauthz`](https://github.com/gofiber/recipes/tree/master/fiber-envoy-extauthz) recipe in the `gofiber/recipes` repository. #### Authenticate only certain endpoints Use the `Next` function to run KeyAuth only on selected routes. ```go package main import ( "crypto/sha256" "crypto/subtle" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/extractors" "github.com/gofiber/fiber/v3/middleware/keyauth" "regexp" "strings" ) var ( apiKey = "correct horse battery staple" protectedURLs = []*regexp.Regexp{ regexp.MustCompile("^/authenticated$"), regexp.MustCompile("^/auth2$"), } ) func validateAPIKey(c fiber.Ctx, key string) (bool, error) { hashedAPIKey := sha256.Sum256([]byte(apiKey)) hashedKey := sha256.Sum256([]byte(key)) if subtle.ConstantTimeCompare(hashedAPIKey[:], hashedKey[:]) == 1 { return true, nil } return false, keyauth.ErrMissingOrMalformedAPIKey } func authFilter(c fiber.Ctx) bool { originalURL := strings.ToLower(c.OriginalURL()) for _, pattern := range protectedURLs { if pattern.MatchString(originalURL) { // Run middleware for protected routes return false } } // Skip middleware for non-protected routes return true } func main() { app := fiber.New() app.Use(keyauth.New(keyauth.Config{ Next: authFilter, Extractor: extractors.FromCookie("access_token"), Validator: validateAPIKey, })) app.Get("/", func(c fiber.Ctx) error { return c.SendString("Welcome") }) app.Get("/authenticated", func(c fiber.Ctx) error { return c.SendString("Successfully authenticated!") }) app.Get("/auth2", func(c fiber.Ctx) error { return c.SendString("Successfully authenticated 2!") }) app.Listen(":3000") } ``` **Test:** ```bash # / doesn't require authentication curl http://localhost:3000 #> Welcome # /authenticated requires authentication curl --cookie "access_token=correct horse battery staple" http://localhost:3000/authenticated #> Successfully authenticated! # /auth2 requires authentication too curl --cookie "access_token=correct horse battery staple" http://localhost:3000/auth2 #> Successfully authenticated 2! ``` #### Apply middleware in the handler You can apply the middleware to specific routes or groups instead of globally. This example uses the default extractor (`FromAuthHeader`). - `FromAuthHeader` expects a compliant [RFC 7235 `token68`](https://datatracker.ietf.org/doc/html/rfc7235#section-2.1) key value. ```go package main import ( "crypto/sha256" "crypto/subtle" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/keyauth" ) const ( apiKey = "my-super-secret-key" ) func main() { app := fiber.New() authMiddleware := keyauth.New(keyauth.Config{ Validator: func(c fiber.Ctx, key string) (bool, error) { hashedAPIKey := sha256.Sum256([]byte(apiKey)) hashedKey := sha256.Sum256([]byte(key)) if subtle.ConstantTimeCompare(hashedAPIKey[:], hashedKey[:]) == 1 { return true, nil } return false, keyauth.ErrMissingOrMalformedAPIKey }, }) app.Get("/", func(c fiber.Ctx) error { return c.SendString("Welcome") }) app.Get("/allowed", authMiddleware, func(c fiber.Ctx) error { return c.SendString("Successfully authenticated!") }) app.Listen(":3000") } ``` **Test:** ```bash # / doesn't require authentication curl http://localhost:3000 #> Welcome # /allowed requires authentication curl --header "Authorization: Bearer my-super-secret-key" http://localhost:3000/allowed #> Successfully authenticated! ``` ### Key Extractors KeyAuth uses an `Extractor` from the shared [extractors](../guide/extractors) package to retrieve the API key from the request. You can specify one or more extractors in the configuration. For a full list of extractors, chaining, and advanced usage, see the [Extractors Guide](../guide/extractors). #### Typical Usage Specify the extractor in the config. For example, to extract from a cookie: ```go app.Use(keyauth.New(keyauth.Config{ Extractor: extractors.FromCookie("access_token"), Validator: validateAPIKey, })) ``` To use the default (Authorization header with Bearer scheme): ```go app.Use(keyauth.New(keyauth.Config{ Validator: validateAPIKey, // Extractor defaults to FromAuthHeader("Bearer") })) ``` To try multiple sources (header, then query): ```go app.Use(keyauth.New(keyauth.Config{ Extractor: extractors.Chain( extractors.FromHeader("X-API-Key"), extractors.FromQuery("api_key"), ), Validator: validateAPIKey, })) ``` For custom logic, use `extractors.FromCustom`: ```go app.Use(keyauth.New(keyauth.Config{ Extractor: extractors.FromCustom("X-My-API-Key", func(c fiber.Ctx) (string, error) { key := c.Get("X-My-API-Key") if key == "" { return "", extractors.ErrNotFound } return key, nil }), Validator: validateAPIKey, })) ``` Refer to the [Extractors Guide](../guide/extractors) for details, security notes, and advanced configuration. ### Config | Property | Type | Description | Default | |:----------------|:-----------------------------------------|:-------------------------------------------------------------------------------------------------------|:------------------------------| | Next | `func(fiber.Ctx) bool` | Next defines a function to skip this middleware when it returns true. | `nil` | | SuccessHandler | `fiber.Handler` | SuccessHandler defines a function which is executed for a valid key. | `c.Next()` | | ErrorHandler | `fiber.ErrorHandler` | ErrorHandler defines a function which is executed for an invalid key. It receives the extraction or validation error, which is `nil` when the `Validator` returned `false` without an error. By default a 401 response with the body `missing or invalid API Key` (`keyauth.ErrMissingOrMalformedAPIKey`) and a `WWW-Authenticate` challenge is sent. The challenge is also added when a custom handler returns a `*fiber.Error` instead of writing the response itself: as `WWW-Authenticate` for status 401, and as `Proxy-Authenticate` for status 407. | Default error handler | | Validator | `func(fiber.Ctx, string) (bool, error)` | **Required.** Validator is a function to validate the key. | `nil` (panic) | | Extractor | `extractors.Extractor` | Extractor defines how to retrieve the key from the request. Use helper functions from the shared extractors package, e.g. `extractors.FromAuthHeader("Bearer")` or `extractors.FromCookie("access_token")`. | `extractors.FromAuthHeader("Bearer")` | | Realm | `string` | Realm specifies the protected area name used in the `WWW-Authenticate` header. | `"Restricted"` | | Challenge | `string` | Value of the `WWW-Authenticate` header when no `Authorization` scheme is present. | `ApiKey realm="Restricted"` | | Error | `string` | Error code appended as the `error` parameter in Bearer challenges. Must be `invalid_request`, `invalid_token`, or `insufficient_scope`, available as the constants `keyauth.ErrorInvalidRequest`, `keyauth.ErrorInvalidToken`, and `keyauth.ErrorInsufficientScope`. Any other value panics. | `""` | | ErrorDescription| `string` | Human-readable text for the `error_description` parameter in Bearer challenges. Requires `Error`. | `""` | | ErrorURI | `string` | URI identifying a human-readable web page with information about the `error` in Bearer challenges. Requires `Error` and must be an absolute URI. | `""` | | Scope | `string` | Space-delimited list of scopes for the `scope` parameter in Bearer challenges. Each token must conform to the RFC 6750 `scope-token` syntax. Only valid with `Error` set to `insufficient_scope`, which in turn requires `Scope`. | `""` | When the extractor reads the `Authorization` header with `extractors.FromAuthHeader` and a non-empty scheme, alone or inside a `Chain`, the challenge is generated as ` realm=""` for each scheme, for example `Bearer realm="Restricted"`, and `Challenge` is not used. `FromAuthHeader("")` names no scheme, so `Challenge` applies, which is `ApiKey realm=""` unless you set it. Generated challenge parameter values are escaped as RFC 9110 HTTP quoted strings. A custom `Challenge` value is emitted verbatim. ### Default Config ```go var ConfigDefault = Config{ SuccessHandler: func(c fiber.Ctx) error { return c.Next() }, ErrorHandler: func(c fiber.Ctx, _ error) error { return c.Status(fiber.StatusUnauthorized).SendString(ErrMissingOrMalformedAPIKey.Error()) }, Realm: "Restricted", Extractor: extractors.FromAuthHeader("Bearer"), } ``` --- ## Limiter The Limiter middleware for [Fiber](https://github.com/gofiber/fiber) throttles repeated requests to public APIs or endpoints such as password resets. It's also useful for API clients, web crawlers, or other tasks that need rate limiting. Limiter redacts request keys in error paths by default so storage identifiers and rate-limit keys don't leak into logs. Set `DisableValueRedaction` to `true` when you need the key for troubleshooting. The key shown is the one the storage sees, so a key longer than 192 bytes appears as its SHA-256 digest. :::note This middleware uses our [Storage](https://github.com/gofiber/storage) package to support various databases through a single interface. The default configuration for this middleware saves data to memory, see the examples below for other databases. ::: :::note This module does not share state with other processes/servers by default. ::: ### Signatures ```go func New(config ...Config) fiber.Handler type Handler interface { New(config *Config) fiber.Handler } ``` ### Examples Import the middleware package: ```go import ( "time" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/limiter" ) ``` Once your Fiber app is initialized, use the middleware like this: ```go // Initialize default config app.Use(limiter.New()) // Or extend your config for customization app.Use(limiter.New(limiter.Config{ Next: func(c fiber.Ctx) bool { return c.IP() == "127.0.0.1" }, Max: 20, MaxFunc: func(c fiber.Ctx) int { return 20 }, Expiration: 30 * time.Second, ExpirationFunc: func(c fiber.Ctx) time.Duration { // Use longer expiration for sensitive endpoints. A window keeps the length of the // request that opened it, so give such a route its own key (see Dynamic expiration) if c.Path() == "/login" { return 60 * time.Second } return 30 * time.Second }, KeyGenerator: func(c fiber.Ctx) string { return c.Get("x-forwarded-for") }, LimitReached: func(c fiber.Ctx) error { return c.SendFile("./toofast.html") }, Storage: myCustomStorage{}, })) ``` :::caution The default `KeyGenerator` keys requests by `c.IP()`. Behind a reverse proxy, `c.IP()` only identifies a client if you configure `TrustProxy`, `TrustProxyConfig` and `ProxyHeader`, plus `EnableIPValidation` when the header is a chain your proxy appends to, such as `X-Forwarded-For`. Without them it is either the proxy's address, so every client shares one bucket, or the raw forwarded header, which a client can vary on every request to get a fresh bucket unless your proxy overwrites it with a single address. See the [reverse proxy guide](../guide/reverse-proxy.md#getting-the-real-client-ip-address). ::: ### Sliding window Instead of using the standard fixed window algorithm, you can enable the [sliding window](https://en.wikipedia.org/wiki/Sliding_window_protocol) algorithm. An example configuration is: ```go app.Use(limiter.New(limiter.Config{ Max: 20, Expiration: 30 * time.Second, LimiterMiddleware: limiter.SlidingWindow{}, })) ``` Each new window also considers the previous one (if any). The rate is calculated as: ```text weightOfPreviousWindow = previousWindowRequests * (timeLeftInCurrentWindow / Expiration) rate = ceil(weightOfPreviousWindow) + currentWindowRequests ``` The previous window therefore counts in full at the start of a window and fades out as the window runs. ### Dynamic limit You can also calculate the limit dynamically using the `MaxFunc` parameter. It receives the request context and allows you to compute a different limit for each request. Returning `0` skips the limiter for that request, and a negative value rejects every request. `Next` runs before `MaxFunc`, so a request that `Next` skips never reaches it. Example: ```go app.Use("/users/:id", limiter.New(limiter.Config{ MaxFunc: func(c fiber.Ctx) int { return getUserLimit(c.Params("id")) }, Expiration: 30 * time.Second, })) ``` ### Dynamic expiration You can also calculate the expiration dynamically using the `ExpirationFunc` parameter. It receives the request context and allows you to set a different expiration window for each request. Window accounting is whole-second, so a positive duration below one second is treated as a one-second window. A zero or negative duration falls back to the configured `Expiration`, which is one minute unless you set another. Requests that share a key share one window, and a window keeps the length given by the request that opened it. With the default `KeyGenerator`, which keys by client IP, a request that arrives while a window opened by another route is running joins that window: its `X-RateLimit-Reset` is what remains of it, whatever `ExpirationFunc` returns for the request. A route that needs a window of its own needs a key of its own. The sliding window weighs the previous window's hits by the time left in the open window divided by the length the current request asks for, capped at 1. That is exact only while every request for a key asks for the same length: a longer one counts fewer of the previous hits. Example: ```go app.Use(limiter.New(limiter.Config{ Max: 20, ExpirationFunc: func(c fiber.Ctx) time.Duration { return getExpirationForRoute(c.Path()) }, // One window per client and route KeyGenerator: func(c fiber.Ctx) string { return c.IP() + c.Path() }, })) ``` ### Concurrency and storage access The limiter reads an entry, updates it and writes it back while holding a lock, so concurrent requests for one key through the same limiter never read the same count. Keys are spread over 64 locks, which lets requests for different keys run in parallel. A lock is held for the storage round trips of its request: with a slow external storage, keys that share a lock wait for each other, and requests for one key are handled one at a time. That is the price of exact counts within one limiter, because `fiber.Storage` offers no atomic increment to rely on. The locks are not shared between limiters, so two limiters, in one process or several, that use the same storage and key can still admit a few extra requests when they handle that key at the same moment. A request that passes costs one storage read and one write. A second read and write follows the handler when: - `SkipSuccessfulRequests` applies to the response (status below 400) or `SkipFailedRequests` does (status 400 or above), to give the hit back. - The algorithm is the sliding window and headers are enabled, so that `X-RateLimit-Remaining` and `X-RateLimit-Reset` describe the state at the end of the request. Set `DisableHeaders` to avoid it. ### Config | Property | Type | Description | Default | |:-----------------------|:--------------------------|:--------------------------------------------------------------------------------------------|:-----------------------------------------| | Next | `func(fiber.Ctx) bool` | Next defines a function to skip this middleware when it returns true. It runs before `MaxFunc`. | `nil` | | Max | `int` | Maximum number of recent connections within `Expiration` seconds before sending a 429 response. A value of `0` or less selects the default. | 5 | | MaxFunc | `func(fiber.Ctx) int` | Function that calculates the maximum number of recent connections within `Expiration` seconds before sending a 429 response. Returning `0` skips the limiter for that request; a negative value rejects every request. | A function that returns `cfg.Max` | | KeyGenerator | `func(fiber.Ctx) string` | Function to generate custom keys; uses `c.IP()` by default. A key longer than 192 bytes, or starting with `sha256:`, is replaced by its SHA-256 digest (`sha256:`) before it reaches the storage, so oversized keys are still limited per client. | A function using `c.IP()` as the default | | Expiration | `time.Duration` | Duration to keep request records in memory, which is also the length of the window. It is counted in whole seconds: a positive value below one second is floored to one second, and zero or a negative value selects the default. | 1 * time.Minute | | ExpirationFunc | `func(fiber.Ctx) time.Duration` | Function that calculates the expiration duration dynamically. Positive values below one second are floored to one second; non-positive values fall back to `Expiration`. Requests that share a key share a window, which keeps the length given by the request that opened it. | A function that returns `cfg.Expiration` | | LimitReached | `fiber.Handler` | Called when a request exceeds the limit. | A function sending a 429 response | | SkipFailedRequests | `bool` | When set to `true`, requests with status code ≥ 400 aren't counted. A handler that returns an error other than a `*fiber.Error` counts as a 500 unless it already set an error status. | false | | SkipSuccessfulRequests | `bool` | When set to `true`, requests with status code < 400 aren't counted. A handler that returns an error other than a `*fiber.Error` counts as a 500 unless it already set an error status. | false | | DisableHeaders | `bool` | When set to `true`, the middleware omits rate limit headers (`X-RateLimit-*` and `Retry-After`). Otherwise a request that passes carries `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` (seconds until the window ends), and a rejected one carries `Retry-After` (seconds). | false | | DisableValueRedaction | `bool` | Disables redaction of limiter keys in error messages and logs. A key longer than 192 bytes is shown as its SHA-256 digest. | false | | Storage | `fiber.Storage` | Persists middleware state. | An in-memory store for this process only | | LimiterMiddleware | `limiter.Handler` | Selects the algorithm implementation. Implementations now receive a pointer to the active config when their `New` method is invoked. | A new Fixed Window Rate Limiter | :::note A custom store can be used if it implements the `fiber.Storage` interface, which is declared in [`storage_interface.go`](https://github.com/gofiber/fiber/blob/main/storage_interface.go). ::: ### Default Config ```go var ConfigDefault = Config{ Max: 5, // MaxFunc defaults to nil and is set dynamically to return cfg.Max Expiration: 1 * time.Minute, // ExpirationFunc defaults to nil and is set dynamically to return cfg.Expiration KeyGenerator: func(c fiber.Ctx) string { return c.IP() }, LimitReached: func(c fiber.Ctx) error { return c.SendStatus(fiber.StatusTooManyRequests) }, SkipFailedRequests: false, SkipSuccessfulRequests: false, DisableHeaders: false, DisableValueRedaction: false, LimiterMiddleware: FixedWindow{}, } ``` #### Custom Storage/Database You can use any storage from our [storage](https://github.com/gofiber/storage/) package. ```go storage := sqlite3.New() // From github.com/gofiber/storage/sqlite3/v2 app.Use(limiter.New(limiter.Config{ Storage: storage, })) ``` --- ## Logger Logger middleware for [Fiber](https://github.com/gofiber/fiber) that logs HTTP requests and responses. ### Signatures ```go func New(config ...Config) fiber.Handler func RegisterTag(tag string, fn LogFunc) error func MustRegisterTag(tag string, fn LogFunc) func RegisterContextTag(name string, extract func(ctx any) string) func SanitizeValue(s string) string func LoggerToWriter[T any](logger log.AllLogger[T], level log.Level) io.Writer ``` ### Examples Import the package: ```go import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/logger" "github.com/gofiber/fiber/v3/middleware/requestid" ) ``` :::tip Registration order matters: only routes added after the logger are logged, so register it early. ::: Once your Fiber app is initialized, use the middleware like this: ```go // Initialize default config app.Use(logger.New()) // Or extend your config for customization // Log remote IP and port app.Use(logger.New(logger.Config{ Format: "[${ip}]:${port} ${status} - ${method} ${path}\n", })) // Logging Request ID app.Use(requestid.New()) // Ensure requestid middleware is used before the logger app.Use(logger.New(logger.Config{ // requestid.New() registers ${requestid} automatically. Format: "${pid} ${requestid} ${status} - ${method} ${path}\n", })) // Changing TimeZone & TimeFormat app.Use(logger.New(logger.Config{ Format: "${pid} ${status} - ${method} ${path}\n", TimeFormat: "02-Jan-2006", TimeZone: "America/New_York", })) // Custom File Writer accessLog, err := os.OpenFile("./access.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) if err != nil { log.Fatalf("error opening access.log file: %v", err) } defer accessLog.Close() app.Use(logger.New(logger.Config{ Stream: accessLog, })) // Add Custom Tags app.Use(logger.New(logger.Config{ CustomTags: map[string]logger.LogFunc{ "custom_tag": func(output logger.Buffer, c fiber.Ctx, data *logger.Data, extraParam string) (int, error) { return output.WriteString("it is a custom tag") }, }, })) // Callback after log is written app.Use(logger.New(logger.Config{ TimeFormat: time.RFC3339Nano, TimeZone: "Asia/Shanghai", Done: func(c fiber.Ctx, logString []byte) { if c.Response().StatusCode() != fiber.StatusOK { reporter.SendToSlack(logString) } }, })) // Disable colors when outputting to default format app.Use(logger.New(logger.Config{ DisableColors: true, })) // Force the use of colors app.Use(logger.New(logger.Config{ ForceColors: true, })) // Use predefined formats app.Use(logger.New(logger.Config{ Format: logger.CommonFormat, })) app.Use(logger.New(logger.Config{ Format: logger.CombinedFormat, })) app.Use(logger.New(logger.Config{ Format: logger.JSONFormat, })) app.Use(logger.New(logger.Config{ Format: logger.ECSFormat, })) ``` #### Logging Handler Errors The `${error}` tag reports a non-nil error returned by a downstream handler or middleware. When no error is returned, it renders `-`. A response status alone is not an error: `return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "..."})` sets `${status}` to `500`, but `${error}` remains `-`. To include an error in the log, return it and let Fiber's `ErrorHandler` create the response. The logger invokes the configured error handler before rendering the log, so a custom handler can return JSON while `${error}` keeps the original error message: ```go app := fiber.New(fiber.Config{ ErrorHandler: func(c fiber.Ctx, err error) error { code := fiber.StatusInternalServerError if fiberErr, ok := err.(*fiber.Error); ok { code = fiberErr.Code } return c.Status(code).JSON(fiber.Map{"error": "request failed"}) }, }) app.Use(logger.New(logger.Config{ Format: "${status} ${method} ${path} ${error}\n", })) app.Get("/reports", func(_ fiber.Ctx) error { return fiber.NewError(fiber.StatusInternalServerError, "report generation failed") }) ``` Register the logger before routes and downstream middleware whose returned errors it should observe. Use `${status}` when code writes a complete response directly; status codes by themselves do not populate `${error}`. #### Auto-Registered Tags Some Fiber middleware registers logger middleware tags automatically. Register the producing middleware before `logger.New()` and then use the tag in `Format`. ```go app.Use(requestid.New()) app.Use(logger.New(logger.Config{ Format: "${requestid} ${status} ${method} ${path}\n", })) ``` The logger middleware resolves tags in this order: 1. Built-in logger tags, such as `${method}`, `${path}`, and `${status}`. 2. Globally registered tags from Fiber middleware or `logger.RegisterTag`. 3. `Config.CustomTags`, which override tags with the same name for that logger instance. The following tags are registered by Fiber middleware when the middleware is initialized: | Tag | Registered by | Value | | :-- | :------------ | :---- | | `${requestid}` / `${request-id}` | `requestid.New()` | Request ID stored by the requestid middleware. | | `${username}` | `basicauth.New()` | Authenticated username stored by the basicauth middleware. | | `${api-key}` | `keyauth.New()` | Redacted API key stored by the keyauth middleware. | | `${csrf-token}` | `csrf.New()` | Redacted marker when the csrf middleware stores a token. | | `${session-id}` | `session.New()` or `session.NewWithStore()` | Redacted session ID stored by the session middleware. | :::note Auto-registered tags are access-log tags for `middleware/logger`. The same names are also registered for application logs in the `log` package via `logger.RegisterContextTag` — see [api/log#context-tags](../api/log.md#context-tags) for details on `log.WithContext` enrichment. ::: #### Register Tags from Custom Middleware Third-party middleware can expose logger tags with `logger.RegisterTag` or `logger.MustRegisterTag`. Use `sync.Once` so the tag is registered once even when the middleware is initialized multiple times. `RegisterTag` returns `logger.ErrTagInvalid` when the tag name is empty or the function is `nil`, and `MustRegisterTag` panics with that error. Registering a name again replaces the earlier renderer, and only loggers created afterwards see the change. To expose a value kept in the request context to both the access log and the [`log` package](../api/log.md#context-tags), call `logger.RegisterContextTag(name, extract)`. The extractor receives a `fiber.Ctx`, a `*fasthttp.RequestCtx` or a `context.Context` and returns the text to log, where an empty string renders nothing. `RegisterContextTag` panics when the name is empty or the extractor is `nil`. A tag you register replaces the built-in renderer, so it does not inherit the [control-character scrubbing](#control-character-sanitization) the built-in tags apply. Wrap request-derived values in `logger.SanitizeValue`. ```go package tenantmw import ( "sync" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/logger" ) type tenantContextKey struct{} var tenantKey tenantContextKey var registerLoggerTagsOnce sync.Once func New() fiber.Handler { registerLoggerTagsOnce.Do(func() { logger.MustRegisterTag("tenant", func(output logger.Buffer, c fiber.Ctx, _ *logger.Data, _ string) (int, error) { tenant, _ := fiber.ValueFromContext[string](c, tenantKey) return output.WriteString(tenant) }) }) return func(c fiber.Ctx) error { fiber.StoreInContext(c, tenantKey, "acme") return c.Next() } } ``` Use the registered tag in the logger format after installing the middleware: ```go app.Use(tenantmw.New()) app.Use(logger.New(logger.Config{ Format: "${tenant} ${status} ${method} ${path}\n", })) ``` Use `Config.CustomTags` when one logger instance needs a local override without changing the global tag registration. These also bypass the built-in [control-character scrubbing](#control-character-sanitization) — wrap request-derived values in `logger.SanitizeValue`: ```go app.Use(logger.New(logger.Config{ Format: "${tenant} ${status} ${method} ${path}\n", CustomTags: map[string]logger.LogFunc{ "tenant": func(output logger.Buffer, c fiber.Ctx, _ *logger.Data, _ string) (int, error) { return output.WriteString("override") }, }, })) ``` #### Unknown Tags A `Format` that names a tag which is neither built in, registered nor listed in `Config.CustomTags` makes `logger.New` panic, so a misspelled `${...}` fails at startup instead of on the first request. The panic value is a `*logger.UnknownTagError`. Its `Tag` field is the tag as written, with the `:parameter` of a parametric tag such as `${query:id}`, `Param` is the parameter, and `Hint` is a suggestion such as `did you mean ${reqHeader:PARAM}?` for a `${reqHeader}` written without its parameter. The error wraps `logger.ErrUnknownTag`, so `errors.Is` and `errors.As` both work on the recovered value. #### Use Logger Middleware with Other Loggers To combine the logger middleware with loggers like Zerolog, Zap, or Logrus, use the `LoggerToWriter` helper to adapt them to an `io.Writer`. It sends each log line to the logger's method for `level`, which is one of `log.LevelTrace`, `log.LevelDebug`, `log.LevelInfo`, `log.LevelWarn` or `log.LevelError`. It panics for a `nil` logger and for `log.LevelFatal` or `log.LevelPanic`. ```go package main import ( "github.com/gofiber/contrib/fiberzap/v2" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/log" "github.com/gofiber/fiber/v3/middleware/logger" ) func main() { // Create a new Fiber instance app := fiber.New() // Create a new zap logger which is compatible with Fiber AllLogger interface zap := fiberzap.NewLogger(fiberzap.LoggerConfig{ ExtraKeys: []string{"request_id"}, }) // Use the logger middleware with the zap logger app.Use(logger.New(logger.Config{ Stream: logger.LoggerToWriter(zap, log.LevelDebug), })) // Define a route app.Get("/", func(c fiber.Ctx) error { return c.SendString("Hello, World!") }) // Start server on http://localhost:3000 app.Listen(":3000") } ``` :::tip Writing to `os.File` is goroutine-safe, but custom streams may require locking to serialize writes. ::: ### Config | Property | Type | Description | Default | | :------------ | :------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------- | | Next | `func(fiber.Ctx) bool` | Next defines a function to skip this middleware when it returns true. | `nil` | | Skip | `func(fiber.Ctx) bool` | Skip is a function to determine if logging is skipped or written to Stream. | `nil` | | Done | `func(fiber.Ctx, []byte)` | Done is a function that is called after the log string for a request is written to Stream, and pass the log string as parameter. | `nil` | | CustomTags | `map[string]LogFunc` | Defines custom tag actions for this logger instance. These tags override built-in and globally registered tags with the same name. | `nil` | | `Format` | `string` | Defines the logging tags. See more in [Predefined Formats](#predefined-formats), or create your own using [Tags](#constants). | `[${time}] ${ip} ${status} - ${latency} ${method} ${path} ${error}\n` (same as `DefaultFormat`) | | TimeFormat | `string` | TimeFormat defines the time format for log timestamps. | `15:04:05` | | TimeZone | `string` | TimeZone can be specified, such as "UTC" and "America/New_York" and "Asia/Chongqing", etc. An unknown name falls back to `Local`. | `"Local"` | | TimeInterval | `time.Duration` | TimeInterval is the delay before the timestamp is updated. | `500 * time.Millisecond` | | Stream | `io.Writer` | Stream is a writer where logs are written. | `os.Stdout` | | TimeDone | `<-chan struct{}` | TimeDone stops the background timestamp updater when it is closed. | `nil` | | BeforeHandlerFunc | `func(*Config)` | BeforeHandlerFunc runs once before the handler is built, letting you customize colors, template, etc. | `beforeHandlerFunc` | | LoggerFunc | `func(c fiber.Ctx, data *Data, cfg *Config) error` | Custom logger function for integration with logging libraries (Zerolog, Zap, Logrus, etc). Defaults to Fiber's default logger if not defined. `Skip` and `Done` are applied by the default logger only, so a custom function reads them from `cfg` itself. `Data.TemplateChain` and `Data.LogFuncChain` are shared by all requests: read them, never modify them. | `see default_logger.go defaultLoggerInstance` | | DisableColors | `bool` | DisableColors defines if the logs output should be colorized. | `false` | | ForceColors | `bool` | ForceColors defines if the logs output should be colorized even when the output is not a terminal. | `false` | Colors are on when logs go to the default `os.Stdout` and `DisableColors` is off, or when `ForceColors` is set. On `os.Stdout`, Fiber strips the escape sequences when stdout is not a terminal, `TERM` is `dumb` or `NO_COLOR` is `1`, unless `ForceColors` is set. ### Default Config ```go var ConfigDefault = Config{ Next: nil, Skip: nil, Done: nil, Format: DefaultFormat, TimeFormat: "15:04:05", TimeZone: "Local", TimeInterval: 500 * time.Millisecond, Stream: os.Stdout, BeforeHandlerFunc: beforeHandlerFunc, LoggerFunc: defaultLoggerInstance, areColorsEnabled: true, } ``` ### Predefined Formats Logger provides predefined formats that you can use by name or directly by specifying the format string. | **Format Constant** | **Format String** | **Description** | |---------------------|--------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------| | `DefaultFormat` | `"[${time}] ${ip} ${status} - ${latency} ${method} ${path} ${error}\n"` | Fiber's default logger format. | | `CommonFormat` | `"${ip} - - [${time}] "${method} ${url} ${protocol}" ${status} ${bytesSent}\n"` | Common Log Format (CLF) used in web server logs. | | `CombinedFormat` | `"${ip} - - [${time}] "${method} ${url} ${protocol}" ${status} ${bytesSent} "${referer}" "${ua}"\n"` | CLF format plus the `referer` and `user agent` fields. | | `JSONFormat` | `"{\"time\":\"${time}\",\"ip\":\"${ip}\",\"method\":\"${method}\",\"url\":\"${url}\",\"status\":${status},\"bytesSent\":${bytesSent}}\n"` | JSON format for structured logging. | | `ECSFormat` | `"{\"@timestamp\":\"${time}\",\"ecs\":{\"version\":\"1.6.0\"},\"client\":{\"ip\":\"${ip}\"},\"http\":{\"request\":{\"method\":\"${method}\",\"url\":\"${url}\",\"protocol\":\"${protocol}\"},\"response\":{\"status_code\":${status},\"body\":{\"bytes\":${bytesSent}}}},\"log\":{\"level\":\"INFO\",\"logger\":\"fiber\"},\"message\":\"${method} ${url} responded with ${status}\"}\n"` | Elastic Common Schema (ECS) format for structured logging. | :::tip `${bytesSent}` returns the value the `Content-Length` response header declares when the logger runs. For an ordinary buffered response fasthttp fills the header in on write, after the logger, so `0` is logged; a streaming response of unknown length (chunked encoding) logs `-1`. Likewise `${bytesReceived}` reports the request's declared `Content-Length`: `-2` for a request without a body and `-1` for a chunked one. Fiber does not calculate the actual body sizes for performance reasons. ::: :::tip `${resBody}` logs nothing for a streamed response (`SendStream`, `SendStreamWriter`, and `SendFile` for a large file). Reading such a body would pull the whole stream into memory and turn the response into a buffered one — an SSE route would deliver nothing until its writer finished — so logging leaves it alone. ::: ### The `${ips}` tag `${ips}` logs the chain the framework parsed, `Ctx.IPs()`, joined with `,`. Reading `X-Forwarded-For` here separately meant reading it a second way, and under [`DisableHeaderNormalizing`](../api/fiber.md#config) a lower-case `x-forwarded-for:` logged an empty chain while `Ctx.IPs()` went on returning it. It is not the list any trust decision is made from. `Ctx.IPs()` parses `X-Forwarded-For` unconditionally, while `Ctx.IP()` consults [`TrustProxy`](../api/fiber.md#config) and reads [`ProxyHeader`](../api/fiber.md#config), which need not be `X-Forwarded-For` at all — so `${ips}` and the address Fiber acted on can name different hosts. Because the entries are split and trimmed rather than echoed as sent, repeated `X-Forwarded-For` header lines and a single comma-joined one log identically, which is what [RFC 9110 §5.2](https://www.rfc-editor.org/rfc/rfc9110.html#section-5.2) says they are. Treat a logged chain as attacker-controlled, trusted peer or not. A proxy you trust appends the address it saw to whatever the client already put in `X-Forwarded-For`, and `Ctx.IPs()` returns every element without the right-to-left walk `Ctx.IP()` uses, so the entries to the left of the ones your own infrastructure added are still the client's to choose. Use `${ip}`, which is the peer address, when you need one you can rely on. ### Control-Character Sanitization Values that come from the request are scrubbed before they reach the log stream: every ASCII control byte (C0 and DEL) is replaced with a space, and horizontal tab is preserved. Without this, a percent-decoded query parameter, form field, or request body containing `\r\n` could forge additional access-log lines and corrupt an audit trail. Scrubbing covers the default format as well as these tags: `${path}` `${url}` `${ua}` `${referer}` `${ip}` `${ips}` `${host}` `${scheme}` `${route}` `${body}` `${resBody}` `${reqHeaders}` `${queryParams}` `${error}` `${reqHeader:}` `${respHeader:}` `${query:}` `${form:}` `${cookie:}` `${locals:}` Tags whose values the framework controls — `${status}`, `${method}`, `${protocol}`, `${port}`, `${latency}`, `${pid}`, `${time}`, `${bytesSent}`, `${bytesReceived}` and the color tags — are written unchanged. `${method}` and `${protocol}` come from the request line, which fasthttp rejects outright if it holds a control byte. Only ASCII controls are replaced. Bytes at or above `0x80` pass through untouched, so C1 controls (U+0080–U+009F, including NEL U+0085, which some log pipelines treat as a line break) survive scrubbing. Handle those yourself if your values can carry them. :::caution Three paths bypass the built-in scrubbing, because each one replaces the renderer rather than wrapping it: - `Config.CustomTags` - `RegisterTag` / `MustRegisterTag` - `Config.LoggerFunc`, which replaces the rendering pipeline wholesale Anything request-derived that you write from one of these needs scrubbing. Use `logger.SanitizeValue`, which applies exactly what the built-in tags apply: ```go logger.MustRegisterTag("tenant", func(output logger.Buffer, c fiber.Ctx, _ *logger.Data, _ string) (int, error) { return output.WriteString(logger.SanitizeValue(c.Get("X-Tenant-ID"))) }) ``` `RegisterContextTag` is not on that list: it wraps your extractor rather than being one, so what the extractor returns is scrubbed on the way out — in both the access-log renderer and the `log` package one. Fiber's own context tags — `${username}`, `${api-key}`, `${csrf-token}`, `${requestid}`, `${session-id}` — are registered through it, and the middleware behind each one validates or redacts at the source as well. ::: ### Constants ```go // Logger variables const ( TagPid = "pid" TagTime = "time" TagReferer = "referer" TagProtocol = "protocol" TagScheme = "scheme" TagPort = "port" TagIP = "ip" TagIPs = "ips" TagHost = "host" TagMethod = "method" TagPath = "path" TagURL = "url" TagUA = "ua" TagLatency = "latency" TagStatus = "status" // response status TagResBody = "resBody" // response body TagReqHeaders = "reqHeaders" TagQueryStringParams = "queryParams" // request query parameters TagBody = "body" // request body TagBytesSent = "bytesSent" TagBytesReceived = "bytesReceived" TagRoute = "route" TagError = "error" TagReqHeader = "reqHeader:" // request header TagRespHeader = "respHeader:" // response header TagQuery = "query:" // request query TagForm = "form:" // request form TagCookie = "cookie:" // request cookie TagLocals = "locals:" // colors TagBlack = "black" TagRed = "red" TagGreen = "green" TagYellow = "yellow" TagBlue = "blue" TagMagenta = "magenta" TagCyan = "cyan" TagWhite = "white" TagReset = "reset" ) ``` --- ## Paginate Pagination middleware for [Fiber](https://github.com/gofiber/fiber) that extracts pagination parameters from query strings and stores them in the request context. Supports page-based, offset-based, and cursor-based pagination with multi-field sorting. ### Signatures ```go func New(config ...Config) fiber.Handler func FromContext(ctx any) (*PageInfo, bool) func NewPageInfo(page, limit, offset int, sort []SortField) *PageInfo func SortOrderFromString(s string) SortOrder ``` `FromContext` accepts `fiber.CustomCtx`, `fiber.Ctx`, `*fasthttp.RequestCtx`, or `context.Context`. It returns `nil, false` when the middleware did not run for the request, for example because `Next` skipped it. `NewPageInfo` builds a `PageInfo` from the given values without running the middleware, which helps when you create fixtures for tests or paginate outside a request. It keeps its arguments as given and does not clamp them, so `NewPageInfo(-1, 10000, -5, nil)` holds all three invalid values. `SortOrderFromString` converts `"asc"` or `"desc"` (case-insensitive) into a `SortOrder`; any other value gives `ASC`. ### Examples Import the middleware package: ```go import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/paginate" ) ``` Once your Fiber app is initialized, choose one of the following approaches: #### Basic Usage ```go app.Use(paginate.New()) app.Get("/users", func(c fiber.Ctx) error { pageInfo, ok := paginate.FromContext(c) if !ok { return fiber.ErrBadRequest } // Use pageInfo.Page, pageInfo.Limit, pageInfo.Start() // GET /users?page=2&limit=20 → Page: 2, Limit: 20, Start(): 20 return c.JSON(pageInfo) }) ``` #### Sorting ```go app.Use(paginate.New(paginate.Config{ SortKey: "sort", DefaultSort: "id", AllowedSorts: []string{"id", "name", "created_at"}, })) // GET /users?sort=name,-created_at // → Sort: [{Field: "name", Order: "asc"}, {Field: "created_at", Order: "desc"}] ``` #### Cursor Pagination ```go app.Use(paginate.New()) app.Get("/feed", func(c fiber.Ctx) error { pageInfo, ok := paginate.FromContext(c) if !ok { return fiber.ErrBadRequest } if pageInfo.Cursor != "" { // Decode the cursor to get keyset values values := pageInfo.CursorValues() // Use values["id"], values["created_at"], etc. for WHERE clause } // results is a slice of items from your database query // After fetching results, set the next cursor for the client if len(results) > 0 { lastItem := results[len(results)-1] if err := pageInfo.SetNextCursor(map[string]any{ "id": lastItem.ID, "created_at": lastItem.CreatedAt, }); err != nil { return err } } return c.JSON(fiber.Map{ "data": results, "has_more": pageInfo.HasMore, "next_cursor": pageInfo.NextCursor, }) }) // First request: GET /feed?limit=20 // Next request: GET /feed?cursor=&limit=20 ``` A request with a non-empty cursor is handled as cursor pagination: the page and offset parameters are ignored, so `Page` and `Offset` are `0` and `Start()` returns `0`. An empty cursor falls back to page-based pagination. #### Custom Configuration ```go app.Use(paginate.New(paginate.Config{ PageKey: "p", LimitKey: "size", DefaultPage: 1, DefaultLimit: 25, SortKey: "order_by", DefaultSort: "created_at", AllowedSorts: []string{"created_at", "name", "email"}, CursorKey: "after", CursorParam: "starting_after", })) ``` ### Config | Property | Type | Description | Default | |:-------------|:-------------------------|:-------------------------------------------------------------------|:-----------| | Next | `func(fiber.Ctx) bool` | Next defines a function to skip this middleware when returned true. | `nil` | | PageKey | `string` | Query string key for page number. | `"page"` | | DefaultPage | `int` | Default page number. | `1` | | LimitKey | `string` | Query string key for limit. | `"limit"` | | DefaultLimit | `int` | Default items per page. It is capped at `MaxLimit`, and a limit below 1 in the request also falls back to it. | `10` | | SortKey | `string` | Query string key for sort. While it is empty, the query string cannot change the sort order. | `""` | | DefaultSort | `string` | Default sort field, applied in ascending order when the request names no allowed sort field. | `"id"` | | AllowedSorts | `[]string` | Whitelist of allowed sort fields. If nil or empty, request sort fields are ignored and the default sort is used. | `nil` | | OffsetKey | `string` | Query string key for offset. | `"offset"` | | CursorKey | `string` | Query string key for cursor-based pagination. | `"cursor"` | | CursorParam | `string` | Optional alias for the cursor query key. | `""` | | MaxLimit | `int` | Maximum items per page. A value below 1 selects the default. | `100` (`paginate.DefaultMaxLimit`) | ### Default Config ```go var ConfigDefault = Config{ Next: nil, PageKey: "page", DefaultPage: 1, LimitKey: "limit", DefaultLimit: 10, MaxLimit: 100, DefaultSort: "id", OffsetKey: "offset", CursorKey: "cursor", } ``` ### PageInfo The `PageInfo` struct is stored in the request context. Its fields, with the JSON keys they serialize to, are listed below. The bounds in the descriptions hold for the value the middleware stores, not for one built with `NewPageInfo`: | Field | JSON key | Description | |:-------------|:--------------|:-------------------------------------------------------------------------------------------------| | `Page` | `page` | Current page, never below 1. It is `0` on a cursor request. | | `Limit` | `limit` | Items per page, never above `MaxLimit`. | | `Offset` | `offset` | Offset taken from `OffsetKey`, never negative. When above 0, `Start()` returns it. | | `Sort` | `sort` | Parsed sort fields as `[]SortField`. It holds the default sort, ascending, when the request names no allowed field. | | `Cursor` | `cursor` | Cursor token of the request. Omitted from the JSON when empty. | | `NextCursor` | `next_cursor` | Token for the next page, set by `SetNextCursor`. Omitted from the JSON when empty. | | `HasMore` | `has_more` | Set to `true` by `SetNextCursor`. Omitted from the JSON when `false`. | Each `SortField` has a `Field` name and an `Order`, serialized as `field` and `order`. `Order` is a `SortOrder` string, either `paginate.ASC` (`"asc"`) or `paginate.DESC` (`"desc"`). `PageInfo` provides these methods: | Method | Description | |:------------------------------------------------|:---------------------------------------------------------------| | `Start() int` | Returns calculated start index (from page/limit or offset) | | `SortBy(field, order)` | Adds a sort field (chainable) | | `NextPageURL(baseURL)` | Generates next page URL with default keys | | `NextPageURLWithKeys(baseURL, pageKey, limitKey)` | Generates next page URL with custom query keys | | `PreviousPageURL(baseURL)` | Generates previous page URL (empty on page 1) | | `PreviousPageURLWithKeys(baseURL, pageKey, limitKey)` | Generates previous page URL with custom query keys | | `NextCursorURL(baseURL)` | Generates next cursor URL (empty if no more) | | `NextCursorURLWithKeys(baseURL, cursorKey, limitKey)` | Generates next cursor URL with custom query keys | | `CursorValues()` | Decodes cursor token into key-value map (`nil` for an empty or invalid token); numbers decode as `float64` with the default JSON decoder | | `SetNextCursor(values)` | Encodes values into cursor token, sets `NextCursor` and `HasMore`; returns an error wrapping `paginate.ErrCursorEncode` | ### Safety - Limit is capped at `MaxLimit` (default: 100, configurable) to prevent excessive memory usage - Page values below 1 reset to 1 - Negative offsets reset to 0 - Sort fields are validated against `AllowedSorts` - Cursor tokens exceeding 2048 characters are rejected with `400 Bad Request` - `SetNextCursor` returns an error if the encoded token would exceed 2048 characters, preventing the server from issuing cursors it would later reject - Invalid cursor tokens return `400 Bad Request` via Fiber's error handler - If `DefaultSort` is not included in `AllowedSorts`, it falls back to the first allowed sort field - URL helpers preserve existing query parameters when building pagination links --- ## Pprof Pprof middleware exposes runtime profiling data for analysis with the Go `pprof` tool. Once you add it with `app.Use`, it serves the profiles under `/debug/pprof/`. The middleware claims a request only when the path is the prefix plus `/debug/pprof` itself or continues it at a segment boundary, so an application route such as `/debug/pprof-old` still reaches its own handler. Within its paths, `303 See Other` redirects settle the spelling: `/debug/pprof` goes to `/debug/pprof/`, a trailing slash after a profile name (`/debug/pprof/heap/`) is dropped, and a name that is not a profile (`/debug/pprof/nope`) goes to the index. ### Signatures ```go func New(config ...Config) fiber.Handler ``` ### Examples Import the middleware package: ```go import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/pprof" ) ``` Once your Fiber app is initialized, use the middleware as shown: ```go // Initialize default config app.Use(pprof.New()) // Or customize the config // For multi-ingress systems, add a URL prefix: app.Use(pprof.New(pprof.Config{Prefix: "/endpoint-prefix"})) // The resulting URL is "/endpoint-prefix/debug/pprof/" ``` ### Config | Property | Type | Description | Default | |:---------|:------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------:| | Next | `func(fiber.Ctx) bool` | Next defines a function to skip this middleware when it returns true. | `nil` | | Prefix | `string` | Prefix adds a segment before `/debug/pprof`; it must start with a slash and omit the trailing slash. Example: `/federated-fiber` | `""` | ### Default Config ```go var ConfigDefault = Config{ Next: nil, Prefix: "", } ``` --- ## Proxy The Proxy middleware forwards requests to one or more upstream servers. ### Signatures ```go // Balancer creates a load balancer among multiple upstream servers. func Balancer(config ...Config) fiber.Handler // Forward performs the given http request and fills the given http response. func Forward(addr string, clients ...*fasthttp.Client) fiber.Handler // Do performs the given http request and fills the given http response. func Do(c fiber.Ctx, addr string, clients ...*fasthttp.Client) error // DoRedirects performs the given http request and fills the given http response while following up to maxRedirectsCount redirects. func DoRedirects(c fiber.Ctx, addr string, maxRedirectsCount int, clients ...*fasthttp.Client) error // DoDeadline performs the given request and waits for response until the given deadline. func DoDeadline(c fiber.Ctx, addr string, deadline time.Time, clients ...*fasthttp.Client) error // DoTimeout performs the given request and waits for response during the given timeout duration. func DoTimeout(c fiber.Ctx, addr string, timeout time.Duration, clients ...*fasthttp.Client) error // DomainForward performs the given http request based on the provided domain and fills the given http response. func DomainForward(hostname string, addr string, clients ...*fasthttp.Client) fiber.Handler // BalancerForward performs the given http request based round robin balancer and fills the given http response. func BalancerForward(servers []string, clients ...*fasthttp.Client) fiber.Handler // WithClient sets the global client the runtime helpers dispatch through. It panics for a nil client. func WithClient(cli *fasthttp.Client) // WithSecurityPolicy installs the global SecurityPolicy and returns the previous one. func WithSecurityPolicy(policy SecurityPolicy) SecurityPolicy // DefaultSecurityPolicy returns the secure-by-default SecurityPolicy. func DefaultSecurityPolicy() SecurityPolicy ``` ### Security The proxy middleware applies several defenses by default. They can be relaxed via `Config.SecurityPolicy` (for `Balancer`) or `proxy.WithSecurityPolicy` (for the runtime helpers `Do`, `Forward`, `DoRedirects`, `DoTimeout`, `DoDeadline`, `DomainForward` and `BalancerForward`). #### SSRF protection Upstream addresses that resolve to loopback, RFC 1918 private, link-local (including the `169.254.169.254` cloud-metadata address), multicast, unspecified, or RFC 6598 CGNAT ranges are rejected with `ErrUpstreamHostBlocked`. If any resolved IP falls in a blocked range the upstream is rejected, mitigating DNS-rebinding attempts that return a mix of public and private answers. A name that cannot be resolved is rejected the same way. An IPv6 literal cannot carry a blocked IPv4 address past the list. The IPv4 address embedded in an IPv4-mapped (`::ffff:a.b.c.d`), IPv4-compatible, IPv4-translated (`::ffff:0:a.b.c.d`), NAT64 well-known (`64:ff9b::/96`) or intra-site tunnel (RFC 5214) address is checked against the same ranges. The 6to4 (`2002::/16`), Teredo (`2001::/32`) and NAT64 local-use (`64:ff9b:1::/48`) ranges are rejected as a whole, because unwrapping one position would leave a bypass: Teredo can hide an IPv4 address in either of two fields, the NAT64 local-use prefix lets the operator choose where it sits, and 6to4 is deprecated ([RFC 7526](https://www.rfc-editor.org/rfc/rfc7526)). For `Balancer`, the resolved IP is re-validated at **dial time** (via a guarded `Dial` on each upstream `fasthttp.HostClient`), which both defeats DNS-rebinding and avoids resolving hostnames at startup — a transient DNS failure won't panic your application. DNS lookups are bounded by a 5-second timeout. :::caution DNS-rebinding scope Dial-time re-validation protects every path but one. `Balancer` builds its own `HostClient`s with a guarded `Dial`. The runtime helpers (`Do`, `DoRedirects`, `DoTimeout`, `DoDeadline`, `Forward`, `DomainForward` and `BalancerForward`) install the same guard on the `*fasthttp.Client` they dispatch through, using its `ConfigureClient` hook after any hook the client already had. The default client and a client registered with `proxy.WithClient` are guarded before their first request, so they are fully protected. A client passed to a helper per call is guarded on first use, and a host it had already dialed keeps its unguarded host client: register such a client with `proxy.WithClient` before it serves traffic, or hand the proxy a client that nothing else uses. The one path with no guard is a custom `Config.Client` (`*fasthttp.LBClient`), whose dialers are yours: validate the resolved IP there. ::: `Do`, `DoRedirects`, `DoTimeout`, `DoDeadline` and `Forward` return a rejected upstream as an error. `Balancer`, `DomainForward` and `BalancerForward` check their upstreams when the handler is built and panic with that error instead, so a bad entry stops the application at startup. `DomainForward` and `BalancerForward` also resolve each hostname for that check unless `AllowPrivateIPs` is set, so an unresolvable name panics too, and so does a loopback or private upstream: call `proxy.WithSecurityPolicy` before building them when the upstream is internal. `Balancer` leaves hostname resolution to dial time and rejects only an IP literal in a blocked range at startup. Set `SecurityPolicy.AllowPrivateIPs = true` to opt out — required when proxying to internal services on the same network. #### Scheme allowlist Only `http` and `https` upstream schemes are accepted by default; `file://`, `gopher://`, `ftp://`, and other schemes are rejected with `ErrUpstreamSchemeNotAllowed`. An empty address, or one without a host, is rejected with `ErrUpstreamHostInvalid`. Override the schemes via `SecurityPolicy.AllowedSchemes`. #### HTTPS-to-HTTP redirect downgrades `DoRedirects` rejects redirects from HTTPS origins to plaintext HTTP targets with `ErrRedirectDowngrade`. Following such a redirect would leak any cookies or `Authorization` headers established under TLS. Set `SecurityPolicy.AllowHTTPSDowngrade = true` to override. When a redirect crosses to a **different host**, `DoRedirects` strips `Authorization`, `Proxy-Authorization`, `Proxy-Authenticate`, `WWW-Authenticate`, `Cookie` and the obsolete `Cookie2`, so credentials bound to the original origin are not forwarded to a third-party upstream. This is `net/http`'s own set. Same-host redirects retain these headers. #### RFC 7230 hop-by-hop header stripping `Connection`, `Keep-Alive`, `Proxy-Authenticate`, `Proxy-Authorization`, `TE`, `Trailer`, `Transfer-Encoding`, and `Upgrade` are stripped from both the outbound request and the inbound response, along with every header listed in the `Connection` field per RFC 7230 §6.1. This prevents request smuggling (`TE`/`Transfer-Encoding`), proxy-credential forwarding, and protocol-upgrade leaks. The legacy `KeepConnectionHeader` option preserves only the literal `Connection` header for backwards compatibility; the other hop-by-hop headers are still stripped. To preserve every hop-by-hop header (not recommended), set `SecurityPolicy.KeepHopByHopHeaders = true`. The names a client lists in `Connection` are removed from the request as it was received, with one intentional exception to RFC 9110 §7.6.1: the forwarding headers `X-Real-IP`, `Forwarded`, and every `X-Forwarded-*` header stay. They tell the upstream about the client as this hop saw it, and an upstream that trusts the gateway acts on them, so they are the gateway's to write and not the client's to have removed. `Balancer`, `Forward`, `DomainForward`, and `BalancerForward` write `X-Real-IP` only after the listing has been applied, and the exception also covers a forwarding header your application sets before the proxy runs, including `X-Real-IP` set by hand before `Do`. A client loses nothing by it: a forwarding header it sends is forwarded whenever `Connection` does not name it. :::caution Headers set before the proxy runs Any other header your application adds to the request before the proxy handler runs — in an earlier middleware, or before calling `Do` — is part of the received request as far as the `Connection` listing is concerned, and a client can have it removed by naming it there. Set headers meant for the upstream in `Balancer`'s `ModifyRequest`, which runs after the listing has been applied. ::: #### TLS minimum version `Config.TLSConfig` is cloned with `MinVersion: tls.VersionTLS12` if no minimum is configured, so deprecated TLS versions cannot be negotiated by accident. #### Response body size and connection caps `Config.MaxResponseBodySize` bounds upstream response bodies to protect against memory exhaustion. `Config.MaxConnsPerHost` (default `1024`) caps concurrent connections per upstream to limit fan-out from a single hot host. #### X-Real-IP spoof prevention `Balancer`, `Forward`, `DomainForward`, and `BalancerForward` automatically overwrite the `X-Real-IP` header with `c.IP()` before forwarding, so clients cannot spoof their address. The address is read from the request as received and written after the `Connection` listing has been applied, and `X-Real-IP` is exempt from that listing, so a client cannot have the header removed by naming it there either (see [hop-by-hop header stripping](#rfc-7230-hop-by-hop-header-stripping)). `Balancer` writes it before `ModifyRequest` runs, so `ModifyRequest` can still set its own value. `DomainForward` only applies the overwrite when the request host matches the configured hostname (matched case-insensitively per RFC 9110 §4.2.3, with or without a port in the `Host` header); non-matching requests are passed on to the next handler unchanged. A configured hostname without a port matches any port. One with a port, such as `example.com:8080` or `[::1]:8080` for an IPv6 literal, matches only that port. A hostname written with non-ASCII letters is converted to Punycode (`xn--` labels) once when the handler is built, because clients send that form in `Host`. After any of the forwarding helpers returns, the request carries its original URI and `Host` again, so middleware running after `Next` still sees the request the client sent. When using `Do`, `DoRedirects`, `DoDeadline`, or `DoTimeout` directly, the `X-Real-IP` header is not set automatically — set it manually if needed: ```go ip := c.IP() c.Request().Header.Del("X-Real-IP") c.Request().Header.Add("X-Real-IP", ip) ``` `Set` alone is not enough here. It replaces the first field line with that name and leaves any others in place, so a client that sends `X-Real-IP` twice keeps one of its own values on the wire, and the upstream — which may read the last line, or join the pair per RFC 9110 §5.2 — attributes the request to an address the client chose. Delete first so exactly one line survives. Resolve `c.IP()` before the delete as well: with `Config.ProxyHeader` set to `X-Real-IP`, `c.IP()` reads the very header being replaced. The line you write survives a client listing `X-Real-IP` in `Connection`, as the forwarding headers are exempt from that listing. :::caution With `DisableHeaderNormalizing`, delete every spelling `Del` matches the stored key byte for byte. That finds every line while Fiber canonicalizes header names, which is the default — but under [`DisableHeaderNormalizing`](../api/fiber.md#config) the store keeps the spelling the client sent, and lower case is what HTTP/2 and HTTP/3 put on the wire. `Del("X-Real-IP")` then leaves a client-sent `x-real-ip` untouched and the `Add` lands beside it, which is the pair the delete exists to prevent. The middleware's own `Balancer`, `Forward`, `DomainForward` and `BalancerForward` handle this. Doing it by hand takes one pass to collect the spellings and another to remove them, since deleting while iterating the store is not safe: ```go ip := c.IP() h := &c.Request().Header var spellings []string for k := range h.All() { if utils.EqualFold(utils.UnsafeString(k), "X-Real-IP") { spellings = append(spellings, string(k)) } } for _, name := range spellings { h.Del(name) } h.Add("X-Real-IP", ip) ``` `utils` here is `github.com/gofiber/utils/v2`. ::: #### Request target `Balancer`, `DomainForward` and `BalancerForward` forward the path the router matched, `c.Path()`, followed by the query. They do not forward the request line as it arrived. The two differ, and the difference is what an attacker uses: fasthttp's normalization of the raw request line decodes `%2F` into a separator and merges repeated slashes, so `/public/..%2Fadmin/secret` and `//admin/secret` used to reach the upstream as `/admin/secret`, a path no middleware mounted on `/admin` had seen. Routed, they stay `/public/..%2Fadmin/secret` and `//admin/secret`, and by default the proxy refuses to forward either, since an upstream that decodes `%2F` or merges `//` would read them as `/admin/secret` as well. Dot segments are resolved before matching, so `/public/../admin/secret` runs the `/admin` middleware and is forwarded as `/admin/secret`. An escape of an unreserved character is decoded (`%41` becomes `A`), every other escape is kept as sent and a stray `%` is forwarded as `%25`. A byte a path may not carry raw ([RFC 3986 Section 3.3](https://www.rfc-editor.org/rfc/rfc3986#section-3.3)), such as `|`, `{`, `"` or a byte of UTF-8, is forwarded as its escape, as fasthttp wrote it before, so `/über` reaches the upstream as `/%C3%BCber`; the sub-delims (`!$&'()*+,;=`), `:` and `@` stay raw. With `UnescapePath` enabled the decoded path is escaped again segment by segment, so a separator that came from `%2F` is forwarded as the separator the router matched it as. The query is forwarded byte for byte as the client sent it, so a signed URL keeps its signature. Only when a handler changed the arguments through `QueryArgs()` is it the changed arguments, as fasthttp serializes them; reading them, as `c.Query()` does, changes nothing. `Balancer` puts the original request line back once the upstream has answered, so `c.OriginalURL()` in a middleware that runs afterwards is unchanged. - `Balancer` dials the configured host and forwards the target as is, so every entry in `Servers` must be a scheme and host only. An entry with a path, userinfo, query or fragment panics at startup with `ErrUpstreamNotOrigin` instead of dropping that part silently, which would have left `http://backend/api` reaching all of the upstream. `DomainForward` and `BalancerForward` prepend the path of their upstream URL, and the joined path keeps the upstream host pinned in configuration whatever the request contains: `//attacker.example/path`, `@attacker` and `/foo://hijack.example` all stay paths. - `Balancer` answers a request whose `Host` header carries userinfo, such as `svc:pw@backend`, with `400 Bad Request`. fasthttp would read the userinfo as credentials and send them upstream as a `Basic` `Authorization` header, replacing one the application had set. - `Balancer` also answers `400 Bad Request` for a routed target fasthttp would read as an authority rather than a path: one that begins with `//` and holds `://`, or any target beginning with `//` on a request without a `Host` header. The router never saw such a target as an authority, and it need not have arrived as one: `..` resolution builds `//u:pw@evil.example/a://b` out of `//u:pw@evil.example/a:/x/..//b`, and under `UnescapePath` a decoded `%3A%2F%2F` spells the `://`. Set as the request line, it would hand the upstream a `Host`, and a `Basic` `Authorization` header, taken from the path rather than from the application. - `Balancer`, `DomainForward` and `BalancerForward` answer `400 Bad Request` for a routed path an upstream could read as another path, one under a prefix whose middleware never ran for it. That is a path holding: - an escaped slash (`%2F`) or an empty segment (`//`). An upstream that decodes `%2F` into a separator or merges repeated slashes before it matches routes, as a `net/http` file server or a fasthttp server does, would map `/public/..%2Fadmin/secret`, `/admin%2Fsecret` and `//admin/secret` to `/admin/secret`. An upstream that keeps both as sent can be reached with `SecurityPolicy.AllowAmbiguousSlashes`, described below. - a backslash, raw or escaped as `%5C`. WHATWG URL parsers, as in Node.js, Bun, Deno and Cloudflare Workers, and IIS read it as a separator, so `/admin\secret` and `/public/..%5Cadmin/secret` would reach `/admin`. - a `.` or `..` segment carrying parameters, such as `..;` or `..;jsessionid=x`. Servlet containers such as Tomcat and Jetty strip the parameters before they resolve dot segments, so `/public/..;/admin/secret` would reach `/admin/secret`. - an escape of an unreserved character, such as `%2e` or `%70`. The router decodes every such escape when it normalizes a path, so one still in `c.Path()` can only have been forged by a stray `%`, and an upstream decoding it again would serve a name no middleware matched. To forward such a target regardless, build it from `c.Path()` and call `Do`. - `SecurityPolicy.AllowAmbiguousSlashes` lets `Balancer`, `DomainForward` and `BalancerForward` forward a path holding `%2F` or `//`, for an upstream that addresses names such as GitLab-style `group%2Fproject` ids or object keys with a doubled slash. Set it on `Config.SecurityPolicy` for `Balancer`, or install it with `proxy.WithSecurityPolicy` for `DomainForward` and `BalancerForward`. Enable it only when every upstream behind the handler keeps `%2F` and `//` as sent; a backslash, a dot segment with parameters and a forged escape are refused regardless. - A path override without a leading slash, as a rewrite of `/go/*` to `$1` produces for `/go/http://u:pw@evil.example/x`, is forwarded rooted, as the path `/http://u:pw@evil.example/x`, rather than as the absolute URL a request line would read it as. - With a custom `Client`, every `*fasthttp.HostClient` among its `Clients` gets `DisablePathNormalizing` set, since a client left normalizing would decode `%2F` into a separator and merge `//` on the way out. Any other `BalancingClient` cannot be told, so with one present `Balancer` answers `400 Bad Request` for a routed target that normalization would change: one holding an empty segment or an escape it would decode into a byte it then writes raw, such as `%3B` or `%40`. An escape of a byte it writes escaped again, such as `%20` or the `%C3%BC` of `/über`, is forwarded. - `Do`, `Forward`, `DomainForward`, `BalancerForward` and their variants switch path normalization off on the default client, on a client registered with `WithClient` and on every host client a per-call `*fasthttp.Client` creates once the proxy has it. fasthttp applies that setting only to the host client it creates for an upstream, so a per-call client that already reached that upstream before it was handed to the proxy keeps the host client it made then, and that one still normalizes: `/public/..%2Fadmin/secret` would leave it as `/admin/secret`. Register such a client with `WithClient` before it serves requests, hand the proxy a client used for nothing else, or create the client with `DisablePathNormalizing: true`. :::caution Upstream normalization The proxy cannot control what the upstream does with the target beyond what it refuses to forward. A target holding `%2F`, `//`, a backslash, a dot segment with parameters or a forged escape is not forwarded by default, so an upstream that decodes, merges or strips parameters cannot be handed a path the router did not match through them. An escape of another reserved character, such as `%3B`, `%3F` or `%23`, is forwarded as sent, and so are a segment's case and a trailing dot or space, any of which an upstream on Windows or a case-insensitive filesystem may treat as another name. Authorize on the upstream as well. ::: When you build the target for `Do`, `Forward` or their variants yourself, derive it from `c.Path()` and `c.Request().URI().QueryString()` rather than from `c.OriginalURL()`, which is the request line as it arrived, before the router resolved dot segments. ### Examples Import the middleware package: ```go import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/proxy" ) ``` Once your Fiber app is initialized, you can use the middleware as shown: ```go // Use proxy.WithClient to set a global custom client. proxy.WithClient(&fasthttp.Client{ NoDefaultUserAgentHeader: true, DisablePathNormalizing: true, MaxConnsPerHost: 2048, // Allow self-signed certificates when proxying to HTTPS targets. // SECURITY: disables certificate verification — use only when the // upstream is on a trusted network. TLSConfig: &tls.Config{ InsecureSkipVerify: true, MinVersion: tls.VersionTLS12, }, }) // Relax SSRF protection for local development against loopback servers. // SECURITY: in production, leave AllowPrivateIPs false (the default) and // list explicit upstream hosts so the proxy cannot be coerced into // reaching internal services or cloud-metadata endpoints. prev := proxy.WithSecurityPolicy(proxy.SecurityPolicy{ AllowedSchemes: []string{"http", "https"}, AllowPrivateIPs: true, }) defer proxy.WithSecurityPolicy(prev) // Forward requests for a specific domain with proxy.DomainForward. app.Get("/payments", proxy.DomainForward("docs.gofiber.io", "http://localhost:8000")) // Forward to a URL using a custom client app.Get("/gif", proxy.Forward("https://i.imgur.com/IWaBepg.gif", &fasthttp.Client{ NoDefaultUserAgentHeader: true, DisablePathNormalizing: true, })) // Make a proxied request within a handler app.Get("/:id", func(c fiber.Ctx) error { url := "https://i.imgur.com/" + c.Params("id") + ".gif" if err := proxy.Do(c, url); err != nil { return err } // Remove Server header from response c.Response().Header.Del(fiber.HeaderServer) return nil }) // Proxy requests while following redirects app.Get("/proxy", func(c fiber.Ctx) error { if err := proxy.DoRedirects(c, "http://google.com", 3); err != nil { return err } // Remove Server header from response c.Response().Header.Del(fiber.HeaderServer) return nil }) // Proxy requests and wait up to five seconds before timing out app.Get("/proxy", func(c fiber.Ctx) error { if err := proxy.DoTimeout(c, "http://localhost:3000", time.Second * 5); err != nil { return err } // Remove Server header from response c.Response().Header.Del(fiber.HeaderServer) return nil }) // Proxy requests with a deadline one minute from now app.Get("/proxy", func(c fiber.Ctx) error { if err := proxy.DoDeadline(c, "http://localhost", time.Now().Add(time.Minute)); err != nil { return err } // Remove Server header from response c.Response().Header.Del(fiber.HeaderServer) return nil }) // Minimal round-robin balancer app.Use(proxy.Balancer(proxy.Config{ Servers: []string{ "http://localhost:3001", "http://localhost:3002", "http://localhost:3003", }, })) // Keep the Connection header when proxying app.Use(proxy.Balancer(proxy.Config{ Servers: []string{ "http://localhost:3001", }, KeepConnectionHeader: true, })) // Or extend your balancer for customization app.Use(proxy.Balancer(proxy.Config{ Servers: []string{ "http://localhost:3001", "http://localhost:3002", "http://localhost:3003", }, MaxConnsPerHost: 2048, ModifyRequest: func(c fiber.Ctx) error { c.Request().Header.Set("X-Real-IP", c.IP()) return nil }, ModifyResponse: func(c fiber.Ctx) error { c.Response().Header.Del(fiber.HeaderServer) return nil }, })) // Or this way if the balancer is using https and the destination server is only using http. app.Use(proxy.BalancerForward([]string{ "http://localhost:3001", "http://localhost:3002", "http://localhost:3003", })) // Make round robin balancer with IPv6 support. app.Use(proxy.Balancer(proxy.Config{ Servers: []string{ "http://[::1]:3001", "http://127.0.0.1:3002", "http://localhost:3003", }, // Enable TCP4 and TCP6 network stacks. DialDualStack: true, })) ``` ### Config | Property | Type | Description | Default | |:----------------|:-----------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------------| | Next | `func(fiber.Ctx) bool` | Next defines a function to skip this middleware when it returns true. | `nil` | | Servers | `[]string` | Servers defines a list of `://` HTTP servers, which are used in a round-robin manner. Each entry is a scheme and host only; one with a path, userinfo, query or fragment panics at startup with `ErrUpstreamNotOrigin`. i.e.: "[https://foobar.com](https://foobar.com), [http://www.foobar.com](http://www.foobar.com)" | (Required unless `Client` is set) | | ModifyRequest | `fiber.Handler` | ModifyRequest allows you to alter the request. | `nil` | | ModifyResponse | `fiber.Handler` | ModifyResponse allows you to alter the response. | `nil` | | Timeout | `time.Duration` | Timeout is the request timeout used when calling the proxy client. | 1 second | | MaxConnsPerHost | `int` | Maximum number of connections per upstream host. The default proxy client and balancer host clients use this limit unless you override it with `WithClient`, a per-handler client, or `proxy.Config`. | `1024` | | ReadBufferSize | `int` | Per-connection buffer size for requests' reading. This also limits the maximum header size. Increase this buffer if your clients send multi-KB RequestURIs and/or multi-KB headers (for example, BIG cookies). | (Not specified) | | WriteBufferSize | `int` | Per-connection buffer size for responses' writing. | (Not specified) | | KeepConnectionHeader | `bool` | Keeps the `Connection` header when set to `true`. By default the header is removed to comply with RFC 7230 §6.1 and avoid proxy loops. Other hop-by-hop headers are still stripped regardless of this setting. | `false` | | TLSConfig | `*tls.Config` | TLS config for the HTTP client. Cloned with `MinVersion: tls.VersionTLS12` when no minimum is set. | `nil` | | DialDualStack | `bool` | Client will attempt to connect to both IPv4 and IPv6 host addresses if set to true. | `false` | | Client | `*fasthttp.LBClient` | Client is a custom client when client config is complex. Each `*fasthttp.HostClient` among its `Clients` gets `DisablePathNormalizing` set; see [Request target](#request-target). When it is set, `Servers`, `Timeout`, `MaxConnsPerHost`, `ReadBufferSize`, `WriteBufferSize`, `MaxResponseBodySize`, `TLSConfig` and `DialDualStack` are not used, and its dialers get no SSRF guard (see [SSRF protection](#ssrf-protection)). | `nil` | | SecurityPolicy | `*SecurityPolicy` | Overrides the default SSRF, redirect, and hop-by-hop header rules for this balancer. When `nil`, the package-level policy set via `WithSecurityPolicy` is used. See [Security](#security). | `nil` | | MaxResponseBodySize | `int` | Maximum upstream response body size in bytes. `0` keeps fasthttp's unlimited default. | `0` | ### Default Config ```go var ConfigDefault = Config{ Next: nil, ModifyRequest: nil, ModifyResponse: nil, MaxConnsPerHost: 1024, Timeout: fasthttp.DefaultLBClientTimeout, KeepConnectionHeader: false, } ``` ### Default SecurityPolicy When `Config.SecurityPolicy` is `nil` (and `proxy.WithSecurityPolicy` has not been called), the package falls back to the value returned by `proxy.DefaultSecurityPolicy()`: ```go // DefaultSecurityPolicy returns the secure-by-default policy. func DefaultSecurityPolicy() proxy.SecurityPolicy { return proxy.SecurityPolicy{ AllowedSchemes: []string{"http", "https"}, AllowPrivateIPs: false, AllowHTTPSDowngrade: false, KeepHopByHopHeaders: false, AllowAmbiguousSlashes: false, } } ``` --- ## Recover The Recover middleware for [Fiber](https://github.com/gofiber/fiber) intercepts panics and forwards them to the central [ErrorHandler](../guide/error-handling). ### Signatures ```go func New(config ...Config) fiber.Handler func DefaultPanicHandler(c fiber.Ctx, r any) error ``` ### Examples Import the middleware package: ```go import ( "errors" "fmt" "github.com/gofiber/fiber/v3" recoverer "github.com/gofiber/fiber/v3/middleware/recover" ) ``` Once your Fiber app is initialized, use the middleware like this: ```go // Initialize default config app.Use(recoverer.New()) // Panics in subsequent handlers are caught by the middleware app.Get("/", func(c fiber.Ctx) error { panic("I'm an error") }) ``` Use `PanicHandler` to decide which error the central `ErrorHandler` receives: ```go // Set up a PanicHandler to hide internals. app.Use(recoverer.New(recoverer.Config{PanicHandler: func(c fiber.Ctx, r any) error { return fiber.ErrInternalServerError }})) // You could also just wrap the default handler's error. app.Use(recoverer.New(recoverer.Config{PanicHandler: func(c fiber.Ctx, r any) error { return fmt.Errorf("[RECOVERED]: %w", recoverer.DefaultPanicHandler(c, r)) }})) ``` In more elaborate scenarios you can create a custom error type that your `ErrorHandler` handles differently: ```go // MyCustomRecoveredFromPanicError marks an error that came from a recovered panic. type MyCustomRecoveredFromPanicError struct { Inner error } func (e *MyCustomRecoveredFromPanicError) Error() string { return e.Inner.Error() } func (e *MyCustomRecoveredFromPanicError) Unwrap() error { return e.Inner } app := fiber.New(fiber.Config{ ErrorHandler: func(c fiber.Ctx, err error) error { var recovered *MyCustomRecoveredFromPanicError if errors.As(err, &recovered) { // Report the panic here, then answer without its details. return c.SendStatus(fiber.StatusInternalServerError) } return fiber.DefaultErrorHandler(c, err) }, }) app.Use(recoverer.New(recoverer.Config{PanicHandler: func(c fiber.Ctx, r any) error { return &MyCustomRecoveredFromPanicError{ Inner: recoverer.DefaultPanicHandler(c, r), } }})) ``` ### Config | Property | Type | Description | Default | |:------------------|:-----------------------------|:------------------------------------------------------|:---------------------------| | Next | `func(fiber.Ctx) bool` | Skip when the function returns `true`. | `nil` | | PanicHandler | `func(fiber.Ctx, any) error` | Customize the error returned from a recovered panic. `DefaultPanicHandler` returns the panic value when it is an `error` and `fmt.Errorf("%v", r)` otherwise. | `DefaultPanicHandler` | | EnableStackTrace | `bool` | Call `StackTraceHandler` with the recovered value when a panic is caught. The stack trace is not added to the returned error or the response. | `false` | | StackTraceHandler | `func(fiber.Ctx, any)` | Handle the captured panic when `EnableStackTrace` is `true`. The default writes `recovered panic: ` and the stack trace to `os.Stderr`. | `defaultStackTraceHandler` | ### Default Config ```go var ConfigDefault = recoverer.Config{ Next: nil, PanicHandler: DefaultPanicHandler, StackTraceHandler: defaultStackTraceHandler, EnableStackTrace: false, } ``` --- ## Redirect Redirect middleware maps old URLs to new ones using simple rules. A `RuleList` is tried in the order you write it and the first match wins; the deprecated `Rules` map has no order of its own and is ranked by a heuristic. ### Signatures ```go func New(config ...Config) fiber.Handler ``` ### Examples ```go package main import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/redirect" ) func main() { app := fiber.New() app.Use(redirect.New(redirect.Config{ RuleList: []redirect.Rule{ {From: "/old", To: "/new"}, {From: "/old/*", To: "/new/$1"}, }, StatusCode: fiber.StatusMovedPermanently, })) app.Get("/new", func(c fiber.Ctx) error { return c.SendString("Hello, World!") }) app.Get("/new/*", func(c fiber.Ctx) error { return c.SendString("Wildcard: " + c.Params("*")) }) app.Listen(":3000") } ``` ### Test ```bash curl http://localhost:3000/old curl http://localhost:3000/old/hello ``` ### Config | Property | Type | Description | Default | |:-----------|:--------------------|:------------------------------------------|:-----------------------| | Next | `func(fiber.Ctx) bool` | Skip when function returns true. | nil | | RuleList | `[]Rule` | Rules tried in order, first match wins; `$1`, `$2` insert wildcard captures. | nil | | Rules | `map[string]string` | **Deprecated.** Use `RuleList`. A map has no order, so precedence is decided by a heuristic. | nil | | StatusCode | `int` | HTTP code for redirects. | 302 Temporary Redirect | A `Rule` is a pattern and a target: ```go type Rule struct { From string // the path pattern, where "*" matches a run of any length To string // the target, where "$1", "$2" stand for what the asterisks captured } ``` `From` reaches the compiled pattern as a regular expression, so the other regexp metacharacters keep their meaning: `/a.b` also matches `/aXb`, and `/faq?` also matches `/fa`. Only `*` is a documented placeholder; write a rule out of path text and `*` and it behaves as it reads. A `From` that is not a valid regular expression, such as `/old(`, makes `New` panic. A rule matches the whole path, letter case included: `/old` answers `/old`, but not `/OLD`, `/old/x` or `/very/old`. In `To`, `$10` is the tenth capture in a rule with ten or more captures, which counts every group of the regular expression and not only the asterisks; in a rule with fewer it reads as `$1` followed by `0`. Setting both `RuleList` and `Rules` panics: the two disagree about what decides precedence, so there is no sensible way to honor them together. ### Rule order Rules are tried in the order you write them and the first one whose `From` matches answers, exactly as [routes](../guide/routing.md) are matched. Nothing reorders a `RuleList`. Put the specific rules before the catch-alls: ```go RuleList: []redirect.Rule{ {From: "/api/users", To: "/v2/users"}, // checked first {From: "/api/*", To: "/v2/$1"}, // takes everything else } ``` Written the other way round, `/api/*` answers `/api/users` too and the second rule never fires. For lists of 100 rules or fewer, Fiber logs a warning at startup naming any rule an earlier one shadows outright, so a dead rule tells you rather than going quiet. The deprecated `Rules` map has no order of its own, so Fiber sorts it: most path text pinned before the first `*` or other regular-expression character wins, then most path text overall, then fewest asterisks, then the key itself. That covers rules written with path text and `*`. Rules relying on regular-expression syntax beyond `*` may order differently. Use `RuleList`, where the order is yours. ### How captures are placed The values a rule captures come from the request path, so a `$N` may stand in the path, the query or the fragment of a target, but never in its authority. **A target with no scheme and no authority**, `{From: "/api/*", To: "/$1"}`, always redirects within this application. The composed location is read the way a browser reads it, then held to this origin: a leading run of slashes collapses to one, and a capture that turned the location into an absolute URL is rooted as a path. Without that, `/api//evil.com` would send `Location: //evil.com`, which the browser follows to evil.com. **A target naming its own authority**, `{From: "/ext/*", To: "https://cdn.example.com/$1"}`, redirects wherever it says. A `$N` after the authority may hold anything, including slashes; it can only add to the path. **A target naming a scheme but no authority**, `{From: "/m/*", To: "myapp:$1"}`, also leaves this application, so the rooting above does not apply to it. A capture there may not open an authority of its own: a value that writes the `//` starting one is refused for that request. :::caution **A `$N` inside the authority is refused.** Fiber logs a warning and the rule never fires, so `"https://$1.cdn.example.com/"`, `"https://cdn.example.com:$1"` and `"https://$1"` all do nothing. That covers the host, the port and any userinfo. Whether a value is safe in there depends on percent-decoding, IDNA mapping, numeric labels read as IPv4 addresses, IPv6 brackets and userinfo, and each of those was a way to move the host somewhere the author did not write. Write the authority in full and capture only what follows it. If you need per-tenant destinations, pick the host in a handler and use `c.Redirect()`, where the value is yours to validate. ::: ### What the location carries The request's query string is appended to every `Location`, so `/old?a=1` redirects to `/new?a=1`, and a query the target already carries is merged rather than replaced. A trailing slash on the request path is trimmed before matching, so `/old/` is answered by the rule for `/old`. ### Default Config ```go var ConfigDefault = Config{ StatusCode: fiber.StatusFound, } ``` --- ## RequestID The RequestID middleware generates or propagates a request identifier, adding it to the response headers and request context. ### Signatures ```go func New(config ...Config) fiber.Handler func FromContext(ctx any) string ``` `FromContext` accepts a `fiber.CustomCtx`, `fiber.Ctx`, a `*fasthttp.RequestCtx`, or a `context.Context`. It returns an empty string when no request ID is stored, for example when `Next` skipped the middleware. When the client sent a valid request ID, the string is that header value, which points into the request's buffer unless [`Immutable`](../api/fiber.md#immutable) is set: copy it with `strings.Clone` before a goroutine keeps it. ### Examples Import the middleware package: ```go import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/requestid" ) ``` Once your Fiber app is initialized, add the middleware like this: ```go // Initialize default config app.Use(requestid.New()) // Or extend your config for customization app.Use(requestid.New(requestid.Config{ Header: "X-Custom-Header", Generator: func() string { return "static-id" }, })) ``` If the request already includes the configured header, that value is reused instead of generating a new one. The middleware rejects IDs containing characters outside the visible ASCII range (for example, control characters or obs-text bytes) and will regenerate the value using up to three attempts from the configured generator (or SecureToken when no generator is set). When a custom generator fails to produce a valid ID, the middleware falls back to SecureToken to keep headers RFC-compliant across transports. The middleware also registers the `${requestid}` and `${request-id}` tags, so the [logger](./logger.md) middleware and [`log.WithContext`](../api/log.md#context-tags) can print the ID. Call `requestid.New()` before `logger.New()`: a logger only sees the tags registered when it is built, and one built earlier prints an empty string for `${requestid}`. `log.WithContext` has no such order requirement. Retrieve the request ID ```go func handler(c fiber.Ctx) error { id := requestid.FromContext(c) log.Printf("Request ID: %s", id) return c.SendString("Hello, World!") } ``` ### Config | Property | Type | Description | Default | |:----------|:---------------------|:-----------------------------------------|:---------------| | Next | `func(fiber.Ctx) bool` | Skip when the function returns `true`. | `nil` | | Header | `string` | Header key used to read an incoming request ID and to store the ID on the response. | "X-Request-ID" | | Generator | `func() string` | Function that generates the identifier. | utils.SecureToken | ### Default Config The default config uses a cryptographically secure token generator for better security and privacy. ```go var ConfigDefault = Config{ Next: nil, Header: fiber.HeaderXRequestID, Generator: utils.SecureToken, } ``` --- ## ResponseTime Response time middleware for [Fiber](https://github.com/gofiber/fiber) that measures the time spent handling a request and exposes it via a response header. The value is the elapsed time as a Go duration string, as `time.Duration.String` formats it (for example `1.532ms` or `2.1s`), and `time.ParseDuration` reads it back. The clock starts when this middleware runs and stops when the rest of the chain returns, whether it returned an error or not, so register it before the middleware you want included in the measurement. ### Signatures ```go func New(config ...Config) fiber.Handler ``` ### Examples Import the package: ```go import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/responsetime" ) ``` #### Default config ```go app.Use(responsetime.New()) ``` #### Custom header ```go app.Use(responsetime.New(responsetime.Config{ Header: "X-Elapsed", })) ``` #### Skip logic ```go app.Use(responsetime.New(responsetime.Config{ Next: func(c fiber.Ctx) bool { return c.Path() == "/healthz" }, })) ``` ### Config | Property | Type | Description | Default | | :------- | :--- | :---------- | :------ | | Next | `func(c fiber.Ctx) bool` | Defines a function to skip this middleware when it returns `true`. | `nil` | | Header | `string` | Header key used to store the measured response time. If left empty, the default header is used. | `"X-Response-Time"` | ### Default Config ```go var ConfigDefault = Config{ Next: nil, Header: fiber.HeaderXResponseTime, } ``` --- ## Rewrite The Rewrite middleware remaps the request path using custom rules, helping with backward compatibility and cleaner URLs. Rules are tried in the order you write them and the first match wins. ### Signatures ```go func New(config ...Config) fiber.Handler ``` ### Config | Property | Type | Description | Default | |:---------|:----------------------|:------------------------------------------------------|:-----------| | Next | `func(fiber.Ctx) bool` | Skip when function returns `true`. | `nil` | | RuleList | `[]Rule` | Rules tried in order, first match wins; `$1`, `$2` insert wildcard captures. | nil | | Rules | `map[string]string` | **Deprecated.** Use `RuleList`. A map has no order, so precedence is decided by a heuristic. | nil | Set one of `RuleList` or `Rules`. With neither, the middleware rewrites nothing. A rule's `From` is path text: `*` matches a run of any bytes but a newline, and every other byte stands for itself, so `/preis-1.000-euro` rewrites that path and not `/preis-1X000-euro`. There is no escape for a literal `*`. A rule matches the whole path, trailing slash and letter case included: `/old` rewrites `/old`, but not `/old/`, `/OLD`, `/old/x` or `/very/old`. In `To`, `$1`, `$2` and so on stand for what the asterisks of `From` captured, in order. `$10` is the tenth capture in a rule with ten or more asterisks; in a rule with fewer it reads as `$1` followed by `0`. Setting both `RuleList` and `Rules` panics. Rules match `c.Path()`, the path as the router normalized it, and the rewritten path goes through the same normalization. With `UnescapePath` that path is the decoded one, so a capture is escaped again before it is inserted: the `%2e%2e` that `/files/%252e%252e/secret` decoded to stays that name in `/public/$1` rather than being decoded a second time into `..` and resolving out of `/public/`. ### Rule order Rules are tried in the order you write them and the first one whose `From` matches answers, exactly as [routes](../guide/routing.md) are matched. Nothing reorders a `RuleList`. Put the specific rules before the catch-alls: ```go RuleList: []rewrite.Rule{ {From: "/api/users", To: "/v2/users"}, // checked first {From: "/api/*", To: "/v2/$1"}, // takes everything else } ``` Written the other way round, `/api/*` answers `/api/users` too and the second rule never fires. :::note The deprecated `Rules` map has no order of its own, so Fiber sorts it: most path text before the first `*` wins, then most path text overall, then fewest asterisks, then the key itself. That is a rough reading of "most specific", not an exact one, so a long wildcard rule can still outrank a shorter exact one. `RuleList` is the exact control. ::: ### Examples ```go package main import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/rewrite" ) func main() { app := fiber.New() app.Use(rewrite.New(rewrite.Config{ RuleList: []rewrite.Rule{ {From: "/old", To: "/new"}, {From: "/old/*", To: "/new/$1"}, }, })) app.Get("/new", func(c fiber.Ctx) error { return c.SendString("Hello, World!") }) app.Get("/new/*", func(c fiber.Ctx) error { return c.SendString("Wildcard: " + c.Params("*")) }) app.Listen(":3000") } ``` ### Test ```bash curl http://localhost:3000/old curl http://localhost:3000/old/hello ``` --- ## Session The Session middleware adds session management to Fiber apps through the [Storage](https://github.com/gofiber/storage) package, which offers a unified interface for multiple databases. By default, sessions live in memory, but you can plug in any storage backend. ### Table of Contents - [Quick Start](#quick-start) - [Usage Patterns](#usage-patterns) - [Session Security](#session-security) - [Session ID Extractors](#session-id-extractors) - [Configuration](#configuration) - [Migration Guide](#migration-guide) - [API Reference](#api-reference) - [Examples](#examples) ### Quick Start ```go import ( "fmt" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/session" ) // Basic usage app.Use(session.New()) app.Get("/", func(c fiber.Ctx) error { sess := session.FromContext(c) // Get and update visits count var visits int if v := sess.Get("visits"); v != nil { // Use type assertion with an ok check to prevent a panic if vInt, ok := v.(int); ok { visits = vInt } } visits++ sess.Set("visits", visits) return c.SendString(fmt.Sprintf("Visits: %d", visits)) }) ``` #### Production Configuration ```go import ( "time" "github.com/gofiber/fiber/v3/extractors" "github.com/gofiber/storage/redis/v3" ) storage := redis.New(redis.Config{ Host: "localhost", Port: 6379, }) app.Use(session.New(session.Config{ Storage: storage, CookieSecure: true, // HTTPS only CookieHTTPOnly: true, // Prevent XSS CookieSameSite: "Lax", // CSRF protection IdleTimeout: 30 * time.Minute, // Session timeout AbsoluteTimeout: 24 * time.Hour, // Maximum session life Extractor: extractors.FromCookie("__Host-session_id"), })) ``` Notes: - AbsoluteTimeout must be greater than or equal to IdleTimeout; otherwise, the middleware panics during configuration. - CookieSameSite accepts `"Disabled"`, `"Lax"`, `"Strict"`, or `"None"` case-insensitively; other values panic during configuration. - If CookieSameSite is set to `"Disabled"`, the cookie omits the SameSite attribute and preserves `CookieSecure`. - If CookieSameSite is set to `"None"`, the middleware automatically forces `CookieSecure=true` when setting the cookie. ### Usage Patterns #### Middleware Pattern (Recommended) This pattern automatically manages the session lifecycle and is recommended for most applications. ```go // Setup middleware app.Use(session.New()) // Use in handlers app.Post("/login", func(c fiber.Ctx) error { sess := session.FromContext(c) // Session is automatically saved when handler returns sess.Set("user_id", 123) sess.Set("authenticated", true) return c.Redirect().To("/dashboard") }) ``` **Benefits:** - Automatic session saving - Automatic resource cleanup - No manual lifecycle management - Thread-safe operations #### Store Pattern (Advanced) Use the store pattern for background tasks or when you need direct access to sessions. ```go import ( "context" "log" "time" ) store := session.NewStore() // In background tasks func backgroundTask(sessionID string) { sess, err := store.GetByID(context.Background(), sessionID) if err != nil { return } defer sess.Release() // Important: Manual cleanup required // Modify session sess.Set("last_task", time.Now().Unix()) // Manual save required if err := sess.Save(); err != nil { log.Printf("Failed to save session: %v", err) } } ``` **Requirements:** - Must call `sess.Release()` when done - Must call `sess.Save()` to persist changes - Handle errors manually ### Session Security #### Authentication Flow Understanding session lifecycle during authentication is crucial for security. ##### Basic Login/Logout ```go app.Post("/login", func(c fiber.Ctx) error { sess := session.FromContext(c) email := c.FormValue("email") password := c.FormValue("password") // Simple credential validation (use proper authentication in production) if email == "admin@example.com" && password == "secret" { // Important: Regenerate the session ID to prevent fixation // This changes the session ID while preserving existing data if err := sess.Regenerate(); err != nil { return c.Status(500).SendString("Session error") } // Add authentication data to existing session sess.Set("user_id", 1) sess.Set("authenticated", true) return c.Redirect().To("/dashboard") } return c.Status(401).SendString("Invalid credentials") }) app.Post("/logout", func(c fiber.Ctx) error { sess := session.FromContext(c) // Complete session reset (clears all data + new session ID) if err := sess.Reset(); err != nil { return c.Status(500).SendString("Session error") } return c.Redirect().To("/") }) ``` ##### Cart Preservation During Login ```go app.Post("/login", func(c fiber.Ctx) error { sess := session.FromContext(c) // Validate credentials (implement your own validation) email := c.FormValue("email") password := c.FormValue("password") if !isValidUser(email, password) { return c.Status(401).JSON(fiber.Map{"error": "Invalid credentials"}) } // Important: Regenerate the session ID to prevent fixation // This changes the session ID while preserving existing data if err := sess.Regenerate(); err != nil { return c.Status(500).JSON(fiber.Map{"error": "Session error"}) } // Add authentication data to existing session sess.Set("user_id", getUserID(email)) sess.Set("authenticated", true) sess.Set("login_time", time.Now().Unix()) return c.JSON(fiber.Map{"status": "logged in"}) }) ``` #### Security Methods Comparison | Method | Session ID | Session Data | Use Case | |--------|------------|--------------|----------| | `Regenerate()` | ✅ Changes | ✅ Preserved | Login, privilege escalation | | `Reset()` | ✅ Changes | ❌ Cleared | Logout, security breach | | `Destroy()` | ⚪ Unchanged, cookie expired | ❌ Cleared | End the session without issuing a new ID | #### Common Security Mistakes ❌ **Session Fixation Vulnerability:** ```go // DANGEROUS: Keeping same session ID after login app.Post("/login", func(c fiber.Ctx) error { sess := session.FromContext(c) // Validate user... sess.Set("user_id", userID) // Attacker can hijack this session! return c.Redirect().To("/dashboard") }) ``` ✅ **Secure Implementation:** ```go // SECURE: Always regenerate session ID after authentication app.Post("/login", func(c fiber.Ctx) error { sess := session.FromContext(c) // Validate user... if err := sess.Regenerate(); err != nil { // Prevents session fixation return err } sess.Set("user_id", userID) return c.Redirect().To("/dashboard") }) ``` #### Authentication Middleware This is a basic example of an authentication middleware that checks if a user is logged in before accessing protected routes. ```go // Authentication check middleware func RequireAuth(c fiber.Ctx) error { sess := session.FromContext(c) if sess == nil { return c.Redirect().To("/login") } // Check if user is authenticated if sess.Get("authenticated") != true { return c.Redirect().To("/login") } return c.Next() } // Usage app.Use("/dashboard", RequireAuth) app.Use("/admin", RequireAuth) ``` #### Automatic Session Expiration Sessions automatically expire based on your configuration: ```go app.Use(session.New(session.Config{ IdleTimeout: 30 * time.Minute, // Auto-expire after 30 min of inactivity AbsoluteTimeout: 24 * time.Hour, // Force expire after 24 hours regardless of activity })) ``` **How it works:** - `IdleTimeout`: Storage automatically removes sessions after inactivity period - Any route that uses the middleware will reset the idle timer - Calling `sess.Save()` will also reset the idle timer - `AbsoluteTimeout`: Sessions are forcibly expired after maximum duration - No manual cleanup required - the storage layer handles this ### Session ID Extractors This middleware uses the shared extractors module for session ID extraction. See the [Extractors Guide](../guide/extractors) for more details. #### Built-in Extractors ```go // Cookie-based (recommended for web apps) extractors.FromCookie("session_id") // Header-based (recommended for APIs) extractors.FromHeader("X-Session-ID") // Authorization header (read-only) extractors.FromAuthHeader("Bearer") // Form data extractors.FromForm("session_id") // URL query parameter extractors.FromQuery("session_id") // URL path parameter extractors.FromParam("id") ``` **Session Response Behavior:** - Cookie extractors: set cookie in the response - Header extractors (non-Authorization): set header in the response - Authorization header, Query, Form, Param, Custom: read-only (no response values are set) #### Multiple Sources with Fallback ```go app.Use(session.New(session.Config{ Extractor: extractors.Chain( extractors.FromCookie("session_id"), // Try cookie first extractors.FromHeader("X-Session-ID"), // Then header extractors.FromQuery("session_id"), // Finally query ), })) ``` **Response Behavior with Chained Extractors:** Only cookie and non-Authorization header extractors contribute to response setting. Others are read-only. - Cookie + Header (non-Auth) extractors: both cookie and header are set - Only Cookie extractors: only cookie is set - Only Header (non-Auth) extractors: only header is set - Any mix that includes Authorization/Query/Form/Param/Custom: those sources are read-only These rules describe a request that carries no session ID, for example a first visit. For an existing session, the ID is written back only through the extractor that supplied it, and only when that extractor is a cookie or a non-Authorization header. An ID that arrived through a read-only source (Authorization header, Query, Form, Param, or Custom) is never copied into a cookie or header, which prevents session fixation. When the middleware issues a new ID for a request that carried one, because `Regenerate()` or `Reset()` was called or because the supplied ID is not in the storage, the new ID goes back through the same cookie or header, or through every cookie and header extractor of the chain when the old ID came from a read-only source. ```go // This will set both cookie and header in response extractors.Chain( extractors.FromCookie("session_id"), extractors.FromHeader("X-Session-ID"), ) // This will set only cookie in response extractors.Chain( extractors.FromCookie("session_id"), extractors.FromQuery("session_id"), // Ignored for response ) // This will set nothing in response (read-only mode) extractors.Chain( extractors.FromQuery("session_id"), extractors.FromForm("session_id"), ) ``` #### Custom Extractors (Session-specific) Prefer the helper constructors from the extractors module. See the Extractors Guide for the full API; below are session-specific examples and notes. ```go // Authorization Bearer tokens (read-only for sessions) // The session middleware will NOT set Authorization back in the response. app.Use(session.New(session.Config{ Extractor: extractors.FromAuthHeader("Bearer"), })) ``` ```go // Custom read-only header via FromCustom (read-only for sessions) app.Use(session.New(session.Config{ Extractor: extractors.FromCustom("X-Custom-Session", func(c fiber.Ctx) (string, error) { v := c.Get("X-Custom-Session") if v == "" { return "", extractors.ErrNotFound } return v, nil }), })) ``` Return `extractors.ErrNotFound` when the request carries no session ID, so the visitor gets a new session. Any other error from an extractor is not treated as a new visitor: it goes to `ErrorHandler`, which answers `500` by default, or is returned by `store.Get`. This lets a custom or chain-level `Extract` that validates the ID refuse a forged one. A validating child inside a built-in `extractors.Chain` cannot: the chain keeps walking after an error, and a later child's value, or its `ErrNotFound`, replaces the refusal, so put the validation in the chain-level `Extract` or use the validating extractor alone. Such an `Extract` must return the ID unchanged, because the same value is written back and looked up in the storage. ### Configuration #### Storage Options ```go import ( "github.com/gofiber/storage/redis/v3" "github.com/gofiber/storage/postgres/v3" ) // Redis (recommended for production) redisStorage := redis.New(redis.Config{ Host: "localhost", Port: 6379, Password: "", Database: 0, }) // PostgreSQL pgStorage := postgres.New(postgres.Config{ Host: "localhost", Port: 5432, Database: "sessions", Username: "user", Password: "pass", }) app.Use(session.New(session.Config{ Storage: redisStorage, })) ``` #### Production Security Settings ```go import ( "log" "time" "github.com/gofiber/fiber/v3" "github.com/gofiber/utils/v2" "github.com/gofiber/fiber/v3/extractors" ) app.Use(session.New(session.Config{ // Storage Storage: redisStorage, // Security CookieSecure: true, // HTTPS only (required in production) CookieHTTPOnly: true, // No JavaScript access (prevents XSS) CookieSameSite: "Lax", // CSRF protection // Session Management IdleTimeout: 30 * time.Minute, // Inactivity timeout AbsoluteTimeout: 24 * time.Hour, // Maximum session duration // Cookie Settings CookiePath: "/", // CookieDomain stays empty: browsers reject a __Host- cookie that sets a Domain CookieSessionOnly: false, // Persist across browser restarts // Session ID Extractor: extractors.FromCookie("__Host-session_id"), KeyGenerator: utils.SecureToken, // Error Handling ErrorHandler: func(c fiber.Ctx, err error) { log.Printf("Session error: %v", err) // Write the response: when the session cannot be loaded, the request is // not passed on, so nothing else answers it. if sendErr := c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Session error"}); sendErr != nil { log.Printf("Failed to send error response: %v", sendErr) } }, })) ``` A custom `ErrorHandler` has to write the response itself. When the session cannot be loaded from the storage, the request is not passed on, so a handler that only logs answers with an empty `200`. When the session cannot be saved, the route handler has already run: the status and body that `ErrorHandler` writes replace its own, while its headers stay, and a handler that only logs leaves that response, usually a success, for a session the storage did not keep. #### Custom Types Session data supports basic Go types by default: - `string`, `int`, `int8`, `int16`, `int32`, `int64` - `uint`, `uint8`, `uint16`, `uint32`, `uint64` - `bool`, `float32`, `float64` - `[]byte`, `complex64`, `complex128` - `any` For custom types (structs, maps, slices), you must register them for encoding/decoding: ```go import "fmt" type User struct { ID int `json:"id"` Name string `json:"name"` Role string `json:"role"` } // Method 1: Using NewWithStore func main() { app := fiber.New() sessionMiddleware, store := session.NewWithStore() store.RegisterType(User{}) // Register custom type app.Use(sessionMiddleware) app.Get("/", func(c fiber.Ctx) error { sess := session.FromContext(c) // Use custom type sess.Set("user", User{ID: 123, Name: "John", Role: "admin"}) user, ok := sess.Get("user").(User) if ok { return c.JSON(fiber.Map{"user": user.Name, "role": user.Role}) } return c.SendString("No user found") }) app.Listen(":3000") } ``` ```go // Method 2: Using separate store store := session.NewStore() store.RegisterType(User{}) app.Use(session.New(session.Config{ Store: store, })) // Usage in handlers sess.Set("user", User{ID: 123, Name: "John", Role: "admin"}) user, ok := sess.Get("user").(User) if ok { fmt.Printf("User: %s (Role: %s)", user.Name, user.Role) } ``` **Important Notes:** - Custom types must be registered before using them in sessions - Registration must happen during application startup - All instances of the application must register the same types - Types are encoded using Go's `gob` package - `time.Time` is a struct, so it needs `store.RegisterType(time.Time{})` as well. The store registers it by itself only when `AbsoluteTimeout` is greater than zero. Saving a session that holds an unregistered `time.Time` fails, and the middleware answers with its `ErrorHandler` (`500` by default). Store `time.Now().Unix()` when a plain timestamp is enough - Reading a struct back out with `Get` makes the save re-encode, even for an all-scalar struct like `User`, because every struct is treated as possibly sharing memory with the session. A request that only reads scalars or `time.Time` values writes the stored bytes back unchanged ### Migration Guide #### v2 to v3 Breaking Changes 1. **Function Signature**: `session.New()` now returns middleware handler, not store 2. **Session ID Extraction**: `KeyLookup` replaced with `Extractor` functions 3. **Lifecycle Management**: Manual `Release()` required for store pattern 4. **Timeout Handling**: `Expiration` split into `IdleTimeout` and `AbsoluteTimeout` #### Migration Examples **v2 Code:** ```go store := session.New(session.Config{ KeyLookup: "cookie:session_id", }) app.Get("/", func(c fiber.Ctx) error { sess, err := store.Get(c) if err != nil { return err } // Session automatically saved and released sess.Set("key", "value") return nil }) ``` **v3 Middleware Pattern (Recommended):** ```go app.Use(session.New(session.Config{ Extractor: extractors.FromCookie("session_id"), })) app.Get("/", func(c fiber.Ctx) error { sess := session.FromContext(c) // Session automatically saved and released sess.Set("key", "value") return nil }) ``` **v3 Store Pattern (Advanced):** ```go store := session.NewStore(session.Config{ Extractor: extractors.FromCookie("session_id"), }) app.Get("/", func(c fiber.Ctx) error { sess, err := store.Get(c) if err != nil { return err } defer sess.Release() // Manual cleanup required sess.Set("key", "value") return sess.Save() // Manual save required }) ``` #### KeyLookup to Extractor Migration | v2 KeyLookup | v3 Extractor | |---------------------------------|------------------------------------------------------------------------------------| | `"cookie:session_id"` | `extractors.FromCookie("session_id")` | | `"header:X-Session-ID"` | `extractors.FromHeader("X-Session-ID")` | | `"query:session_id"` | `extractors.FromQuery("session_id")` | | `"form:session_id"` | `extractors.FromForm("session_id")` | | `"cookie:sid,header:X-Sid"` | `extractors.Chain(extractors.FromCookie("sid"), extractors.FromHeader("X-Sid"))` | ### API Reference #### Constructors and Helpers ```go func New(config ...Config) fiber.Handler func NewWithStore(config ...Config) (fiber.Handler, *Store) func NewStore(config ...Config) *Store func FromContext(ctx any) *Middleware func DefaultErrorHandler(c fiber.Ctx, err error) ``` `NewWithStore` returns the middleware together with the `Store` it uses, for example to call `RegisterType`. `FromContext` returns `nil` when the request has no session middleware. #### Middleware Methods (Recommended) ```go sess := session.FromContext(c) // Data operations sess.Get(key any) any sess.Set(key, value any) sess.Delete(key any) sess.Keys() []any // Session management sess.ID() string sess.Fresh() bool sess.Regenerate() error // Change ID, keep data sess.Reset() error // Change ID, clear data sess.Destroy() error // Delete from storage, expire the cookie // Context-aware variants propagate cancellation/deadlines to storage I/O. // Pass a context.Context to control timeouts; a nil context is treated as // context.Background(). sess.RegenerateWithContext(ctx context.Context) error sess.ResetWithContext(ctx context.Context) error sess.DestroyWithContext(ctx context.Context) error // Store access sess.Store() *session.Store ``` `FromContext` accepts a `fiber.CustomCtx`, `fiber.Ctx`, a `*fasthttp.RequestCtx`, or a `context.Context`. The returned `*session.Middleware` is only valid while the request is being handled. A context you kept from `c.Context()` (with `PassLocalsToContext` enabled) stops resolving it when the request ends: `FromContext` returns `nil` for that context from then on, so a goroutine that outlives the handler cannot reach the session, and never another request's. Do not hand the pointer itself to such a goroutine. Pass it the session ID and use the [Store Pattern](#store-pattern-advanced) instead. A session that was only read is written back to storage without being re-encoded. `Get` and `Keys` give that up when the value or key they return could share memory with the session (a map, slice, array, pointer or struct; scalars and `time.Time` are safe), because the caller could then change it in place without calling `Set`. #### Store Methods ```go store := session.NewStore() // Store operations store.Get(c fiber.Ctx) (*session.Session, error) store.GetByID(ctx context.Context, sessionID string) (*session.Session, error) store.Reset(ctx context.Context) error store.Delete(ctx context.Context, sessionID string) error // Type registration store.RegisterType(User{}) ``` #### Errors ```go var ( ErrEmptySessionID = errors.New("session ID cannot be empty") ErrSessionAlreadyLoadedByMiddleware = errors.New("session already loaded by middleware") ErrSessionIDNotFoundInStore = errors.New("session ID not found in session store") ) ``` - `store.Get(c)` returns `ErrSessionAlreadyLoadedByMiddleware` when the session middleware already loaded the session for the request. Use `session.FromContext(c)` there. - `store.GetByID` and `store.Delete` return `ErrEmptySessionID` for an empty session ID. - `store.GetByID` returns `ErrSessionIDNotFoundInStore` when the storage has no such session, or when the session has outlived its `AbsoluteTimeout` (it is deleted). #### Session Methods (Store Pattern) ```go sess, err := store.Get(c) defer sess.Release() // Required! // Same methods as middleware (except Store()), plus: sess.Save() error // Manual save required sess.SetIdleTimeout(duration) // Per-session timeout sess.Release() // Manual cleanup required // Context-aware variants propagate cancellation/deadlines to storage I/O. // A nil context is treated as context.Background(). sess.DestroyWithContext(ctx context.Context) error sess.RegenerateWithContext(ctx context.Context) error sess.ResetWithContext(ctx context.Context) error sess.SaveWithContext(ctx context.Context) error ``` #### Session with Context (timeouts/cancellation) The `*WithContext` variants let you propagate a `context.Context` to the underlying storage call so that slow or unresponsive backends can be bounded by a deadline or canceled. This mirrors the `Storage` and `SharedState` `WithContext` convention. ```go ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) defer cancel() // Persist the session, bounding the storage write by the deadline. if err := sess.SaveWithContext(ctx); err != nil { // deadline exceeded, cancellation, or storage error } // Destroy is also covered: // if err := sess.DestroyWithContext(ctx); err != nil { ... } ``` A `nil` context is treated as `context.Background()`, so `sess.SaveWithContext(nil)` is equivalent to `sess.Save()` when no request context is available. #### Extractor Functions ```go // Built-in extractors (import "github.com/gofiber/fiber/v3/extractors") extractors.FromCookie(key string) extractors.Extractor extractors.FromHeader(key string) extractors.Extractor extractors.FromAuthHeader(authScheme string) extractors.Extractor extractors.FromQuery(key string) extractors.Extractor extractors.FromForm(key string) extractors.Extractor extractors.FromParam(key string) extractors.Extractor extractors.FromCustom(key string, fn func(fiber.Ctx) (string, error)) extractors.Extractor // Chaining extractors.Chain(extractors ...extractors.Extractor) extractors.Extractor ``` #### Config Properties | Property | Type | Description | Default | |---------------------|-----------------------------|-----------------------------|--------------------------------------------| | `Store` | `*session.Store` | Pre-built session store (use when you need to share/register types) | `nil` (auto-created) | | `Storage` | `fiber.Storage` | Session storage backend (used when creating a store if `Store` is nil) | `memory.New()` | | `Extractor` | `extractors.Extractor` | Session ID extraction | `extractors.FromCookie("session_id")` | | `KeyGenerator` | `func() string` | Session ID generator | `utils.SecureToken` | | `IdleTimeout` | `time.Duration` | Inactivity timeout | `30 * time.Minute` | | `AbsoluteTimeout` | `time.Duration` | Maximum session duration | `0` (unlimited) | | `CookieSecure` | `bool` | HTTPS only | `false` | | `CookieHTTPOnly` | `bool` | No JavaScript access | `false` | | `CookieSameSite` | `string` | SameSite mode: `Disabled`, `Lax`, `Strict`, or `None` | `"Lax"` | | `CookiePath` | `string` | Cookie path | `""` | | `CookieDomain` | `string` | Cookie domain | `""` | | `CookieSessionOnly` | `bool` | Session cookie | `false` | | `Next` | `func(fiber.Ctx) bool` | Skip middleware when returns true | `nil` | | `ErrorHandler` | `func(fiber.Ctx, error)` | Called when the session cannot be loaded from the storage, in which case the request is not passed on, or cannot be saved after the handler returns. A custom handler has to write the response: one that only logs answers an empty `200` after a load failure and leaves the route handler's response in place after a save failure. The default logs the error and responds with `500 Internal Server Error` | `DefaultErrorHandler` | #### Default Config ```go var ConfigDefault = Config{ IdleTimeout: 30 * time.Minute, KeyGenerator: utils.SecureToken, Extractor: extractors.FromCookie("session_id"), CookieSameSite: fiber.CookieSameSiteLaxMode, } ``` An unset `IdleTimeout`, `Extractor`, `KeyGenerator`, or `CookieSameSite` is filled from `ConfigDefault`. A `Storage` that is `nil` becomes an in-memory store when the store is created. ### Examples #### E-commerce with Cart Persistence ```go import ( "time" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/session" "github.com/gofiber/fiber/v3/extractors" "github.com/gofiber/storage/redis/v3" ) func main() { app := fiber.New() // Session middleware app.Use(session.New(session.Config{ Storage: redis.New(), CookieSecure: true, CookieHTTPOnly: true, CookieSameSite: "Lax", IdleTimeout: 30 * time.Minute, AbsoluteTimeout: 24 * time.Hour, Extractor: extractors.FromCookie("__Host-cart_session"), })) // Add to cart (anonymous user) app.Post("/cart/add", func(c fiber.Ctx) error { sess := session.FromContext(c) cart, _ := sess.Get("cart").([]string) cart = append(cart, c.FormValue("item_id")) sess.Set("cart", cart) return c.JSON(fiber.Map{"items": len(cart)}) }) // Login (preserve session data) app.Post("/login", func(c fiber.Ctx) error { sess := session.FromContext(c) // Simple validation (implement proper authentication) email := c.FormValue("email") password := c.FormValue("password") if email != "user@example.com" || password != "password" { return c.Status(401).JSON(fiber.Map{"error": "Invalid credentials"}) } // Regenerate session ID for security // This changes the session ID while preserving existing data if err := sess.Regenerate(); err != nil { return c.Status(500).JSON(fiber.Map{"error": "Session error"}) } sess.Set("user_id", 1) sess.Set("authenticated", true) return c.JSON(fiber.Map{"status": "logged in"}) }) // Logout (clear everything) app.Post("/logout", func(c fiber.Ctx) error { sess := session.FromContext(c) // Reset clears all data and generates new session ID if err := sess.Reset(); err != nil { return c.Status(500).JSON(fiber.Map{"error": "Session error"}) } return c.JSON(fiber.Map{"status": "logged out"}) }) app.Listen(":3000") } // Helper functions (implement these properly in production) func isValidUser(email, password string) bool { return email == "user@example.com" && password == "password" } func getUserID(email string) int { return 1 // Return actual user ID from database } ``` #### API with Header-based Sessions ```go import ( "time" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/session" "github.com/gofiber/fiber/v3/extractors" "github.com/gofiber/storage/redis/v3" ) func main() { app := fiber.New() // API session middleware with header extraction app.Use(session.New(session.Config{ Storage: redis.New(), Extractor: extractors.FromHeader("X-Session-Token"), IdleTimeout: time.Hour, })) // API endpoint app.Post("/api/data", func(c fiber.Ctx) error { sess := session.FromContext(c) // Track API usage count, _ := sess.Get("api_calls").(int) count++ sess.Set("api_calls", count) sess.Set("last_call", time.Now().Unix()) return c.JSON(fiber.Map{ "data": "some data", "calls": count, }) }) app.Listen(":3000") } ``` #### Multi-source Session ID Support ```go import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/session" "github.com/gofiber/fiber/v3/extractors" ) func main() { app := fiber.New() // Support multiple sources with priority app.Use(session.New(session.Config{ Extractor: extractors.Chain( extractors.FromCookie("session_id"), // 1st: Cookie (web) extractors.FromHeader("X-Session-ID"), // 2nd: Header (API) extractors.FromQuery("session_id"), // 3rd: Query (fallback) ), })) app.Get("/", func(c fiber.Ctx) error { sess := session.FromContext(c) // Works with any of the above methods return c.JSON(fiber.Map{ "session_id": sess.ID(), "source": "multi-source", }) }) app.Listen(":3000") } ``` --- ## Skip The Skip middleware wraps a handler and bypasses it when the predicate returns `true` for the current request. ### Signatures ```go func New(handler fiber.Handler, exclude func(c fiber.Ctx) bool) fiber.Handler ``` ### Examples Import the package: ```go import ( "log" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/skip" ) ``` `skip.New` accepts the handler to wrap and a predicate function. The predicate runs for every request, and returning `true` skips the wrapped handler and executes the next middleware in the chain. If the predicate is `nil`, `skip.New` returns the handler unchanged, so it runs for every request. After you initialize your Fiber app, use `skip.New` like this: ```go func main() { app := fiber.New() app.Use(skip.New(BasicHandler, func(ctx fiber.Ctx) bool { return ctx.Method() == fiber.MethodGet })) app.Get("/", func(ctx fiber.Ctx) error { return ctx.SendString("It was a GET request!") }) log.Fatal(app.Listen(":3000")) } func BasicHandler(ctx fiber.Ctx) error { return ctx.SendString("It was not a GET request!") } ``` :::tip `app.Use` processes requests on any route and method. In the example above, the handler is skipped only for `GET`. ::: --- ## SSE The SSE handler provides the transport pieces for Server-Sent Events: response headers, event formatting, flushing, heartbeat comments, and disconnect detection through `Flush` errors. It intentionally does not include a hub, topics, authentication, replay storage, metrics, or external pub/sub bridges. Those are application concerns that can be composed around the stream handler. ### Signatures ```go func New(config ...Config) fiber.Handler type Handler func(c fiber.Ctx, stream *Stream) error ``` ### Examples Import the SSE package: ```go import ( "context" "time" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/sse" ) ``` Once your Fiber app is initialized, mount an SSE endpoint like this: ```go app.Get("/events", sse.New(sse.Config{ Retry: 5 * time.Second, Handler: func(c fiber.Ctx, stream *sse.Stream) error { return stream.Event(sse.Event{ Name: "message", Data: fiber.Map{"message": "hello"}, }) }, })) ``` Before `Handler` runs, the middleware sets `Content-Type: text/event-stream`, `Cache-Control: no-cache`, `Connection: keep-alive`, and `X-Accel-Buffering: no`, which stops nginx from buffering the stream. For long-running streams, subscribe each client to its own event channel and stop when the client disconnects. A single shared channel load-balances messages across clients; use a fan-out source when every client must receive every event: ```go type Broker interface { Subscribe(ctx context.Context) (<-chan string, error) } app.Get("/events", sse.New(sse.Config{ Handler: func(c fiber.Ctx, stream *sse.Stream) error { events, err := broker.Subscribe(stream.Context()) if err != nil { return err } for { select { case msg, ok := <-events: if !ok { return nil } if err := stream.Event(sse.Event{Name: "message", Data: msg}); err != nil { return err } case <-stream.Done(): return stream.Err() } } }, })) ``` `stream.Context()` is canceled when the stream ends or a write fails, which makes it convenient to pass into database, broker, or gRPC calls: ```go app.Get("/events", sse.New(sse.Config{ Handler: func(c fiber.Ctx, stream *sse.Stream) error { rows, err := db.QueryContext(stream.Context(), "SELECT id FROM jobs") if err != nil { return err } defer rows.Close() return stream.Comment("connected") }, })) ``` ### Config | Property | Type | Description | Default | |:------------------|:-------------------------|:-----------------------------------------------------------------------------------------------------|:----------------------| | Handler | `sse.Handler` | Required. Writes events to the stream. `New` panics if this field is omitted or `nil`. | required (`nil` panics) | | OnClose | `func(fiber.Ctx, error)` | Called when the stream ends, with `nil` when the handler returned successfully and no stream write failed. A panic in `Handler` is recovered and passed here as an error (`sse: handler panic: ...`). | `nil` | | Retry | `time.Duration` | Initial EventSource reconnect delay. | `0` | | HeartbeatInterval | `time.Duration` | Interval for SSE comment heartbeats. A value less than or equal to zero uses the default. | `15 * time.Second` | | DisableHeartbeat | `bool` | Disable automatic heartbeat comments. When disabled, disconnected clients may not be detected until the next write. | `false` | ### Default Config ```go var ConfigDefault = Config{ Handler: nil, OnClose: nil, Retry: 0, HeartbeatInterval: 15 * time.Second, DisableHeartbeat: false, } ``` `ConfigDefault.Handler` is `nil`, but `Handler` is still required. Set it before calling `New`, or `New` will panic. ### Event ```go type Event struct { Data any ID string Name string Retry time.Duration } ``` | Field | Type | Description | |:------|:----------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Data | `any` | Written as `data` fields. Strings, `[]byte`, and `json.RawMessage` are written as-is, `nil` writes no `data` field, and any other value is JSON encoded with the app's `JSONEncoder`. A value with line breaks becomes one `data` field per line. | | ID | `string` | Sets the `id` field. Surrounding spaces are trimmed and a blank value is omitted. | | Name | `string` | Sets the `event` field, trimmed and omitted like `ID`. | | Retry | `time.Duration` | Sets the `retry` field for this event. Values less than or equal to zero are omitted. | `ID` and `Name` must not contain a carriage return or line feed. `Stream.Event` returns an error instead of writing a value that could start a new field. ### Stream ```go func (s *Stream) Event(event Event) error func (s *Stream) Comment(comment string) error func (s *Stream) Retry(retry time.Duration) error func (s *Stream) Context() context.Context func (s *Stream) Done() <-chan struct{} func (s *Stream) Err() error func (s *Stream) LastEventID() string ``` Every write is flushed. A failed write closes `Done`, stores the error returned by `Err`, and lets the handler stop without relying on `fasthttp.RequestCtx.Done`, which is not a per-client disconnect signal. A write fails when the flush fails, and also when `Event` rejects an `ID` or `Name` with a line break or cannot encode `Data`. In every case the stream ends and later writes return the same error. After a normal handler return, `Done` is closed and `Context()` is canceled while `Err()` remains `nil`; writes after that return `sse: stream closed`. Automatic heartbeat comments keep idle streams active and make silent client disconnects observable through the next flush error. If heartbeats are disabled, a handler waiting on an external source might not notice a disconnected client until it writes again. Stopping a stream waits for an in-flight heartbeat write to finish, so a very slow client can delay shutdown until the underlying write unblocks. `LastEventID` returns the `Last-Event-ID` header sent by a reconnecting client, or an empty string. `Config.Retry` sends the initial reconnect delay when the stream opens. `Event.Retry` changes the reconnect delay for a specific event, following the SSE wire format. The delay is sent in whole milliseconds, rounded up, so a value below one millisecond becomes `retry: 1` rather than `retry: 0`. --- ## Static The Static middleware serves assets such as **images**, **CSS**, and **JavaScript**. It answers `GET` and `HEAD` requests and passes every other method to the next handler. :::info By default, it serves `index.html` when a directory is requested. Customize this behavior in the [Config](#config) options. ::: ### Signatures ```go func New(root string, cfg ...Config) fiber.Handler ``` ### Examples Import the package: ```go import( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/static" ) ``` #### Serving files from a directory ```go app.Get("/*", static.New("./public")) ```
Test ```sh curl http://localhost:3000/hello.html curl http://localhost:3000/css/style.css ```
#### Serving files from a directory with `Use` ```go app.Use("/", static.New("./public")) ```
Test ```sh curl http://localhost:3000/hello.html curl http://localhost:3000/css/style.css ```
#### Serving a single file ```go app.Use("/static", static.New("./public/hello.html")) ```
Test ```sh curl http://localhost:3000/static # will show hello.html curl http://localhost:3000/static/john/doe # will show hello.html ```
#### Serving files using os.DirFS ```go app.Get("/files*", static.New("", static.Config{ FS: os.DirFS("files"), Browse: true, })) ```
Test ```sh curl http://localhost:3000/files/css/style.css curl http://localhost:3000/files/index.html ```
#### Serving files using embed.FS ```go //go:embed path/to/files var myfiles embed.FS app.Get("/files*", static.New("path/to/files", static.Config{ FS: myfiles, Browse: true, })) ``` With `FS` set, the first argument names the directory inside the file system to serve, and `""` serves its root. An `embed.FS` keeps the path it embedded, so pass that path.
Test ```sh curl http://localhost:3000/files/css/style.css curl http://localhost:3000/files/index.html ```
#### SPA (Single Page Application) ```go app.Use("/web", static.New("", static.Config{ FS: os.DirFS("dist"), })) app.Get("/web*", func(c fiber.Ctx) error { return c.SendFile("dist/index.html") }) ```
Test ```sh curl http://localhost:3000/web/css/style.css curl http://localhost:3000/web/index.html curl http://localhost:3000/web ```
:::caution To define static routes using `Get`, append the wildcard (`*`) operator at the end of the route. Under a `/static*` route the rest of the path must start at a segment boundary, so `/static-v2/app.js` or `/staticapp.js` are no longer served; only `/static` and paths below `/static/` are. ::: :::info The file server resolves the path the router matched. Fiber normalizes request paths before routing (see [Path normalization](../guide/routing.md#path-normalization)), so middleware mounted on `/static/private` also guards `/static/%70rivate/secret.txt` and `/static/x/../private/secret.txt`. Escapes the router leaves encoded, such as `%20`, `%40` or `%25`, also remain encoded for file lookup. This keeps routing, including path-scoped authorization middleware, and static-file resolution on the same resource name. Consequently, `/static/hello%20world.txt` does not serve `hello world.txt`; enable [`UnescapePath`](../api/fiber.md#config) when encoded spellings should resolve to decoded file names. A path that cannot name a file inside the root is answered with `404 Not Found`: - an escape that represents a slash, a backslash or a control character, so `/static/private%2Fsecret.txt` never reaches `private/secret.txt` unless `UnescapePath` decodes it for the router as well; - a malformed escape such as `%zz`; - an escape of an unreserved character. The router decodes every one of those when it normalizes a path, so one still in the routed path can only have been forged by a stray `%`, and it is rejected rather than decoded a second time: `/static/%%370rivate/secret.txt` never opens `private/secret.txt`; - an empty, `.` or `..` segment, so `/static//private/secret.txt` is not found; - for a root on the operating system's file system, a first segment that starts with a drive letter such as `C:`. With `UnescapePath` enabled the router has already decoded every escape, so a malformed escape is an ordinary part of the name: `/static/100%zz.txt` names `100%zz.txt`. A root mount, such as `app.Use(static.New(dir))` or `app.Get("/*", static.New(dir))`, refuses a path whose first segment is empty too, whether it serves a directory or a single file. The router keeps the empty first segment of `//private/secret.txt` apart from `/private/secret.txt`, so a root mount answers it with `404 Not Found` instead of serving anything for a path that middleware mounted on `/private` never saw. That includes any spelling the router turns into such a path, such as `/.//private/secret.txt` or, with `UnescapePath` enabled, `/%2Fprivate/secret.txt`. A root that is a single file looks nothing up by the path, so it serves its file for every other path the route matches, whatever its spelling, and no spelling of a path can select another file. An empty segment further in is an ordinary part of a name there, as in `/web/https://example.com`. ::: :::tip A client that puts a doubled slash in front of a path, such as a base URL that ends in `/` joined to a path that starts with `/`, gets `404 Not Found` from a root mount. To keep serving it, collapse the slash with the [Rewrite](./rewrite.md) middleware before any middleware that matches a path prefix runs. The middleware registered after it, one guarding `/private` included, then sees `/private/secret.txt`: ```go app.Use(rewrite.New(rewrite.Config{ RuleList: []rewrite.Rule{{From: "//*", To: "/$1"}}, })) ``` ::: :::caution The file server opens the name the router matched, so it is only as strict as the filesystem underneath. On Windows a path component loses its trailing dots and spaces when it is opened, so `/static/private./secret.txt` would reach `private/secret.txt` without passing middleware mounted on `/static/private`; the file server answers such a segment with `404 Not Found` on Windows, whatever the root: an `fs.FS` may be OS-backed (`os.DirFS`) and cannot be told apart from a virtual one such as `embed.FS`, so an embedded name ending in a dot or a space is not served on Windows either. Case is a different matter: on a case-insensitive filesystem (Windows, and macOS by default) `/static/PRIVATE/secret.txt` opens `private/secret.txt`, and with [`CaseSensitive`](../api/fiber.md#config) enabled the router keeps the two apart, so middleware mounted on `/static/private` does not run. Leave `CaseSensitive` off (the default) for a static root with guarded directories, or serve it from a case-sensitive filesystem. ::: ### Config | Property | Type | Description | Default | |:-----------|:------------------------|:---------------------------------------------------------------------------------------------------------------------------|:-----------------------| | Next | `func(fiber.Ctx) bool` | Next defines a function to skip this middleware when it returns true. | `nil` | | FS | `fs.FS` | FS is the file system to serve the static files from. You can use interfaces compatible with fs.FS like embed.FS, os.DirFS etc. With `FS` set, the `root` argument of `New` names a directory inside it, and `""` serves its root. | `nil` | | Compress | `bool` | When set to true, the server tries minimizing CPU usage by caching compressed files. The middleware will compress the response using `gzip`, `brotli`, or `zstd` compression depending on the [Accept-Encoding](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding) header. This works differently than the github.com/gofiber/compression middleware. | `false` | | ByteRange | `bool` | When set to true, enables byte range requests. A request for several ranges, or whose `If-Range` does not match, gets the whole file. | `false` | | Browse | `bool` | When set to true, enables directory browsing. | `false` | | Download | `bool` | When set to true, enables direct download. | `false` | | IndexNames | `[]string` | The names of the index files for serving a directory. | `[]string{"index.html"}` | | CacheDuration | `time.Duration` | Expiration duration for inactive file handlers. Use a negative time.Duration to disable it. | `10 * time.Second` | | MaxAge | `int` | The value for the Cache-Control HTTP-header that is set on the file response. MaxAge is defined in seconds. | `0` | | ModifyResponse | `fiber.Handler` | ModifyResponse defines a function that allows you to alter the response. | `nil` | | NotFoundHandler | `fiber.Handler` | NotFoundHandler defines a function to handle when the path is not found. | `nil` | When **Download** is enabled, the response of a served file includes a `Content-Disposition` header with the requested filename, while the `Content-Type` the file server detected is kept. Non-ASCII names use the `filename*` parameter as defined by [RFC 6266](https://www.rfc-editor.org/rfc/rfc6266) and [RFC 8187](https://www.rfc-editor.org/rfc/rfc8187). A request for a missing file falls through to the next handler without the header. **MaxAge** only applies to successful responses; an error such as `416 Range Not Satisfiable` carries no `Cache-Control`. A handler registered on several routes serves each under that route's own prefix. :::info You can set `CacheDuration` config property to `-1` to disable caching. ::: ### Default Config ```go var ConfigDefault = Config{ IndexNames: []string{"index.html"}, CacheDuration: 10 * time.Second, } ``` --- ## Timeout The timeout middleware enforces a deadline on handler execution. It wraps handlers with `context.WithTimeout`, exposes the derived context through `c.Context()`, and returns `503 Service Unavailable` when the deadline is exceeded. ### How It Works When a timeout occurs, the middleware **returns immediately** without waiting for the handler to finish. This is achieved through Fiber's **Abandon mechanism**: 1. The handler runs in a goroutine with a timeout context 2. On timeout, the middleware marks the context as "abandoned" and returns `503` immediately 3. The handler goroutine can continue safely (e.g., for cleanup) without blocking the response 4. A background cleanup goroutine waits for the handler to finish and performs context cleanup Handlers can detect the timeout by listening on `c.Context().Done()` and return early. This is the recommended pattern for cooperative cancellation. If a handler panics, the middleware catches it and returns `500 Internal Server Error`. ### Why 503 A handler that outlives its deadline is slow on the server's side: the request arrived complete. `408 Request Timeout` means the server did not receive a complete request in time ([RFC 9110 §15.5.9](https://www.rfc-editor.org/rfc/rfc9110#section-15.5.9)), and it tells the client that it may send the request again, while the timed-out handler can still be running and may finish its work. The default is therefore `503 Service Unavailable` ([§15.6.4](https://www.rfc-editor.org/rfc/rfc9110#section-15.6.4)). Use `504 Gateway Timeout` ([§15.6.5](https://www.rfc-editor.org/rfc/rfc9110#section-15.6.5)) when the handler stands in front of an upstream that did not answer in time, and return it from `OnTimeout`: ```go timeout.Config{ Timeout: 5 * time.Second, OnTimeout: func(c fiber.Ctx) error { return fiber.ErrGatewayTimeout }, } ``` ### Known limitations - The timed-out handler keeps running in its own goroutine while the middleware returns `fiber.ErrServiceUnavailable` (or runs `OnTimeout`). A handler must stop using the context once `c.Context()` is done: one that keeps writing the response races with `OnTimeout`. - The error is returned to the outer middleware, but the app's `ErrorHandler` is not run for a timed-out request: fasthttp already holds the response it will send, so the handler could not change it, and the timed-out handler may still be writing to the context. - Timed-out requests abandon their `fiber.Ctx` to avoid data races with the core request handler. A `*fiber.DefaultCtx` is reclaimed once both the timed-out handler has finished and Fiber has released the context, so it does return to the pool. A **custom `Ctx` implementation** has no such wiring: its context stays out of the pool, and each timed-out request leaks one. Calling `ForceRelease` yourself is only safe if you can guarantee that no goroutine (including Fiber internals) will touch the context anymore. :::caution `timeout.New` wraps your final handler, so register it per route rather than with `app.Use`. The handler runs in its own goroutine: if it calls `c.Next()`, the rest of the chain runs in that goroutine too and keeps running after the timeout response has been sent. ::: ### Signatures ```go func New(handler fiber.Handler, config ...timeout.Config) fiber.Handler ``` ### Examples #### Basic example The following program times out any request that takes longer than two seconds. The handler simulates work with `sleepWithContext`, which stops when the context is canceled: ```go package main import ( "context" "fmt" "log" "time" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/timeout" ) func sleepWithContext(ctx context.Context, d time.Duration) error { select { case <-time.After(d): return nil case <-ctx.Done(): return ctx.Err() } } func main() { app := fiber.New() handler := func(c fiber.Ctx) error { delay, _ := time.ParseDuration(c.Params("delay") + "ms") if err := sleepWithContext(c.Context(), delay); err != nil { return fmt.Errorf("%w: execution error", err) } return c.SendString("finished") } app.Get("/sleep/:delay", timeout.New(handler, timeout.Config{ Timeout: 2 * time.Second, })) log.Fatal(app.Listen(":3000")) } ``` Use these requests to see the middleware in action: ```bash curl -i http://localhost:3000/sleep/1000 # finishes within the timeout curl -i http://localhost:3000/sleep/3000 # returns 503 Service Unavailable ``` ### Config | Property | Type | Description | Default | |:------------|:-------------------|:---------------------------------------------------------------------|:-------| | Next | `func(fiber.Ctx) bool` | Function to skip this middleware when it returns `true`. | `nil` | | Timeout | `time.Duration` | Timeout duration for requests. `0` or a negative value disables the timeout. | `0` | | OnTimeout | `fiber.Handler` | Handler executed when a timeout occurs. It may write the response itself or return a `*fiber.Error`, whose status and message then form the response; otherwise the default 503 is sent. Defaults to returning `fiber.ErrServiceUnavailable`. | `nil` | | Errors | `[]error` | Custom errors treated as timeout errors, in addition to `context.DeadlineExceeded`. A handler that returns one of them (checked with `errors.Is`) gets the timeout response. | `nil` | #### Use with a custom error ```go var ErrFooTimeOut = errors.New("foo context canceled") func main() { app := fiber.New() h := func(c fiber.Ctx) error { sleepTime, _ := time.ParseDuration(c.Params("sleepTime") + "ms") if err := sleepWithContextWithCustomError(c.Context(), sleepTime); err != nil { return fmt.Errorf("%w: execution error", err) } return nil } app.Get("/foo/:sleepTime", timeout.New(h, timeout.Config{Timeout: 2 * time.Second, Errors: []error{ErrFooTimeOut}})) log.Fatal(app.Listen(":3000")) } func sleepWithContextWithCustomError(ctx context.Context, d time.Duration) error { timer := time.NewTimer(d) select { case <-ctx.Done(): if !timer.Stop() { <-timer.C } return ErrFooTimeOut case <-timer.C: } return nil } ``` #### Sample usage with a database call ```go func main() { app := fiber.New() db, _ := gorm.Open(postgres.Open("postgres://localhost/foodb"), &gorm.Config{}) handler := func(ctx fiber.Ctx) error { tran := db.WithContext(ctx.Context()).Begin() if tran = tran.Exec("SELECT pg_sleep(50)"); tran.Error != nil { return tran.Error } if tran = tran.Commit(); tran.Error != nil { return tran.Error } return nil } app.Get("/foo", timeout.New(handler, timeout.Config{Timeout: 10 * time.Second})) log.Fatal(app.Listen(":3000")) } ``` ### Default Config With the default config, `Timeout` is `0`, so the wrapped handler runs without a deadline until you set one. ```go var ConfigDefault = Config{ Next: nil, Timeout: 0, OnTimeout: nil, Errors: nil, } ``` --- ## 🆕 What's New in v3 ### 🎉 Welcome We are excited to announce the release of Fiber v3! 🚀 In this guide, we'll walk you through the most important changes in Fiber `v3` and show you how to migrate your existing Fiber `v2` applications to Fiber `v3`. #### 🛠️ Migration tool Fiber v3 introduces a CLI-powered migration helper. Install the CLI and let it update your project automatically: ```bash go install github.com/gofiber/cli/fiber@latest fiber migrate --to v3 ``` See the [migration guide](#-migration-guide) for more details and options. Here's a quick overview of the changes in Fiber `v3`: - [🚀 App](#-app) - [🎣 Hooks](#-hooks) - [🚀 Listen](#-listen) - [🗺️ Router](#-router) - [🧠 Context](#-context) - [📎 Binding](#-binding) - [🔬 Extractors Package](#-extractors-package) - [🔄️ Redirect](#-redirect) - [🌎 Client package](#-client-package) - [🧰 Generic functions](#-generic-functions) - [🛠️ Utils](#utils) - [🧩 Services](#-services) - [📃 Log](#-log) - [📦 Storage Interface](#-storage-interface) - [🧬 Middlewares](#-middlewares) - [Important Change for Accessing Middleware Data](#important-change-for-accessing-middleware-data) - [Adaptor](#adaptor) - [BasicAuth](#basicauth) - [Cache](#cache) - [CORS](#cors) - [CSRF](#csrf) - [Compression](#compression) - [EncryptCookie](#encryptcookie) - [Favicon](#favicon) - [Filesystem](#filesystem) - [Healthcheck](#healthcheck) - [KeyAuth](#keyauth) - [Logger](#logger) - [Monitor](#monitor) - [Proxy](#proxy) - [Recover](#recover) - [Session](#session) - [SSE](#sse) - [🔌 Addons](#-addons) - [📋 Migration guide](#-migration-guide) ### Dropping support for old Go versions Fiber `v3` requires Go `1.26` or later. Update your toolchain to `1.26+` before upgrading so the module `go` directive and standard library features align with the new minimum version. ### 🚀 App We have made several changes to the Fiber app, including: - **Listen**: The `Listen` method has been unified with the configuration, allowing for more streamlined setup. - **Static**: The `Static` method has been removed and its functionality has been moved to the [static middleware](./middleware/static.md). - **app.Config properties**: Several properties have been moved to the listen configuration: - `DisableStartupMessage` - `EnablePrefork` (previously `Prefork`) - `EnablePrintRoutes` - `ListenerNetwork` (previously `Network`) - **Trusted Proxy Configuration**: The `EnabledTrustedProxyCheck` has been moved to `app.Config.TrustProxy`, and `TrustedProxies` has been moved to `TrustProxyConfig.Proxies`. Additionally, `ProxyHeader` must be set to read client IPs from proxy headers (e.g., `X-Forwarded-For`). - **XMLDecoder Config Property**: The `XMLDecoder` property has been added to allow usage of 3rd-party XML libraries in XML binder. - **SkipUnmatchedRoutes Config Property**: Opt-in flag that answers requests with no matching route with `404`/`405` before the middleware chain runs. Note that middleware (loggers, static or catch-all responders) does not run for these requests; CORS preflight requests are exempt so cors middleware keeps working. Customize the responses via `ErrorHandler`. - **Buffer sizes**: The default `ReadBufferSize` and `WriteBufferSize` are now `8192` bytes (they were `4096` in v2). The read buffer also limits the size of the request headers, so requests with larger headers or cookies are accepted by default. Set either field to keep a different size. #### New Methods - **RegisterCustomBinder**: Allows for the registration of custom binders. - **RegisterCustomConstraint**: Allows for the registration of custom constraints. - **NewWithCustomCtx**: Initialize an app with a custom context in one step. - **State**: Provides a global state for the application, which can be used to store and retrieve data across the application. Check out the [State](./api/state) method for further details. - **SharedState**: Introduces storage-backed app state for prefork-safe/multi-process coordination via `Config.SharedStorage`, with optional `Config.SharedStatePrefix` namespacing, codec-aware helpers (`SetJSON`, `SetMsgPack`, `SetCBOR`, `SetXML`, matching getters, and `WithContext` variants), empty-key no-op handling, and `Reset`/`Close` passthrough helpers. - **NewErrorf**: Allows variadic parameters when creating formatted errors. - **GetBytes / GetString**: Helpers that detach values only when `Immutable` is enabled and the data still references request or response buffers. Access via `c.App().GetString` and `c.App().GetBytes`. - **ReloadViews**: Lets you re-run the configured view engine's `Load()` logic at runtime, including guard rails for missing or nil view engines so development hot-reload hooks can refresh templates safely. - **Render**: Renders a template through the configured view engine to any `io.Writer`, applying `Config.ViewsLayout` unless you pass layouts, and returns `ErrNoViewEngineConfigured` when no engine is set. See [Render](./api/app.md#render). ##### Custom Route Constraints Custom route constraints enable you to define your own validation rules for route parameters. Use `RegisterCustomConstraint` to add a constraint type that implements the `CustomConstraint` interface.
Example ```go type UlidConstraint struct { fiber.CustomConstraint } func (*UlidConstraint) Name() string { return "ulid" } func (*UlidConstraint) Execute(param string, args ...string) bool { _, err := ulid.Parse(param) return err == nil } app.RegisterCustomConstraint(&UlidConstraint{}) app.Get("/login/:id", func(c fiber.Ctx) error { return c.SendString("User " + c.Params("id")) }) ```
#### Removed Methods - **Mount**: Use `app.Use()` instead. - **ListenTLS**: Use `app.Listen()` with `tls.Config`. - **ListenTLSWithCertificate**: Use `app.Listen()` with `tls.Config`. - **ListenMutualTLS**: Use `app.Listen()` with `tls.Config`. - **ListenMutualTLSWithCertificate**: Use `app.Listen()` with `tls.Config`. #### Method Changes - **Test**: The `Test` method has replaced the timeout parameter with a configuration parameter. `0` or lower represents no timeout. - **Listen**: Now has a configuration parameter. - **Listener**: Now has a configuration parameter. #### Custom Ctx Interface in Fiber v3 Fiber v3 introduces a customizable `Ctx` interface, allowing developers to extend and modify the context to fit their needs. This feature provides greater flexibility and control over request handling. ##### Idea Behind Custom Ctx Classes The idea behind custom `Ctx` classes is to give developers the ability to extend the default context with additional methods and properties tailored to the specific requirements of their application. This allows for better request handling and easier implementation of specific logic. ##### NewWithCustomCtx `NewWithCustomCtx` creates the application and sets the custom context factory at initialization time. ```go title="Signature" func NewWithCustomCtx(fn func(app *App) CustomCtx, config ...Config) *App ```
Example ```go package main import ( "log" "github.com/gofiber/fiber/v3" ) type CustomCtx struct { fiber.DefaultCtx } func (c *CustomCtx) CustomMethod() string { return "custom value" } func main() { app := fiber.NewWithCustomCtx(func(app *fiber.App) fiber.CustomCtx { return &CustomCtx{ DefaultCtx: *fiber.NewDefaultCtx(app), } }) app.Get("/", func(c fiber.Ctx) error { customCtx := c.(*CustomCtx) return c.SendString(customCtx.CustomMethod()) }) log.Fatal(app.Listen(":3000")) } ``` This example creates a `CustomCtx` with an extra `CustomMethod` and initializes the app with `NewWithCustomCtx`.
#### Configurable TLS Minimum Version We have added support for configuring the TLS minimum version. This field allows you to set the TLS minimum version for TLSAutoCert and the server listener. ```go app.Listen(":444", fiber.ListenConfig{TLSMinVersion: tls.VersionTLS12}) ``` ##### TLS AutoCert support (ACME / Let's Encrypt) We have added native support for automatic certificates management from Let's Encrypt and any other ACME-based providers. ```go // Certificate manager certManager := &autocert.Manager{ Prompt: autocert.AcceptTOS, // Replace with your domain name HostPolicy: autocert.HostWhitelist("example.com"), // Folder to store the certificates Cache: autocert.DirCache("./certs"), } app.Listen(":444", fiber.ListenConfig{ AutoCertManager: certManager, }) ``` #### MIME Constants `MIMEApplicationJavaScriptCharsetUTF8` has been removed. Use `MIMETextJavaScriptCharsetUTF8` instead. `MIMEApplicationJavaScript` is still defined, but prefer `MIMETextJavaScript` for new code. #### Protocol error responses Requests that the server refuses before routing, because of how they are framed or which method they use, are answered as [RFC 9110](https://www.rfc-editor.org/rfc/rfc9110) and [RFC 9112](https://www.rfc-editor.org/rfc/rfc9112) ask: - **`GETOnly`**: the `405 Method Not Allowed` sent for any other method now carries `Allow: GET, HEAD`, as every 405 must. - **Unknown transfer coding**: a request whose `Transfer-Encoding` names a coding the server cannot decode (`gzip, chunked`, say) is answered `501 Not Implemented`; it used to be `400 Bad Request`, and the body no longer repeats the start of the request. A malformed message, such as a repeated `Transfer-Encoding` field or the field on an HTTP/1.0 request, is still `400`, and so is every one of these when `app.Server().SecureErrorLogMessage` is set, because fasthttp then no longer says which coding it saw. - **`Content-Length` together with `Transfer-Encoding`**: the request is processed as before, by its `Transfer-Encoding` or, when the field says `identity`, which fasthttp ignores, by its `Content-Length`, and the connection is now closed after the response (`Connection: close`). The two fields are how a request-smuggling message looks, and the bytes behind it on the connection may belong to a request that another recipient framed differently. Fiber installs this check right before the server it starts begins to serve (`Listen`, `Listener`, `Test`), and keeps the `HeaderReceived` callback you set on `app.Server()` up to then, in `ListenConfig.BeforeServeFunc` too. A callback you set again between two starts is wrapped at the next one. A `fasthttp.Server` of your own that serves `app.Handler()` needs the same check in its own `HeaderReceived` callback. ### 🎣 Hooks We have made several changes to the Fiber hooks, including: - Added new shutdown hooks to provide better control over the shutdown process: - `OnPreShutdown` - Executes before the server starts shutting down - `OnPostShutdown` - Executes after the server has shut down, receives any shutdown error - `OnPreStartupMessage` - Executes before the startup message is printed, allowing customization of the banner and info entries - `OnPostStartupMessage` - Executes after the startup message is printed or skipped, allowing post-startup logic - Replaced `OnShutdown` with the new pre/post shutdown hooks - Improved shutdown hook execution order and reliability - Added mutex protection for hook registration and execution Important: When using shutdown hooks, ensure app.Listen() is called in a separate goroutine: ```go // Correct usage go app.Listen(":3000") // ... register shutdown hooks app.Shutdown() // Incorrect usage - hooks won't work app.Listen(":3000") // This blocks app.Shutdown() // Never reached ``` ### 🚀 Listen We have made several changes to the Fiber listen, including: - Removed `OnShutdownError` and `OnShutdownSuccess` from `ListenConfig` in favor of using the `OnPostShutdown` hook, which receives the shutdown error ```go app := fiber.New() // Before - using ListenConfig callbacks app.Listen(":3000", fiber.ListenConfig{ OnShutdownError: func(err error) { log.Printf("Shutdown error: %v", err) }, OnShutdownSuccess: func() { log.Println("Shutdown successful") }, }) // After - using OnPostShutdown hook app.Hooks().OnPostShutdown(func(err error) error { if err != nil { log.Printf("Shutdown error: %v", err) } else { log.Println("Shutdown successful") } return nil }) go app.Listen(":3000") ``` This change simplifies the shutdown handling by consolidating the shutdown callbacks into a single hook that receives the error status. - Added support for Unix domain sockets via `ListenerNetwork` and `UnixSocketFileMode` ```go // v2 - Requires manual deletion of old file and permissions change app := fiber.New(fiber.Config{ Network: "unix", }) os.Remove("app.sock") app.Hooks().OnListen(func(fiber.ListenData) error { return os.Chmod("app.sock", 0770) }) app.Listen("app.sock") // v3 - Fiber does it for you app := fiber.New() app.Listen("app.sock", fiber.ListenConfig{ ListenerNetwork: fiber.NetworkUnix, UnixSocketFileMode: 0770, }) ``` - Added `TLSConfig` to `ListenConfig` so external providers can supply certificates via `GetCertificate`. Prefer `TLSConfig` when configuring TLS; when set, it is cloned and takes precedence over other TLS fields. ```go app := fiber.New() app.Listen(":443", fiber.ListenConfig{ TLSConfig: &tls.Config{ GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) { return myProvider.Certificate(info.ServerName) }, }, }) ``` - Expanded `ListenData` with versioning, handler, process, and PID metadata, plus dedicated startup message hooks for customization. Check out the [Hooks](./api/hooks#startup-message-customization) documentation for further details. ```go title="Customize the startup message" package main import ( "fmt" "os" "github.com/gofiber/fiber/v3" ) func main() { app := fiber.New() app.Hooks().OnPreStartupMessage(func(sm *fiber.PreStartupMessageData) error { sm.BannerHeader = "FOOBER " + sm.Version + "\n-------" // Optional: you can also remove old entries // sm.ResetEntries() sm.AddInfo("git-hash", "Git hash", os.Getenv("GIT_HASH")) sm.AddInfo("prefork", "Prefork", fmt.Sprintf("%v", sm.Prefork), 15) return nil }) app.Hooks().OnPostStartupMessage(func(sm *fiber.PostStartupMessageData) error { if !sm.Disabled && !sm.IsChild && !sm.Prevented { fmt.Println("startup completed") } return nil }) app.Listen(":5000") } ``` ### 🗺 Router We have slightly adapted our router interface #### Path normalization The router normalizes every request path before matching, as [RFC 3986 Section 6.2.2](https://www.rfc-editor.org/rfc/rfc3986#section-6.2.2) describes. Percent-encoded unreserved characters are decoded, so `/%70rivate` is matched as `/private`; every other escape keeps its encoding with uppercase hex digits; and `.` and `..` segments are removed, so `/static/x/../private` and `/static/./private` are both matched as `/static/private`. Middleware mounted on a prefix therefore sees those spellings of a path under it, and the static middleware serves the file that same path names. An encoded slash (`%2F`) still does not split a segment, so `/static%2Fprivate` does not match `/static/private`, and empty segments are kept, so neither does `/static//private`. `UnescapePath` decodes every escape, as before. Decoding happens exactly once, so `/%2570rivate` never becomes `/private`. A stray `%` that begins no escape is kept as sent, unless an escape decoded after it would complete a new escape with it: `/%%370rivate` is matched as `/%2570rivate`, never as `/%70rivate`, while `/files/100%` and `/files/./100%` both give the parameter `100%`. A request target that is not a rooted path, the asterisk form of `OPTIONS *` or an absolute-form URI with an empty path, is routed as the rooted path fasthttp reports for it (`/*` and `/`), so root-level middleware runs for it as for any other request; so is the authority form fasthttp admits for `CONNECT`, whose `admin//secret` is routed as `/admin//secret`. `RoutePatternMatch` takes a path rather than a request target: it reads the empty string as `/` and answers `false` for any other string without a leading slash; before, `RoutePatternMatch("*", "/*")` was `true`. `c.Path()` returns the normalized path. `c.OriginalURL()` still returns the request target as the client sent it. #### Optional parameters An optional parameter that ends at a single-character constant, such as `:month?-` or `:name?.`, no longer matches across a `/`. Before, `/api/:day-:month?-:year?` matched `/api/1-/-` with `month` set to `/`, and `/file/:name?.:ext` matched `/file/a/b.txt` with `name` set to `a/b`; both now return 404. A required parameter keeps its behavior: `/file/:name.:ext` still matches `/file/a/b.txt`. With `UnescapePath` on, `Route.URL`, `GetRouteURL` and `Redirect().Route` return `ErrRouteNotRepresentable` for a `/` in the value of such an optional parameter. #### Generated route URLs `Route.URL`, `GetRouteURL` and `Redirect().Route` percent-encode each parameter value with URL path-segment rules. A value is data, so a representable value composes the same URL under any configuration. Previously the value was written into the path as given: `a/b` under `/user/:name` produced `/user/a/b`, which matched no route; `a?b` produced `/user/a?b`, whose tail became a query and truncated the value; and `100%` produced `/user/100%`, which Go's own URL parser rejects. Greedy parameters (`*` and `+`) keep the `/` of the matched path tail. Because the value is data, one that already carries escapes is encoded again. With `UnescapePath` off, which is the default, `c.Params` returns the value as the request spelled it, so forwarding it into a generated URL turns `M%C3%BCller` into `M%25C3%25BCller`. Decode it first, or turn `UnescapePath` on. A value the router could not match back returns `ErrRouteNotRepresentable` instead of a URL: one that forms a `.` or `..` segment, which clients and the router remove (`/user/:name` with `..`), and, with `UnescapePath` on, a `/` in an ordinary single-segment parameter, which the router decodes before matching. Greedy parameters keep accepting slashes. #### Handler compatibility Fiber now ships with a routing adapter (see `adapter.go`) that understands native Fiber handlers alongside `net/http` and `fasthttp` handlers. Route registration helpers accept a required `handler` argument plus optional additional `handlers`, all typed as `any`, and the adapter transparently converts supported handler styles so you can keep using the ecosystem functions you're familiar with. To align even closer with Express, you can also register handlers that accept the new `fiber.Req` and `fiber.Res` helper interfaces. The adapter understands both two-argument (`func(fiber.Req, fiber.Res)`) and three-argument (`func(fiber.Req, fiber.Res, func() error)`) callbacks, regardless of whether they return an `error`. It also accepts Express-style `next` callbacks that take an `error` (`func(error)` or `func(error) error`). When you include the optional `next` callback, Fiber wires it to `c.Next()` for you so middleware continues to behave as expected. Calling `next(nil)` continues the chain, while passing a non-nil error short-circuits and returns that error. If your handler returns an `error`, the value returned from the injected `next()` bubbles straight back to the caller. When your handler omits an `error` return, Fiber records the result of `next()` and returns it after your function exits so downstream failures still propagate. | Case | Handler signature | Notes | | ---- | ----------------- | ----- | | 1 | `fiber.Handler` | Native Fiber handler. | | 2 | `func(fiber.Ctx)` | Fiber handler without an error return. | | 3 | `func(fiber.Req, fiber.Res) error` | Express-style request handler with error return. | | 4 | `func(fiber.Req, fiber.Res)` | Express-style request handler without error return. | | 5 | `func(fiber.Req, fiber.Res, func() error) error` | Express-style middleware with an error-returning `next` callback and handler error return. | | 6 | `func(fiber.Req, fiber.Res, func() error)` | Express-style middleware with an error-returning `next` callback. | | 7 | `func(fiber.Req, fiber.Res, func()) error` | Express-style middleware with a no-argument `next` callback and handler error return. | | 8 | `func(fiber.Req, fiber.Res, func())` | Express-style middleware with a no-argument `next` callback. | | 9 | `func(fiber.Req, fiber.Res, func(error))` | Express-style middleware with an error-accepting `next` callback. | | 10 | `func(fiber.Req, fiber.Res, func(error)) error` | Express-style middleware with an error-accepting `next` callback and handler error return. | | 11 | `func(fiber.Req, fiber.Res, func(error) error)` | Express-style middleware with an error-accepting `next` callback that returns an error. | | 12 | `func(fiber.Req, fiber.Res, func(error) error) error` | Express-style middleware with an error-accepting `next` callback that returns an error and handler error return. | | 13 | `http.HandlerFunc` | Standard-library handler function adapted through `fasthttpadaptor`. | | 14 | `http.Handler` | Standard-library handler implementation; pointer receivers must be non-nil. | | 15 | `func(http.ResponseWriter, *http.Request)` | Standard-library function handlers via `fasthttpadaptor`. | | 16 | `fasthttp.RequestHandler` | Direct fasthttp handler without error return. | | 17 | `func(*fasthttp.RequestCtx) error` | fasthttp handler that returns an error to Fiber. | #### Route chaining `RouteChain` is a new helper inspired by [`Express`](https://expressjs.com/en/api.html#app.route) that makes it easy to declare a stack of handlers on the same path, while the existing `Route` helper stays available for prefix encapsulation. ```go RouteChain(path string) Register ```
Example ```go app.RouteChain("/api").RouteChain("/user/:id?"). Get(func(c fiber.Ctx) error { // Get user return c.JSON(fiber.Map{"message": "Get user", "id": c.Params("id")}) }). Post(func(c fiber.Ctx) error { // Create user return c.JSON(fiber.Map{"message": "User created"}) }). Put(func(c fiber.Ctx) error { // Update user return c.JSON(fiber.Map{"message": "User updated", "id": c.Params("id")}) }). Delete(func(c fiber.Ctx) error { // Delete user return c.JSON(fiber.Map{"message": "User deleted", "id": c.Params("id")}) }) ```
You can find more information about `app.RouteChain` and `app.Route` in the API documentation ([RouteChain](./api/app#routechain), [Route](./api/app#route)). Named routes retrieved with `app.GetRoute(name)` also support `route.URL(params)` for generating relative URLs directly from the route definition, including parameter substitution for named, wildcard (`*`), and plus (`+`) segments. #### Domain routing `Domain` creates a router scoped to a specific hostname pattern. Routes registered through the returned `Router` only match requests whose hostname (from `c.Hostname()`) matches the pattern. When `TrustProxy` is enabled and the proxy is trusted (as defined by [`TrustProxyConfig`](./api/fiber#trustproxyconfig)), the hostname may be derived from the `X-Forwarded-Host` header. Be sure to configure `TrustProxyConfig` to restrict which proxies are trusted and prevent header spoofing when enabling `TrustProxy`. The pattern can contain parameters prefixed with `:`, accessible via `fiber.DomainParam`. Domain routing has **zero performance impact** on routes that don't use it because the hostname check is applied as a handler wrapper, not a change to the core router. > **Note:** Because domain filtering happens at handler-execution time, Fiber's `405 Method Not Allowed` responses may advertise methods from domain-scoped routes even when the requesting host does not match. This is a known trade-off of the handler-wrapping approach. > > When mounting sub-applications via `Domain(...).Use(*fiber.App)`, routes are cloned at mount time. The same sub-app can safely be mounted on multiple domains, but routes added to the sub-app after mounting will not inherit domain filtering. Register all sub-app routes before mounting. ```go Domain(host string) Router ```
Example ```go app := fiber.New() // Static domain app.Domain("api.example.com").Get("/users", func(c fiber.Ctx) error { return c.SendString("API users") }) // Domain with parameter app.Domain(":user.blog.example.com").Get("/", func(c fiber.Ctx) error { user := fiber.DomainParam(c, "user") return c.SendString(user + "'s blog") }) // Domain with groups api := app.Domain("api.example.com") v1 := api.Group("/v1") v1.Get("/posts", listPosts) // Domain with middleware admin := app.Domain("admin.example.com") admin.Use(authMiddleware) admin.Get("/dashboard", dashboardHandler) // Mount sub-applications on domain routers subApp := fiber.New() subApp.Get("/users", listUsers) app.Domain("api.example.com").Use("/api", subApp) // Fallback for unmatched domains app.Get("/", func(c fiber.Ctx) error { return c.SendString("default site") }) ```
#### Automatic HEAD routes for GET Fiber now auto-registers a `HEAD` route whenever you add a `GET` route. The generated handler chain matches the `GET` chain so status codes and headers stay in sync while the response body remains empty, ensuring `HEAD` clients observe the same metadata as a `GET` consumer. ```go title="GET now enables HEAD automatically" app := fiber.New() app.Get("/health", func(c fiber.Ctx) error { c.Set("X-Service", "api") return c.SendString("OK") }) // HEAD /health reuses the GET middleware chain and returns headers only. ``` You can still register explicit `HEAD` handlers for any `GET` route, and they continue to win when you add them: ```go title="Override the generated HEAD handler" app.Head("/health", func(c fiber.Ctx) error { return c.SendStatus(fiber.StatusNoContent) }) ``` Prefer to manage `HEAD` routes yourself? Disable the feature through `fiber.Config.DisableHeadAutoRegister`: ```go title="Disable automatic HEAD registration" handler := func(c fiber.Ctx) error { c.Set("X-Service", "api") return c.SendString("OK") } app := fiber.New(fiber.Config{DisableHeadAutoRegister: true}) app.Get("/health", handler) // HEAD /health now returns 405 unless you add it manually. ``` Auto-generated `HEAD` routes appear in tooling such as `app.Stack()` and cover the same routing scenarios as their `GET` counterparts, including groups, mounted apps, dynamic parameters, and static file handlers. #### QUERY method (RFC 10008) Fiber now supports the HTTP `QUERY` method ([RFC 10008](https://www.rfc-editor.org/rfc/rfc10008.html)) as a first-class verb. `QUERY` is safe and idempotent like `GET`, but allows a request body for complex queries. ```go title="Register a QUERY route" app := fiber.New() app.Query("/search", func(c fiber.Ctx) error { // QUERY carries a request body, unlike GET. return c.Send(c.Body()) }) ``` `Query` is available on `App`, `Group`, the route-chaining `Register` interface, and domain routers, plus the HTTP client (`Request.Query`, `Client.Query`, and the package-level `Query`). `fiber.IsMethodSafe` and `fiber.IsMethodIdempotent` both return `true` for `QUERY`, so middleware that keys off method safety (CSRF, idempotency, early data) treats it like the other safe methods. The cache middleware can cache `QUERY` responses when `QUERY` is added to `Config.Methods`; the default key generator folds the request body into the cache key so different bodies on the same URL do not collide. #### Middleware registration We have aligned our method for middlewares closer to [`Express`](https://expressjs.com/en/api.html#app.use) and now also support the [`Use`](./api/app#use) of multiple prefixes. Prefix matching is now stricter: partial matches must end at a slash boundary (or be an exact match). This keeps `/api` middleware from running on `/apiv1` while still allowing `/api/:version` style patterns that leverage route parameters, optional segments, or wildcards. Registering a subapp is now also possible via the [`Use`](./api/app#use) method instead of the old `app.Mount` method.
Example ```go // register multiple prefixes app.Use([]string{"/v1", "/v2"}, func(c fiber.Ctx) error { // Middleware for /v1 and /v2 return c.Next() }) // define subapp api := fiber.New() api.Get("/user", func(c fiber.Ctx) error { return c.SendString("User") }) // register subapp app.Use("/api", api) ```
To enable the routing changes above we had to slightly adjust the signature of the `Add` method. ```diff - Add(method, path string, handlers ...Handler) Router + Add(methods []string, path string, handler any, handlers ...any) Router ``` #### Test Config The `app.Test()` method now allows users to customize their test configurations:
Example ```go // Create a test app with a handler to test app := fiber.New() app.Get("/", func(c fiber.Ctx) error { return c.SendString("hello world") }) // Define the HTTP request and custom TestConfig to test the handler req := httptest.NewRequest(fiber.MethodGet, "/", nil) testConfig := fiber.TestConfig{ Timeout: 0, FailOnTimeout: false, } // Test the handler using the request and testConfig resp, err := app.Test(req, testConfig) ```
To provide configurable testing capabilities, we had to change the signature of the `Test` method. ```diff - Test(req *http.Request, timeout ...time.Duration) (*http.Response, error) + Test(req *http.Request, config ...fiber.TestConfig) (*http.Response, error) ``` The `TestConfig` struct provides the following configuration options: - `Timeout`: The duration to wait before timing out the test. Use 0 for no timeout. - `FailOnTimeout`: Controls the behavior when a timeout occurs: - When true, the test will return an `os.ErrDeadlineExceeded` if the test exceeds the `Timeout` duration. - When false, the test will return the partial response received before timing out. If a custom `TestConfig` isn't provided, then the following will be used: ```go testConfig := fiber.TestConfig{ Timeout: time.Second, FailOnTimeout: true, } ``` **Note:** Using this default is **NOT** the same as providing an empty `TestConfig` as an argument to `app.Test()`. An empty `TestConfig` is the equivalent of: ```go testConfig := fiber.TestConfig{ Timeout: 0, FailOnTimeout: false, } ``` #### Constraint System The internal constraint system has been unified into a single `ConstraintHandler` interface. Built-in and custom constraints are now treated uniformly through this interface, with an optional `ConstraintAnalyzer` phase for precomputation at route registration time. ```go type ConstraintHandler interface { Name() string Execute(param string, data []any) bool } type ConstraintAnalyzer interface { Analyze(args []string) ([]any, error) } ``` Key improvements: - **Zero per-request parsing**: `strconv.Atoi`, `time.Parse` layouts, and regex compilation happen once at registration via `Analyze()`, not on every request. - **Single dispatch**: The previous `TypeConstraint` bitmask switch has been replaced by a single `handler.Execute()` call. - **Backward compatible**: Existing `CustomConstraint` implementations continue to work unchanged. The `CustomConstraint` interface, `RegisterCustomConstraint()` API, and `CheckConstraint()` method are all preserved. `RegisterCustomConstraint()` still takes a `CustomConstraint`, which Fiber wraps in a `ConstraintHandler` itself; a `ConstraintHandler` cannot be registered directly. The `TypeConstraint` type, `Constraint.ID`, and `Constraint.RegexCompiler` fields are retained but deprecated. ### 🧠 Context #### New Features - Cookie now allows Partitioned cookies for [CHIPS](https://developers.google.com/privacy-sandbox/3pcd/chips) support. CHIPS (Cookies Having Independent Partitioned State) is a feature that improves privacy by allowing cookies to be partitioned by top-level site, mitigating cross-site tracking. - Cookie automatic security enforcement: When setting a cookie with `SameSite=None`, Fiber automatically sets `Secure=true` as required by RFC 6265bis and modern browsers (Chrome, Firefox, Safari). This ensures compliance with the "None" SameSite policy. See [Mozilla docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#none) and [Chrome docs](https://developers.google.com/search/blog/2020/01/get-ready-for-new-samesitenone-secure) for details. - `Ctx` now implements the [context.Context](https://pkg.go.dev/context#Context) interface, replacing the former `UserContext` helpers. #### New Methods - **AutoFormat**: Similar to Express.js, automatically formats the response based on the request's `Accept` header. - **Deadline**: For implementing `context.Context`. - **Done**: For implementing `context.Context`. - **Err**: For implementing `context.Context`. - **Host**: Similar to Express.js, returns the host name of the request. - **Port**: Similar to Express.js, returns the port number of the request. - **IsProxyTrusted**: Checks the trustworthiness of the remote IP. - **Reset**: Resets context fields for server handlers. - **Scheme**: Similar to Express.js, returns the scheme (`http` or `https`) of the request. - **SendEarlyHints**: Sends `HTTP 103 Early Hints` status code with `Link` headers so browsers can preload resources while the final response is being prepared. - **SendStream**: Similar to Express.js, sends a stream as the response. - **SendStreamWriter**: Sends a stream using a writer function. - **SendString**: Similar to Express.js, sends a string as the response. - **String**: Similar to Express.js, converts a value to a string. - **Value**: For implementing `context.Context`. Returns request-scoped value from Locals. - **Context()**: Returns a `context.Context` that can be used outside the handler. - **SetContext**: Sets the base `context.Context` returned by `Context()` for propagating deadlines or values. - **SetLocal**: Stores a request-scoped value like `Locals(key, value)`, without building the one-element slice the variadic call allocates through the `Ctx` interface. - **ViewBind**: Binds data to a view, replacing the old `Bind` method. - **CBOR**: Introducing [CBOR](https://cbor.io/) binary encoding format for both request & response body. CBOR is a binary data serialization format which is both compact and efficient, making it ideal for use in web applications. - **MsgPack**: Introducing [MsgPack](https://msgpack.org/) binary encoding format for both request & response body. MsgPack is a binary serialization format that is more efficient than JSON, making it ideal for high-performance applications. - **Drop**: Terminates the client connection silently without sending any HTTP headers or response body. This can be used for scenarios where you want to block certain requests without notifying the client, such as mitigating DDoS attacks or protecting sensitive endpoints from unauthorized access. - **End**: Similar to Express.js, immediately flushes the current response and closes the underlying connection. - **AcceptsLanguagesExtended**: Matches language ranges using RFC 4647 Extended Filtering with wildcard subtags. - **FullURL**: Returns the full request URL (scheme + host + original URL). - **RequestID**: Returns the request identifier from the response or request headers. - **UserAgent**: Returns the `User-Agent` request header. - **Referer**: Returns the `Referer` request header. - **AcceptLanguage**: Returns the `Accept-Language` request header. - **AcceptEncoding**: Returns the `Accept-Encoding` request header. - **HasHeader**: Reports whether the request includes a header with the given key. - **HasHeaderValue**: Reports whether a comma-separated request header such as `Cache-Control` lists a member on any of its field lines, case-insensitively and respecting quoted arguments. - **MediaType**: Returns the MIME type from the `Content-Type` header without parameters. - **Charset**: Returns the `charset` parameter from the `Content-Type` header, or an empty string when the first `charset` value is not a valid token. - **IsJSON**: Reports whether the `Content-Type` header is JSON. - **IsForm**: Reports whether the `Content-Type` header is form-encoded. - **IsMultipart**: Reports whether the `Content-Type` header is multipart form data. - **AcceptsJSON**: Reports whether the `Accept` header allows JSON. - **AcceptsHTML**: Reports whether the `Accept` header allows HTML. - **AcceptsXML**: Reports whether the `Accept` header allows XML. - **AcceptsEventStream**: Reports whether the `Accept` header allows `text/event-stream`. - **Matched**: Detects when the current request path matched a registered route. - **Endpoint**: Looks ahead to the route that will handle the request, without advancing the handler chain, so global middleware can read its `Path` or `Name` before calling `Next`. It returns `nil` when no route will run. - **IsMiddleware**: Indicates if the current handler was registered as middleware. - **HasBody**: Quickly checks whether the request includes a body. - **OverrideParam**: Overwrites the value of an existing route parameter, or does nothing if the parameter does not exist - **IsWebSocket**: Reports if the request attempts a WebSocket upgrade. - **IsPreflight**: Identifies CORS preflight requests before handlers run. - **ID**: Returns the connection-unique identifier of the request, without depending on a header. - **StartTime** / **Elapsed**: Report when the server began handling the request and how long it has taken so far. - **LocalAddr** / **RemoteAddr**: Return the connection's addresses as `net.Addr`, where `IP` returns a string. - **Hijack** / **Hijacked**: Take over the connection after the response is sent, and report whether that has happened. - **RouteName**: Returns the name of the route currently executing. - **IsFinal**: Reports whether the current handler is the last one of a matched, non-middleware route. - **MountPath**: Returns the prefix the sub-app owning the current route was mounted under. - **Error**: Builds a `*fiber.Error` with the given status code, so a handler can reject a request in one line. - **GetAll**: Returns every field line of a request header, where `Get` returns only the first. - **GetHeaders**: Returns all headers as a map from name to every value. On `Ctx` and `Req` it returns the request headers, like `GetReqHeaders`; on `Res` it returns the response headers, like `GetRespHeaders`. - **Authorization** / **Bearer**: Split the `Authorization` header into scheme and credentials, and read a `Bearer` token. - **Origin**: Returns the `Origin` request header. - **ContentLength**: Returns the value of the `Content-Length` request header; `Res` carries one for the response. - **ContentType**: Returns the `Content-Type` request header with its parameters; `Res` carries one for the response. - **BodyStream**: Returns the request body as a stream when `StreamRequestBody` is enabled. - **URI**: Returns the parsed `*fasthttp.URI` of the request. - **CookieNames** / **AllCookies**: Enumerate the cookies the client sent. - **IfNoneMatch** / **IfModifiedSince**: Read the conditional-request headers, parsed. - **IsSafe** / **IsIdempotent**: Classify the request method per RFC 9110. - **Add**: Appends a response header as a new field line, where `Append` folds values into one. - **Del**: Removes a response header. - **StatusCode**: Returns the status code set on the response, the read side of `Status`. - **Body** (on `Res`): Returns the response body buffered so far. - **ResetBody**: Discards the response body, keeping the status and headers. - **Written**: Reports whether anything has been written to the response body yet. - **ContentType**: Returns the `Content-Type` response header, the read side of `Type`. - **GetCookie** / **GetCookies** (on `Res`): Read back the cookies the response is set to send. - **NoContent**: Replies `204 No Content`, discarding any body and the `Content-Type`. #### Req and Res Method Placement The request helpers listed above are defined on `Req`, so they are reachable both as `c.UserAgent()` and as `c.Req().UserAgent()`. This also applies to `Path`, `Secure`, `XHR`, `HasBody`, `IsWebSocket`, `IsPreflight` and the `Accept`/`Content-Type` helpers, which are request methods and are now on `Req` as well. `Req` and `Res` each define a `Get`, `GetHeaders`, `Body`, `ContentLength` and `ContentType`. On `Ctx` these resolve to the **request**; use `c.Res().Get()`, `c.Res().GetHeaders()`, `c.Res().Body()`, `c.Res().ContentLength()` and `c.Res().ContentType()` for the response. The response cookies are `c.Res().GetCookies()`, named apart from `Req.Cookies` so that a `Ctx` keeps satisfying `fiber.Res`. #### Removed Methods - **AllParams**: Use `c.Bind().URI()` instead. - **ParamsInt**: Use `Params` with generic types. - **QueryBool**: Use `Query` with generic types. - **QueryFloat**: Use `Query` with generic types. - **QueryInt**: Use `Query` with generic types. - **BodyParser**: Use `c.Bind().Body()` instead. - **CookieParser**: Use `c.Bind().Cookie()` instead. - **ParamsParser**: Use `c.Bind().URI()` instead. - **RedirectToRoute**: Use `c.Redirect().Route()` instead. - **RedirectBack**: Use `c.Redirect().Back()` instead. - **ReqHeaderParser**: Use `c.Bind().Header()` instead. - **UserContext**: Removed. `Ctx` itself now satisfies `context.Context`; pass `c` directly where a `context.Context` is required. - **SetUserContext**: Removed. Use `SetContext` and `Context()` or `context.WithValue` on `c` to store additional request-scoped values. #### Changed Methods - **Bind**: Now used for binding instead of view binding. Use `c.ViewBind()` for view binding. - **Format**: Parameter changed from `body any` to `handlers ...ResFmt`. - **Redirect**: Use `c.Redirect().To()` instead. - **SendFile**: Now supports different configurations using a config parameter. The file name is taken literally, as `os.Open` takes it: it is no longer percent-decoded, and a `?` or `#` in it is part of the name, so drop any `url.PathEscape` applied to it. An encoded separator carried by a route parameter (`..%2Fsecret.txt`) no longer decodes into a step out of the directory the name was joined to, a directory named without a trailing slash is not found instead of redirected to its filesystem path, and a name holding an ASCII control byte is not found. With `UnescapePath` off, a route parameter keeps its escapes, so a handler that passes one to `SendFile` must decode it first and confine the result, or enable `UnescapePath` (see the [migration guide](#-context-1)). `Download` follows, since it serves through `SendFile`. - **Attachment and Download**: Non-ASCII filenames now use `filename*` as specified by [RFC 6266](https://www.rfc-editor.org/rfc/rfc6266) and [RFC 8187](https://www.rfc-editor.org/rfc/rfc8187). The `filename` parameter is now emitted as a plain RFC 9110 quoted-string instead of being URL-encoded: `c.Download("report 2024.txt")` produces `filename="report 2024.txt"` (previously `filename="report+2024.txt"`), with quotes and backslashes escaped as quoted-pairs, so browsers save files under their real names. - **Get, GetReqHeader and the request header helpers**: Field names are now matched case-insensitively, as [RFC 9110, Section 5.1](https://www.rfc-editor.org/rfc/rfc9110#section-5.1) requires. This is only observable under `DisableHeaderNormalizing: true`, where the store keeps the spelling the peer sent: with a `x-test: yes` line, `c.Get("X-Test")` returned `""` in v2 and returns `"yes"` now. The default configuration is unchanged, because fasthttp canonicalizes the names on the way in. - **Fresh()**: `If-None-Match`, `If-Modified-Since` and `Cache-Control` are read case-insensitively for the same reason, so `c.Fresh()` can now return `true` where it returned `false` under `DisableHeaderNormalizing`. That changes which requests are answered `304 Not Modified`. - **SendStatus()**: For a status that cannot carry a body — `1xx`, `204`, `205` and `304` — the response body and any `Content-Length` already set are now dropped. [RFC 9110, Section 8.6](https://www.rfc-editor.org/rfc/rfc9110#section-8.6) requires that for `1xx` and `204`; Fiber also does it for `205` and `304`. `c.Set(fiber.HeaderContentLength, "42")` followed by `c.SendStatus(204)` previously put `Content-Length: 42` on the wire, along with a `Content-Type` fasthttp emits for a declared length; neither line is sent now. `205 Reset Content` still sends `Content-Length: 0`: it has no content ([Section 15.3.6](https://www.rfc-editor.org/rfc/rfc9110#section-15.3.6)), but unlike `1xx`, `204` and `304` it still has to be framed ([RFC 9112, Section 6.3](https://www.rfc-editor.org/rfc/rfc9112#section-6.3)). Unlike the two above, this applies under the default configuration. - **AcceptsEncodings()**: Follows [RFC 9110, Section 12.5.3](https://www.rfc-editor.org/rfc/rfc9110#section-12.5.3). `identity`, the absence of a coding, is now acceptable when the `Accept-Encoding` header does not list it, unless the header excludes it with `identity;q=0` or `*;q=0`: `c.AcceptsEncodings("br", "identity")` answers `"identity"` for `Accept-Encoding: gzip`, where it answered `""`. An unlisted `identity` ranks below every coding the header does list. A header that is present but empty now accepts only `identity`; it used to read as absent, which accepts the first offer. - **Status()**: A code outside `100` to `999` cannot be written as a status line ([RFC 9112, Section 4](https://www.rfc-editor.org/rfc/rfc9112#section-4)). `c.Status(99)` and `c.Status(1000)` used to put a malformed line on the wire that clients refuse, and `c.Status(0)` or a negative code put `200 OK` there. They now answer `500 Internal Server Error` and log the code. `SendStatus`, `Redirect().Status` and an error built with `fiber.NewError` go through `Status`, so they follow. Codes from `600` to `999` are still sent as given. - **Cookie()**: A value with a space or a comma is now written in double quotes, as `net/http` does, because neither is a `cookie-octet` ([RFC 6265, Section 4.1.1](https://www.rfc-editor.org/rfc/rfc6265#section-4.1.1)). `c.Cookie(&fiber.Cookie{Name: "n", Value: "hello world"})` used to send `n=hello world` and sends `n="hello world"` now; the quotes frame the value, and `Cookies` and `GetCookie` return `hello world`. A value that already starts and ends with a double quote is accepted and written quoted instead of being dropped as invalid. Values with a semicolon, an inner double quote, a backslash, a control character or a non-ASCII character are still refused. Percent-encode a value that has to pass through a strict parser. - **SendFile() and the static middleware, with `ByteRange`**: The file server answered one byte range and nothing else. Fiber now applies the rest of [RFC 9110, Section 13.1.5 and Section 14.2](https://www.rfc-editor.org/rfc/rfc9110#section-14.2) around it. A request for several ranges used to get `416`; it gets the whole file, as does one with a range unit the server does not know and one with a method other than `GET` (a `HEAD` or `POST` with a `Range` used to get `206`). The unit is case-insensitive (`Bytes=0-4` used to get `416`). `If-Range` was ignored, so a resumed download was spliced onto a file that had changed; the range is now served only when the date in `If-Range` is exactly the file's `Last-Modified` (and at least two seconds old), and the whole file is sent otherwise. - **Context()**: Renamed to `RequestCtx()` to access the underlying `fasthttp.RequestCtx`. - **IP()**: When `EnableIPValidation` is `true` and `TrustProxyConfig` is set, `c.IP()` now walks the `X-Forwarded-For` chain from right to left and returns the first non-trusted IP, instead of the leftmost syntactically valid IP. This closes an IP-spoofing vector where an attacker could prepend a fake address and have it returned by `c.IP()`. Apps with `EnableIPValidation = false` (the default) are unaffected. See [`Ctx.IP`](./api/ctx.md#ip) and the [reverse proxy guide](./guide/reverse-proxy.md#getting-the-real-client-ip-address) for details. #### SendEarlyHints `SendEarlyHints` sends an informational [`103 Early Hints`](https://developer.chrome.com/docs/web-platform/early-hints) response with `Link` headers based on the provided `hints` argument. This allows a browser to start preloading assets while the server is still preparing the final response. ```go hints := []string{"; rel=preload; as=script"} app.Get("/early", func(c fiber.Ctx) error { if err := c.SendEarlyHints(hints); err != nil { return err } return c.SendString("done") }) ``` Older HTTP/1.1 clients may ignore these interim responses or handle them inconsistently. #### SendStreamWriter In v3, we introduced support for buffered streaming with the addition of the `SendStreamWriter` method: ```go func (c Ctx) SendStreamWriter(streamWriter func(w *bufio.Writer)) error ``` With this new method, you can implement: - Server-Side Events (SSE) - Large file downloads - Live data streaming ```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") c.Set("Transfer-Encoding", "chunked") return c.SendStreamWriter(func(w *bufio.Writer) { for { fmt.Fprintf(w, "event: my-event\n") fmt.Fprintf(w, "data: Hello SSE\n\n") if err := w.Flush(); err != nil { log.Print("Client disconnected!") return } } }) }) ``` You can find more details about this feature in [/docs/api/ctx.md](./api/ctx.md). #### Drop In v3, we introduced support to silently terminate requests through `Drop`. ```go func (c Ctx) Drop() error ``` With this method, you can: - Block certain requests without notifying the client to mitigate DDoS attacks - Protect sensitive endpoints from unauthorized access without leaking errors. :::caution While this feature adds the ability to drop connections, it is still **highly recommended** to use additional measures (such as **firewalls**, **proxies**, etc.) to further protect your server endpoints by blocking malicious connections before the server establishes a connection. ::: ```go app.Get("/", func(c fiber.Ctx) error { if c.IP() == "192.168.1.1" { return c.Drop() } return c.SendString("Hello World!") }) ``` You can find more details about this feature in [/docs/api/ctx.md](./api/ctx.md). #### End In v3, we introduced a new method to match the Express.js API's `res.end()` method. ```go func (c Ctx) End() error ``` With this method, you can: - Stop middleware from controlling the connection after a handler further up the method chain by immediately flushing the current response and closing the connection. - Use `return c.End()` as an alternative to `return nil` ```go app.Use(func (c fiber.Ctx) error { err := c.Next() if err != nil { log.Printf("Got error: %v", err) return c.SendString(err.Error()) // Will be unsuccessful since the response ended below } return nil }) app.Get("/hello", func (c fiber.Ctx) error { query := c.Query("name", "") if query == "" { _ = c.SendString("You don't have a name?") _ = c.End() // Closes the underlying connection; errors intentionally ignored return errors.New("No name provided") } return c.SendString("Hello, " + query + "!") }) ``` --- ### 📎 Binding Fiber v3 introduces a new binding mechanism that simplifies the process of binding request data to structs. The new binding system supports binding from various sources such as URL parameters, query parameters, headers, and request bodies. This unified approach makes it easier to handle different types of request data in a consistent manner. #### New Features - Unified binding from URL parameters, query parameters, headers, and request bodies. - Support for custom binders and constraints. - Improved error handling and validation. - Support multipart file binding for `*multipart.FileHeader`, `*[]*multipart.FileHeader`, and `[]*multipart.FileHeader` field types. - Support for unified binding (`Bind().All()`) with defined precedence order: (URI -> Body -> Query -> Headers -> Cookies). A struct can customize the order for all its fields with one `binding_source` tag on any top-level field. [Learn more](./api/bind.md#all). - Support MsgPack binding for request body.
Example ```go type User struct { ID int `uri:"id"` Name string `json:"name"` Email string `json:"email"` } app.Post("/user/:id", func(c fiber.Ctx) error { var user User if err := c.Bind().Body(&user); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()}) } return c.JSON(user) }) ``` In this example, the `Bind` method is used to bind the request body to the `User` struct. The `Body` method of the `Bind` class performs the actual binding.
### 🔬 Extractors Package Fiber v3 introduces a new shared `extractors` package that consolidates value extraction utilities previously duplicated across middleware packages. This package provides a unified API for extracting values from headers, cookies, query parameters, form data, and URL parameters with built-in chain/fallback logic and security considerations. #### Key Features - **Unified API**: Single package for extracting values from headers, cookies, query parameters, form data, and URL parameters - **Chain Logic**: Built-in fallback mechanism to try multiple extraction sources in order - **Source Awareness**: Source inspection for security-sensitive operations: `Resolve` reports which source produced a value, and `Extractor.Walk` and `Extractor.Contains` inspect every extractor of a chain - **Type Safety**: Strongly typed extraction with proper error handling - **Performance**: Optimized extraction functions with minimal overhead #### Available Extractors - `FromAuthHeader(authScheme string)`: Extract from Authorization header with scheme support - `FromCookie(key string)`: Extract from HTTP cookies - `FromParam(param string)`: Extract from URL path parameters - `FromForm(param string)`: Extract from form data - `FromHeader(header string)`: Extract from custom HTTP headers - `FromQuery(param string)`: Extract from URL query parameters - `FromCustom(key string, extractor func(c fiber.Ctx) (string, error))`: Define custom extraction logic with metadata - `Chain(extractors ...Extractor)`: Chain multiple extractors with fallback logic #### Usage Example ```go import "github.com/gofiber/fiber/v3/extractors" // Extract API key from multiple sources with fallback apiKeyExtractor := extractors.Chain( extractors.FromHeader("X-API-Key"), extractors.FromQuery("api_key"), extractors.FromCookie("api_key"), ) app.Use(func(c fiber.Ctx) error { apiKey, err := apiKeyExtractor.Extract(c) if err != nil { return c.Status(401).SendString("API key required") } // Use apiKey for authentication return c.Next() }) ``` #### Migration from Middleware-Specific Extractors Middleware packages in Fiber v3 now use the shared extractors package instead of maintaining their own extraction logic. This provides: - **Code Deduplication**: Eliminates ~500+ lines of duplicated extraction code - **Consistency**: Standardized extraction behavior across all middleware - **Maintainability**: Single source of truth for extraction logic - **Security**: Unified security considerations and warnings ### 🔄 Redirect Fiber v3 enhances the redirect functionality by introducing new methods and improving existing ones. The new redirect methods provide more flexibility and control over the redirection process. #### New Methods - `Redirect().To()`: Redirects to a specific URL. - `Redirect().Route()`: Redirects to a named route. - `Redirect().Back()`: Redirects to the previous URL.
Example ```go app.Get("/old", func(c fiber.Ctx) error { return c.Redirect().To("/new") }) app.Get("/new", func(c fiber.Ctx) error { return c.SendString("Welcome to the new route!") }) ```
#### Disabling flash messages Flash messages travel in the `fiber_flash` cookie, so only a client that keeps cookies across the redirect (a browser, typically) receives them, and every request is scanned for the cookie. Deployments whose clients keep no cookies can turn the feature off with `fiber.Config{DisableFlashMessages: true}`: `Redirect().With` and `WithInput` then set no cookie, `Messages` and `OldInput` report nothing, and the scan is skipped. #### Changed behavior :::info The default redirect status code has been updated from `302 Found` to `303 See Other` to ensure more consistent behavior across different browsers. ::: ### 🌎 Client package The Gofiber client has been completely rebuilt. It includes numerous new features such as Cookiejar, request/response hooks, and more. You can take a look to [client docs](./client/rest.md) to see what's new with the client. #### Configuration improvements The v3 client centralizes common configuration on the client instance and lets you override it per request with `client.Config`. You can define base URLs, defaults (headers, cookies, path parameters, timeouts), and toggle path normalization once, while still using axios-style helpers for each call. ```go cc := client.New(). SetBaseURL("https://api.service.local"). AddHeader("Authorization", "Bearer "). SetTimeout(5 * time.Second). SetPathParam("tenant", "acme"). SetDisablePathNormalizing(true) resp, err := cc.Get("/users/:tenant/:id", client.Config{ PathParam: map[string]string{"id": "42"}, Param: map[string]string{"include": "profile"}, }) if err != nil { panic(err) } defer resp.Close() fmt.Println(resp.StatusCode(), resp.String()) ``` :::caution A path parameter value now fills exactly one path segment. A value holding `/` or `\`, a value that is exactly `.` or `..`, and an empty value are rejected with `ErrPathParamInPath`; a value substituted into the host must already be a valid host or the request fails with `ErrPathParamInHost`. Previously such a value was pasted in unchanged, so `SetPathParam("id", "../../admin")` on `/api/:id` quietly sent the request to `/admin`. Call `SetDisablePathNormalizing(true)` on the client to send a multi-segment value on purpose (for a client from `NewWithClient`, create the fasthttp client with `DisablePathNormalizing: true` as well); the setting on a single request only counts for a client that already sends paths as written. `.`, `..` and an empty value stay rejected either way, because a server resolves a dot segment and may merge slashes. ::: :::note `Debug()` logs the raw request and response after each response through the client's logger, which is Fiber's default logger unless `SetLogger` chose another one. The text is logged as it is, `Authorization` and `Cookie` headers and bodies included. A request with no referer set carries no `Referer` header; it used to carry an empty one. ::: #### Fasthttp transport integration - `client.NewWithHostClient` and `client.NewWithLBClient` allow you to plug existing `fasthttp` clients directly into Fiber while keeping retries, redirects, and hook logic consistent. - Dialer, TLS, and proxy helpers now update every host client inside a load balancer, so complex pools inherit the same configuration. - The Fiber client exposes `Do`, `DoTimeout`, `DoDeadline`, `DoRedirects`, and `CloseIdleConnections`, matching the surface area of the wrapped fasthttp transports. - Redirects are off by default (`MaxRedirects` is `0`). The `Request` methods follow them for `GET`, `HEAD` and `QUERY` requests only, while `Client.DoRedirects` follows any method. A redirect to a host that is neither the original hostname (on any port) nor one of its subdomains drops the credentials of the original request (`Authorization`, `Proxy-Authorization`, `Cookie` and related headers), and a redirect from HTTPS to HTTP fails with `client.ErrRedirectDowngrade`. ### 🧰 Generic functions Fiber v3 introduces new generic functions that provide additional utility and flexibility for developers. These functions are designed to simplify common tasks and improve code readability. #### New Generic Functions - **StoreInContext**: Stores request-scoped values in both `c.Locals()` and the request `context.Context`, so the same value can be read through middleware `FromContext` helpers and direct locals access. - **Convert**: Converts a value with a specified converter function and default value. - **Locals**: Retrieves or sets local values within a request context. - **Params**: Retrieves route parameters and can handle various types of route parameters. - **Query**: Retrieves the value of a query parameter from the request URI and can handle various types of query parameters. - **GetReqHeader**: Returns the HTTP request header specified by the field and can handle various types of header values. `fiber.Config.PassLocalsToContext` is now available to control whether `StoreInContext` also synchronizes values with request `context.Context` for Fiber-backed contexts. The default is `false` for backward compatibility. `ValueFromContext` continues reading Fiber-backed values from `c.Locals()`. #### Example
Convert ```go package main import ( "strconv" "github.com/gofiber/fiber/v3" ) func main() { app := fiber.New() app.Get("/convert", func(c fiber.Ctx) error { value, err := fiber.Convert[int](c.Query("value"), strconv.Atoi, 0) if err != nil { return c.Status(fiber.StatusBadRequest).SendString(err.Error()) } return c.JSON(value) }) app.Listen(":3000") } ``` ```sh curl "http://localhost:3000/convert?value=123" # Output: 123 curl "http://localhost:3000/convert?value=abc" # Output: "failed to convert: strconv.Atoi: parsing \"abc\": invalid syntax" ```
Locals ```go package main import ( "github.com/gofiber/fiber/v3" ) func main() { app := fiber.New() app.Use("/user/:id", func(c fiber.Ctx) error { // ask database for user // ... // set local values from database fiber.Locals[string](c, "user", "john") fiber.Locals[int](c, "age", 25) // ... return c.Next() }) app.Get("/user/*", func(c fiber.Ctx) error { // get local values name := fiber.Locals[string](c, "user") age := fiber.Locals[int](c, "age") // ... return c.JSON(fiber.Map{"name": name, "age": age}) }) app.Listen(":3000") } ``` ```sh curl "http://localhost:3000/user/5" # Output: {"name":"john","age":25} ```
Params ```go package main import ( "github.com/gofiber/fiber/v3" ) func main() { app := fiber.New() app.Get("/params/:id", func(c fiber.Ctx) error { id := fiber.Params[int](c, "id", 0) return c.JSON(id) }) app.Listen(":3000") } ``` ```sh curl "http://localhost:3000/params/123" # Output: 123 curl "http://localhost:3000/params/abc" # Output: 0 ```
Query ```go package main import ( "github.com/gofiber/fiber/v3" ) func main() { app := fiber.New() app.Get("/query", func(c fiber.Ctx) error { age := fiber.Query[int](c, "age", 0) return c.JSON(age) }) app.Listen(":3000") } ``` ```sh curl "http://localhost:3000/query?age=25" # Output: 25 curl "http://localhost:3000/query?age=abc" # Output: 0 ```
GetReqHeader ```go package main import ( "github.com/gofiber/fiber/v3" ) func main() { app := fiber.New() app.Get("/header", func(c fiber.Ctx) error { userAgent := fiber.GetReqHeader[string](c, "User-Agent", "Unknown") return c.JSON(userAgent) }) app.Listen(":3000") } ``` ```sh curl -H "User-Agent: CustomAgent" "http://localhost:3000/header" # Output: "CustomAgent" curl "http://localhost:3000/header" # Output: "Unknown" ```
### 🛠️ Utils {#utils} Fiber v3 removes the built-in `utils` directory and now imports utility helpers from the separate [`github.com/gofiber/utils/v2`](https://github.com/gofiber/utils) module. See the [migration guide](#utils-migration) for detailed replacement steps and examples. The `github.com/gofiber/utils` module also introduces new helpers like `ParseInt`, `ParseUint`, `Walk`, `ReadFile`, and `Timestamp`. ### 🧩 Services Fiber v3 introduces a new feature called Services. This feature allows developers to quickly start services that the application depends on, removing the need to manually provision things like database servers, caches, or message brokers, to name a few. #### Example
Adding a service ```go package main import ( "context" "log" "github.com/gofiber/fiber/v3" ) type myService struct { img string // ... } // Start initializes and starts the service. It implements the [fiber.Service] interface. func (s *myService) Start(ctx context.Context) error { // start the service return nil } // String returns a string representation of the service. // It is used to print a human-readable name of the service in the startup message. // It implements the [fiber.Service] interface. func (s *myService) String() string { return s.img } // State returns the current state of the service. // It implements the [fiber.Service] interface. func (s *myService) State(ctx context.Context) (string, error) { return "running", nil } // Terminate stops and removes the service. It implements the [fiber.Service] interface. func (s *myService) Terminate(ctx context.Context) error { // stop the service return nil } func main() { cfg := &fiber.Config{} cfg.Services = append(cfg.Services, &myService{img: "postgres:latest"}) cfg.Services = append(cfg.Services, &myService{img: "redis:latest"}) app := fiber.New(*cfg) // ... log.Fatal(app.Listen(":3000")) } ```
Output ```sh $ go run . -v _______ __ / ____(_) /_ ___ _____ / /_ / / __ \/ _ \/ ___/ / __/ / / /_/ / __/ / /_/ /_/_.___/\___/_/ v3.0.0 -------------------------------------------------- INFO Server started on: http://127.0.0.1:3000 (bound on host 0.0.0.0 and port 3000) INFO Services: 2 INFO 🧩 [ RUNNING ] postgres:latest INFO 🧩 [ RUNNING ] redis:latest INFO Total handlers count: 2 INFO Prefork: Disabled INFO PID: 12279 INFO Total process count: 1 ```
See [Services](./api/services.md) for the configuration and the startup and shutdown lifecycle. ### 📃 Log The `log.AllLogger[T]` interface (package `github.com/gofiber/fiber/v3/log`) now has a generic type parameter `T` and a method called `Logger`. This method returns the underlying logger instance. This is useful when you register a custom logger with `log.SetLogger` and still want to access the underlying logger with its concrete type. You can find more details about this feature in [/docs/api/log.md](./api/log.md#logger). `logger.Config` now supports a new field called `ForceColors`. This field allows you to force the logger to always use colors, even if the output is not a terminal. This is useful when you want to ensure that the logs are always colored, regardless of the output destination. ```go package main import "github.com/gofiber/fiber/v3/middleware/logger" app.Use(logger.New(logger.Config{ ForceColors: true, })) ``` ### 📦 Storage Interface The storage interface has been updated to include new subset of methods with `WithContext` suffix. These methods allow you to pass a context to the storage operations, enabling better control over timeouts and cancellation if needed. This is particularly useful when storage implementations used outside of the Fiber core, such as in background jobs or long-running tasks. **New Methods Signatures:** ```go // GetWithContext gets the value for the given key with a context. // `nil, nil` is returned when the key does not exist GetWithContext(ctx context.Context, key string) ([]byte, error) // SetWithContext stores the given value for the given key // with an expiration value, 0 means no expiration. // Empty key or value will be ignored without an error. SetWithContext(ctx context.Context, key string, val []byte, exp time.Duration) error // DeleteWithContext deletes the value for the given key with a context. // It returns no error if the storage does not contain the key, DeleteWithContext(ctx context.Context, key string) error // ResetWithContext resets the storage and deletes all keys with a context. ResetWithContext(ctx context.Context) error ``` ### 🧬 Middlewares #### Important Change for Accessing Middleware Data In Fiber v3, many middlewares that previously set values in `c.Locals()` using string keys (e.g., `c.Locals("requestid")`) have been updated. To align with Go's context best practices and prevent key collisions, these middlewares now store their specific data in the request's context using unexported keys of custom types. This means that directly accessing these values via `c.Locals("some_string_key")` will no longer work for such middleware-provided data. **How to Access Middleware Data in v3:** Each affected middleware now provides dedicated exported functions to retrieve its specific data from the context. You should use these functions instead of relying on string-based lookups in `c.Locals()`. Examples include: - `requestid.FromContext(c)` - `csrf.TokenFromContext(c)` - `csrf.HandlerFromContext(c)` - `session.FromContext(c)` - `basicauth.UsernameFromContext(c)` - `keyauth.TokenFromContext(c)` When used with the Logger middleware, the recommended approach is to use the `CustomTags` feature of the logger, which allows you to call these specific `FromContext` functions. See the [Logger](#logger) section for more details. #### Adaptor The adaptor middleware has been significantly optimized for performance and efficiency. Key improvements include reduced response times, lower memory usage, and fewer memory allocations. These changes make the middleware more reliable and capable of handling higher loads effectively. Enhancements include the introduction of a `sync.Pool` for managing `fasthttp.RequestCtx` instances and better HTTP request and response handling between net/http and fasthttp contexts. Incoming body sizes now respect the Fiber app's configured `BodyLimit` (falling back to the default when unset) when running Fiber from `net/http` through the adaptor, returning `413 Request Entity Too Large` for oversized payloads. `FiberHandler` and `FiberHandlerFunc` run the handler on a fresh app, so they always apply the default limit; pass your own app to `FiberApp` to configure it. The adaptor also propagates the request's protocol version, normalized to Fiber's convention (`HTTP/2.0` → `HTTP/2`, `HTTP/3.0` → `HTTP/3`), so `c.Protocol()` reports the real version instead of always `HTTP/1.1`. Interim responses such as `SendEarlyHints`' `103` are silently skipped through the adaptor — there is no client connection to write them to — while the `Link` headers still reach the final response. An adapted `net/http` handler, middleware or `ConvertRequest` request is built from the path the router matched, `c.Path()`, followed by the query as the client sent it, rather than from the request line as it arrived, so a `net/http` guard on `r.URL.Path` sees the `/admin/x` that `/a/../admin/x` was routed as. A byte a path may not carry raw is written as its escape, and the request keeps the host Fiber saw, including the one an absolute-form request line names. `HTTPHandler`, `HTTPHandlerFunc` and `HTTPHandlerWithContext` answer `404 Not Found` for a routed path with an empty segment or an escaped slash, such as `//admin/x` or `/public/..%2Fadmin/x`, which `net/http` would otherwise decode and clean into a path no Fiber middleware ran for, and for one with a backslash or a dot segment carrying parameters (`..;`), which an upstream behind a handler such as `httputil.ReverseProxy` may resolve into another path; a handler that must serve such names can be wrapped with `HTTPMiddleware` instead. `HTTPMiddleware` answers `400 Bad Request`, and `ConvertRequest` returns `fiber.ErrBadRequest`, for a routed path fasthttp would read as an authority rather than a path: one that begins with `//` and holds `://`, or one beginning with `//` on a request without a `Host` header. A routed path holding an escape of an unreserved character, which only a stray `%` can forge, is `404 Not Found` from the handler adapters and `400 Bad Request` from `HTTPMiddleware` and `ConvertRequest`, since `URL.Path` would read a forged `%2e%2e` as `..`. `FiberHandler`, `FiberHandlerFunc` and `FiberApp` route `r.RequestURI` as received, so a raw `/über` is routed as a Fiber server routes it, and when `r.URL` no longer matches that line, the request line `net/http` would write for `r`: a rewritten `r.URL`, as `http.StripPrefix` produces, a request built in code without a `RequestURI`, and the authority of a `CONNECT` request in the authority form. See the adaptor's [notes and limitations](./middleware/adaptor.md#notes-and-limitations). | Payload Size | Metric | V2 | V3 | Percent Change | | ------------ | -------------- | ------------ | ----------- | -------------- | | 100KB | Execution Time | 1056 ns/op | 588.6 ns/op | -44.25% | | | Memory Usage | 2644 B/op | 254 B/op | -90.39% | | | Allocations | 16 allocs/op | 5 allocs/op | -68.75% | | 500KB | Execution Time | 1061 ns/op | 562.9 ns/op | -46.94% | | | Memory Usage | 2644 B/op | 248 B/op | -90.62% | | | Allocations | 16 allocs/op | 5 allocs/op | -68.75% | | 1MB | Execution Time | 1080 ns/op | 629.7 ns/op | -41.68% | | | Memory Usage | 2646 B/op | 267 B/op | -89.91% | | | Allocations | 16 allocs/op | 5 allocs/op | -68.75% | | 5MB | Execution Time | 1093 ns/op | 540.3 ns/op | -50.58% | | | Memory Usage | 2654 B/op | 254 B/op | -90.43% | | | Allocations | 16 allocs/op | 5 allocs/op | -68.75% | | 10MB | Execution Time | 1044 ns/op | 533.1 ns/op | -48.94% | | | Memory Usage | 2665 B/op | 258 B/op | -90.32% | | | Allocations | 16 allocs/op | 5 allocs/op | -68.75% | | 25MB | Execution Time | 1069 ns/op | 540.7 ns/op | -49.42% | | | Memory Usage | 2706 B/op | 289 B/op | -89.32% | | | Allocations | 16 allocs/op | 5 allocs/op | -68.75% | | 50MB | Execution Time | 1137 ns/op | 554.6 ns/op | -51.21% | | | Memory Usage | 2734 B/op | 298 B/op | -89.10% | | | Allocations | 16 allocs/op | 5 allocs/op | -68.75% | #### BasicAuth The BasicAuth middleware now validates the `Authorization` header more rigorously and sets security-focused response headers. Passwords must be provided in **hashed** form (e.g. SHA-256 or bcrypt) rather than plaintext. The default challenge includes the `charset="UTF-8"` parameter and disables caching. Responses also set a `Vary: Authorization` header to prevent caching based on credentials. Passwords are no longer stored in the request context. A `Charset` option controls the value used in the challenge header. A request that carries the `Authorization` field on more than one line is refused with `400 Bad Request`: the field is defined as a single value, and reading either line would let the other decide what the request means. A new `HeaderLimit` option restricts the maximum length of the `Authorization` header (default: `8192` bytes). The `Authorizer` function now receives the current `fiber.Ctx` as a third argument, allowing credential checks to incorporate request context. #### Cache We are excited to introduce a new option in our caching middleware: Cache Invalidator. This feature provides greater control over cache management, allowing you to define custom conditions for invalidating cache entries. The middleware now emits `Cache-Control` headers by default via the new `DisableCacheControl` flag, increases the default `Expiration` from `1 minute` to `5 minutes`, and applies a new `MaxBytes` limit of `1 MB` (previously unlimited). That limit is the default of `cache.New()` called without a `Config`: a `Config` that leaves `MaxBytes` unset still stores bodies without a limit. The middleware also caches `308 Permanent Redirect` responses and honors the request directives `max-age`, `min-fresh`, `max-stale` and `only-if-cached`, as well as `Pragma: no-cache`. A hit served stale carries a `Warning` header. See [Request directives](./middleware/cache.md#request-directives). Cache hits replay the stored `Vary` header even when `StoreResponseHeaders` is off, and a hit on an entry keyed by a cookie or credential (`Vary: Cookie`, `KeyCookies`, or `KeyHeaders` naming `Cookie` or `Authorization`) is sent with `Cache-Control: private` rather than `public`, so a shared cache in front of the app does not store one user's response for the next. Additionally, the caching middleware has been optimized to avoid caching non-cacheable status codes, as defined by the [HTTP standards](https://datatracker.ietf.org/doc/html/rfc7231#section-6.1). This improvement enhances cache accuracy and reduces unnecessary cache storage usage. Cached responses now include an RFC-compliant Age header, providing a standardized indication of how long a response has been stored in cache since it was originally generated. This enhancement improves HTTP compliance and facilitates better client-side caching strategies. Cache keys are now redacted in logs and error messages by default, and a `DisableValueRedaction` boolean (default `false`) lets you opt out when you need the raw value for troubleshooting. The default cache key strategy was also hardened. Instead of path-only behavior, keys now use structured request dimensions: method partitioning, scheme and host, path, canonical query string, and selected representation headers (`Accept`, `Accept-Encoding`, `Accept-Language`). This avoids collisions such as `/items?id=1` vs `/items?id=2`, or the same path on two hosts served by one app, while keeping key generation deterministic. New config fields were added for explicit control: `DisableQueryKeys`, `KeyHeaders`, `KeyCookies`, and `DisableVaryHeaders`. As a security/performance default, request body/form values are not part of the default cache key. Cache handling is limited to `GET` and `HEAD` requests by default, configurable via the `Methods` field. :::note The deprecated `Store` and `Key` options have been removed in v3. Use `Storage` and `KeyGenerator` instead. ::: #### ResponseTime A new response time middleware measures how long each request takes to process and adds the duration to the response headers. By default it writes the elapsed time to `X-Response-Time`, and you can change the header name. A `Next` hook lets you skip endpoints such as health checks. #### CORS We've made some changes to the CORS middleware to improve its functionality and flexibility. Here's what's new: ##### New Struct Fields - `Config.AllowPrivateNetwork`: This new field is a boolean that allows you to control whether private networks are allowed. This is related to the [Private Network Access (PNA)](https://wicg.github.io/private-network-access/) specification from the [Web Incubator Community Group (WICG)](https://wicg.io/). When set to `true`, the CORS middleware will allow CORS preflight requests from private networks and respond with the `Access-Control-Allow-Private-Network: true` header. This could be useful in development environments or specific use cases, but should be done with caution due to potential security risks. ##### Updated Struct Fields We've updated several fields from a single string (containing comma-separated values) to slices, allowing for more explicit declaration of multiple values. Here are the updated fields: - `Config.AllowOrigins`: Now accepts a slice of strings, each representing an allowed origin. - `Config.AllowMethods`: Now accepts a slice of strings, each representing an allowed method. - `Config.AllowHeaders`: Now accepts a slice of strings, each representing an allowed header. - `Config.ExposeHeaders`: Now accepts a slice of strings, each representing an exposed header. Additionally, the panic for an invalid `AllowOrigins` entry redacts the origin by default, and a `DisableValueRedaction` flag (default `false`) lets you reveal it when necessary. #### Compression - Added support for `zstd` compression alongside `gzip`, `deflate`, and `brotli`. - Strong `ETag` values are now recomputed for compressed payloads so validators remain accurate. - Compression is bypassed for responses that already specify `Content-Encoding`, for range requests or `206` statuses, and when either side sends `Cache-Control: no-transform`. - `HEAD` requests still negotiate compression so `Content-Encoding`, `Content-Length`, `ETag`, and `Vary` match a corresponding `GET`, but the body is omitted. - `Vary: Accept-Encoding` is merged into responses even when compression is skipped, preventing caches from mixing encoded and unencoded variants. - Decoding compressed request bodies now enforces the app `BodyLimit` through fasthttp `WithLimit` helpers, including when the compression middleware is active. - Multipart form parsing now enforces the app `BodyLimit` by using fasthttp `MultipartFormWithLimit`. #### CSRF The `Expiration` field in the CSRF middleware configuration has been renamed to `IdleTimeout` to better describe its functionality. Additionally, the default value has been reduced from 1 hour to 30 minutes. CSRF now redacts tokens and storage keys by default and exposes a `DisableValueRedaction` toggle (default `false`) if you must surface those values in diagnostics. The CSRF middleware now validates the [`Sec-Fetch-Site`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-Fetch-Site) header for unsafe HTTP methods. When present, requests with invalid `Sec-Fetch-Site` values (not one of "same-origin", "none", "same-site", or "cross-site") are rejected with `ErrFetchSiteInvalid`. Valid or absent headers proceed to standard origin and token validation checks, providing an early gate to catch malformed requests while maintaining compatibility with legitimate cross-site traffic. `csrf.NewCrossOriginProtection` adds a tokenless alternative that follows Go's [`net/http.CrossOriginProtection`](https://pkg.go.dev/net/http#CrossOriginProtection). It rejects unsafe cross-origin browser requests by their `Sec-Fetch-Site` and `Origin` headers and needs no cookie, token, or storage. See [Cross-Origin Protection Without Tokens](./middleware/csrf.md#cross-origin-protection-without-tokens) for the rules and the differences from `net/http`. #### Idempotency Idempotency middleware now redacts keys by default and offers a `DisableValueRedaction` configuration flag (default `false`) to expose them when debugging. #### EncryptCookie - Added support for specifying key length when using `encryptcookie.GenerateKey(length)`. Keys must be base64-encoded and may be 16, 24, or 32 bytes when decoded, supporting AES-128, AES-192, and AES-256 (default). - Custom encryptor and decryptor callbacks now receive the cookie name. The default AES-GCM helpers bind it as additional authenticated data (AAD) so ciphertext cannot be replayed under a different cookie. - **Breaking change:** Custom encryptor/decryptor hooks now accept the cookie name as their first argument. Update overrides like: ```go // Before Encryptor func(value, key string) (string, error) Decryptor func(value, key string) (string, error) // After Encryptor func(name, value, key string) (string, error) Decryptor func(name, value, key string) (string, error) ``` #### Favicon The favicon middleware now caps cached favicon assets with a configurable `MaxBytes` limit (default `1 MiB`) and uses a limited reader to guard against oversized files when loading from disk. #### EnvVar The `ExcludeVars` field has been removed from the EnvVar middleware configuration. When upgrading, remove any references to this field and explicitly list the variables you wish to expose using `ExportVars`. #### Filesystem The filesystem middleware was removed to reduce confusion with the static middleware. The static middleware now covers the functionality of both. Review the [static middleware](./middleware/static.md) docs or the [migration guide](#-migration-guide) for the updated usage. #### Healthcheck The healthcheck middleware has been simplified into a single generic probe handler. No endpoints are registered automatically. Register the middleware on each route you need—using the conventional paths `healthcheck.LivenessEndpoint` (`/livez`), `healthcheck.ReadinessEndpoint` (`/readyz`), or `healthcheck.StartupEndpoint` (`/startupz`)—and optionally supply a `Probe` function to determine the service's health. This approach lets you expose any number of health check routes. Refer to the [healthcheck middleware migration guide](./middleware/healthcheck.md) or the [general migration guide](#-migration-guide) to review the changes. #### KeyAuth The keyauth middleware was updated to introduce a configurable `Realm` field for the `WWW-Authenticate` header. The old string-based `KeyLookup` configuration has been replaced with an `Extractor` field. Use the helpers of the shared [`extractors`](#-extractors-package) package, such as `extractors.FromHeader`, `extractors.FromAuthHeader`, or `extractors.FromCookie`, to define where the key should be retrieved from. Multiple sources can be combined with `extractors.Chain`. See the migration guide below. New `Challenge`, `Error`, `ErrorDescription`, `ErrorURI`, and `Scope` fields allow customizing the `WWW-Authenticate` header, returning Bearer error details, and specifying required scopes. `ErrorURI` values are validated as absolute, a default `ApiKey` challenge is emitted when using non-Authorization extractors, Bearer `error` values are validated, credentials must conform to RFC 7235 `token68` syntax, and `scope` values are checked against RFC 6750's `scope-token` format. The header is also emitted only after the status code is finalized. #### Logger New helper function called `LoggerToWriter` has been added to the logger middleware. This function allows you to use 3rd party loggers such as `logrus` or `zap` with the Fiber logger middleware without an extra adapter. For example, you can use `zap` with Fiber logger middleware like this: Logger configuration now uses `Stream` instead of `Output` for the destination writer, so update your logger middleware configuration when migrating to v3. Custom logger integrations should update any `LoggerFunc` implementations to the new signature that receives a pointer to the middleware config: `func(c fiber.Ctx, data *logger.Data, cfg *logger.Config) error`.
Example ```go package main import ( "github.com/gofiber/contrib/fiberzap/v2" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/log" "github.com/gofiber/fiber/v3/middleware/logger" ) func main() { // Create a new Fiber instance app := fiber.New() // Create a new zap logger which is compatible with Fiber AllLogger interface zap := fiberzap.NewLogger(fiberzap.LoggerConfig{ ExtraKeys: []string{"request_id"}, }) // Use the logger middleware with zerolog logger app.Use(logger.New(logger.Config{ Stream: logger.LoggerToWriter(zap, log.LevelDebug), })) // Define a route app.Get("/", func(c fiber.Ctx) error { return c.SendString("Hello, World!") }) // Start server on http://localhost:3000 app.Listen(":3000") } ```
:::note The deprecated `TagHeader` constant was removed. Use `TagReqHeader` when you need to log request headers. ::: ##### Logging Middleware Values (e.g., Request ID) In Fiber v3, middleware (like `requestid`) now stores values in the request context using unexported keys of custom types. This aligns with Go's context best practices to prevent key collisions between packages. As a result, directly accessing these values using string keys with `c.Locals("your_key")` or in the logger format string with `${locals:your_key}` (e.g., `${locals:requestid}`) will no longer work for values set by such middleware. **Recommended Solution: `CustomTags`** The cleanest and most maintainable way to include these middleware-specific values in your logs is by using the `CustomTags` option in the logger middleware configuration. This allows you to define a custom function to retrieve the value correctly from the context.
Example: Logging Request ID with CustomTags ```go package main import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/logger" "github.com/gofiber/fiber/v3/middleware/requestid" ) func main() { app := fiber.New() // Ensure requestid middleware is used before the logger app.Use(requestid.New()) app.Use(logger.New(logger.Config{ CustomTags: map[string]logger.LogFunc{ "requestid": func(output logger.Buffer, c fiber.Ctx, data *logger.Data, extraParam string) (int, error) { // Retrieve the request ID using the middleware's specific function return output.WriteString(requestid.FromContext(c)) }, }, // Use the custom tag in your format string Format: "[${time}] ${ip} - ${requestid} - ${status} ${method} ${path}\n", })) app.Get("/", func(c fiber.Ctx) error { return c.SendString("Hello, World!") }) app.Listen(":3000") } ```
**Alternative: Manually Copying to `Locals`** If you have existing logging patterns that rely on `c.Locals` or prefer to manage these values in `Locals` for other reasons, you can manually copy the value from the context to `c.Locals` in a preceding middleware:
Example: Manually setting requestid in Locals ```go app.Use(requestid.New()) // Request ID middleware app.Use(func(c fiber.Ctx) error { // Manually copy the request ID to Locals c.Locals("requestid", requestid.FromContext(c)) return c.Next() }) app.Use(logger.New(logger.Config{ // Now ${locals:requestid} can be used, but CustomTags is generally preferred Format: "[${time}] ${ip} - ${locals:requestid} - ${status} ${method} ${path}\n", })) ```
Both approaches ensure your logger can access these values while respecting Go's context practices. The same template/tag mechanism is also available for application logs emitted through `log.WithContext`. This keeps request logging and handler logging consistent without hard-coding middleware-specific values into the `log` package: ```go app.Use(requestid.New()) // The requestid middleware automatically registers the ${requestid} tag. log.MustSetContextTemplate(log.ContextConfig{Format: "[${requestid}] "}) app.Get("/", func(c fiber.Ctx) error { // Pass c so middleware values stored on Fiber's request context can be read. log.WithContext(c).Info("handling request") return c.SendString("OK") }) ``` `SetContextTemplate` configures Fiber's built-in default logger. Custom loggers registered with `log.SetLogger` keep control over their own `WithContext` behavior. ##### Breaking change: `WithContext(ctx any)` The signature of `log.WithContext`, `baseLogger.WithContext`, and `AllLogger[T].WithContext` changed from `WithContext(ctx context.Context)` to `WithContext(ctx any)`. The wider parameter type is what allows the same call site to receive `fiber.Ctx`, `*fasthttp.RequestCtx`, or a standard `context.Context` and still resolve middleware-stored values via `Value` / `UserValue`. Adapter packages that implement `AllLogger[T]` directly (for example community Zap, Zerolog, or Logrus integrations) must update their `WithContext` method signature accordingly. Direct call sites that already pass a `context.Context` or a `fiber.Ctx` continue to compile. ##### New API surface The `log` and `middleware/logger` packages now expose the following exported symbols: - `log.SetContextTemplate(ContextConfig) error` / `log.MustSetContextTemplate(ContextConfig)` — configure the active context template. - `log.RegisterContextTag(name string, fn ContextTagFunc) error` / `log.MustRegisterContextTag` — register a global context tag. - `log.ContextConfig`, `log.ContextData`, `log.ContextTagFunc`, `log.Buffer` — supporting types. - `log.DefaultFormat`, `log.RequestIDFormat`, `log.KeyValueFormat`, `log.TagContextValue` — built-in format and tag constants. - `logger.RegisterTag(name string, fn LogFunc) error` / `logger.MustRegisterTag` — register a global access-log tag (in addition to per-instance `Config.CustomTags`). - `logger.RegisterContextTag(name string, extract func(ctx any) string)` — convenience helper that registers a string-valued tag in **both** the access-log registry and the `log.RegisterContextTag` registry, so middleware authors do not have to maintain two parallel renderers. - `logger.ErrUnknownTag` (sentinel) and `logger.UnknownTagError` (typed) — replace the older `ErrTemplateParameterMissing` sentinel that was never exported in a stable release. `New(Config{})` panics with `*UnknownTagError` when the format references a tag that has no registered renderer; `errors.As` retrieves the offending tag name. The `requestid`, `basicauth`, `keyauth`, `csrf`, and `session` middlewares automatically register their respective context tags (`${requestid}`, `${request-id}`, `${username}`, `${api-key}`, `${csrf-token}`, `${session-id}`) on first `New(...)`. Empty stubs for the same names are pre-registered at package init, so a logger format that references one of them compiles even when the corresponding middleware has not been initialized. For `log.WithContext`, later registration rebuilds the active context template. For `middleware/logger` access logs, construct the producing middleware (or call `logger.RegisterTag`) before `logger.New(...)`; existing logger instances keep the function chain compiled at construction time and do not retroactively pick up later registrations. The `Skip` is a function to determine if logging is skipped or written to `Stream`.
Example Usage ```go app.Use(logger.New(logger.Config{ Skip: func(c fiber.Ctx) bool { // Skip logging HTTP 200 requests return c.Response().StatusCode() == fiber.StatusOK }, })) ``` ```go app.Use(logger.New(logger.Config{ Skip: func(c fiber.Ctx) bool { // Only log errors, similar to an error.log return c.Response().StatusCode() < 400 }, })) ```
##### Predefined Formats Logger provides predefined formats that you can use by name or directly by specifying the format string.
Example Usage ```go app.Use(logger.New(logger.Config{ Format: logger.CombinedFormat, })) ``` See more in [Logger](./middleware/logger.md#predefined-formats)
#### Limiter The limiter middleware uses a new Fixed Window Rate Limiter implementation. Custom limiter algorithms should now implement the updated `limiter.Handler` interface, whose `New` method receives a pointer to the active config: `New(cfg *limiter.Config) fiber.Handler`. Limiter now redacts request keys in error paths by default. A new `DisableValueRedaction` boolean (default `false`) lets you reveal the limiter key, as the storage sees it, if diagnostics require it. :::note Deprecated fields `Duration`, `Store`, and `Key` have been removed in v3. Use `Expiration`, `Storage`, and `KeyGenerator` instead. ::: #### Monitor Monitor middleware is migrated to the [Contrib package](https://github.com/gofiber/contrib/tree/main/monitor) with [PR #1172](https://github.com/gofiber/contrib/pull/1172). #### Proxy The proxy middleware has been updated to improve consistency with Go naming conventions. The `TlsConfig` field in the configuration struct has been renamed to `TLSConfig`. Additionally, the `WithTlsConfig` method has been removed; you should now configure TLS directly via the `TLSConfig` property within the `Config` struct. The new `KeepConnectionHeader` option (default `false`) drops the `Connection` header unless explicitly enabled to retain it. `proxy.Balancer` now accepts an optional variadic configuration: call `proxy.Balancer()` to use defaults or continue passing a `proxy.Config` value as before. The proxy is secure by default. Upstreams that resolve to loopback, private, link-local, multicast, unspecified or CGNAT addresses (IPv6 transition forms included) are refused, and so are schemes other than `http` and `https` and HTTPS-to-HTTP redirects. Hop-by-hop headers are stripped, and `Balancer`, `Forward`, `DomainForward` and `BalancerForward` overwrite `X-Real-IP` with `c.IP()`; with `Do`, `DoTimeout`, `DoDeadline` and `DoRedirects` you set it yourself. To proxy to an internal host, relax the policy with `proxy.WithSecurityPolicy` (or `Config.SecurityPolicy` for `Balancer`), for example `proxy.SecurityPolicy{AllowPrivateIPs: true}`, before you build `DomainForward` or `BalancerForward`: they check their upstreams when the handler is built and panic on a rejected one. See [Security](./middleware/proxy.md#security). `Balancer`, `DomainForward` and `BalancerForward` forward the path the router matched, `c.Path()`, followed by the query as the client sent it, instead of the request line as it arrived. Spellings such as `/public/..%2Fadmin/secret` and `//admin/secret`, which fasthttp's normalization of the raw request line turned into `/admin/secret` on the way to the upstream, are answered with `400 Bad Request`, since an upstream that decodes `%2F` or merges `//` before it matches routes would still serve a path no middleware matched; the new `SecurityPolicy.AllowAmbiguousSlashes` forwards them exactly as the router matched them, for an upstream that keeps both. A path holding a backslash, a dot segment carrying parameters (`..;`), which servlet containers resolve, or an escape of an unreserved character, which only a stray `%` can forge, is answered with `400` regardless. A path with dot segments is forwarded resolved, and a byte a path may not carry raw is forwarded as its escape. Every entry in `Config.Servers` must be a scheme and host only; an entry with a path, userinfo, query or fragment panics at startup with `ErrUpstreamNotOrigin`. `Balancer` also answers `400 Bad Request` for a request whose `Host` header carries userinfo and for a routed target fasthttp would read as an authority rather than a path (one that begins with `//` and holds `://`, or one beginning with `//` on a request without a `Host` header). A path override without a leading slash is forwarded rooted. Every `*fasthttp.HostClient` in a custom `Client` gets `DisablePathNormalizing` set, and behind any other `BalancingClient` a target that normalization would change is answered with `400`. Path normalization is also disabled on the default client, on a client registered with `WithClient` and on every host client a per-call client creates once the proxy has it. See [Request target](./middleware/proxy.md#request-target). #### Recover The Recover middleware allows customizing the error it returns. Set a `PanicHandler` in its `Config` to change the default behavior. #### Session The Session middleware has undergone key changes in v3 to improve functionality and flexibility. While v2 methods remain available for backward compatibility, we now recommend using the new middleware handler for session management. ##### Key Updates The session middleware has undergone significant improvements in v3, focusing on type safety, flexibility, and better developer experience. ##### Key Changes - **Extractor Pattern**: The string-based `KeyLookup` configuration has been replaced with a more flexible and type-safe `Extractor` function pattern. - **New Middleware Handler**: The `New` function now returns a middleware handler instead of a `*Store`. To access the session store, use the `Store` method on the middleware, or opt for `NewStore` or `NewWithStore` for custom store integration. - **Manual Session Release**: Session instances are no longer automatically released after being saved. To ensure proper lifecycle management, you must manually call `sess.Release()`. - **Idle Timeout**: The `Expiration` field has been replaced with `IdleTimeout`, which handles session inactivity. If the session is idle for the specified duration, it will expire. The idle timeout is updated when the session is saved. If you are using the middleware handler, the idle timeout will be updated automatically. - **Absolute Timeout**: The `AbsoluteTimeout` field has been added. If you need to set an absolute session timeout, you can use this field to define the duration. The session will expire after the specified duration, regardless of activity. - **Default KeyGenerator**: Changed from `utils.UUIDv4` to `utils.SecureToken`, producing base64-encoded tokens instead of UUID format. - **Context-Aware Lifecycle Methods**: The `DestroyWithContext`, `RegenerateWithContext`, and `ResetWithContext` methods (on both `Session` and `Middleware`), and `SaveWithContext` (on `Session`, since the middleware saves the session itself), accept a `context.Context` to propagate cancellation and deadlines to the underlying storage I/O, mirroring the existing `Storage` and `SharedState` `WithContext` convention. The non-context variants delegate to these. A nil context is treated as `context.Background()`. - **Session ID Write-Back**: When a request already carries a session ID, the ID is written back only through the extractor that supplied it, and only when that extractor is a cookie or a non-Authorization header. An ID from a read-only source (Authorization header, Query, Form, Param, or Custom) is never copied into a cookie or header, which prevents session fixation. A request without an ID gets a server-generated one through every cookie and non-Authorization header extractor of the chain. When the middleware issues a new ID for a request that carried one, because of `Regenerate()`, `Reset()`, or an ID the storage does not know, the new ID goes back through the same cookie or header, or through every such extractor of the chain when the old ID came from a read-only source. An extractor error other than `extractors.ErrNotFound` is passed to `ErrorHandler` instead of being treated as a new visitor. For more details on these changes and migration instructions, check the [Session Middleware Migration Guide](./middleware/session.md#migration-guide). #### SSE Fiber now includes an [SSE middleware](./middleware/sse.md) for Server-Sent Events. It handles native `SendStreamWriter` setup, SSE response headers, event formatting, flushing, heartbeat comments, and disconnect detection through flush errors while leaving application-level hubs, topics, replay stores, and pub/sub bridges to user code or recipes. #### Timeout The timeout middleware is now configurable. A new `Config` struct allows customizing the timeout duration, defining a handler that runs when a timeout occurs, and specifying errors to treat as timeouts. The `New` function now accepts a `Config` value instead of a duration. **Behavioral changes:** - **Status**: The default response for a timed-out handler is `503 Service Unavailable`, not `408 Request Timeout`. The request arrived complete and the handler is what is slow, whereas `408` means the server did not receive a complete request in time ([RFC 9110, Section 15.5.9](https://www.rfc-editor.org/rfc/rfc9110#section-15.5.9)) and tells the client it may send the request again, while the timed-out handler can still be running. Return `fiber.ErrRequestTimeout` (or `fiber.ErrGatewayTimeout` for a handler that waits on an upstream) from `OnTimeout` to keep or choose another status. - **Immediate return on timeout**: The middleware now returns immediately when a timeout occurs, without waiting for the handler to finish. This is achieved through the new **Abandon mechanism** which marks the context as abandoned so it won't be returned to the pool while the handler is still running. - **Context propagation**: The timeout context is properly propagated to the handler. Handlers can detect timeouts by listening on `c.Context().Done()` and return early. - **Panic handling**: Panics in the handler are caught and converted to `500 Internal Server Error` responses. - **Race-free design**: The implementation uses fasthttp's `TimeoutErrorWithCode` combined with Fiber's Abandon mechanism to ensure complete race-freedom between the middleware, handler goroutine, and context pooling. **New Ctx methods for the Abandon mechanism:** - `Abandon()`: Marks the context as abandoned - `IsAbandoned()`: Returns true if the context was abandoned - `ForceRelease()`: Releases an abandoned context back to the pool (for advanced use) - `ScheduleReclaim(handlerDone, cancel)`: Abandons the context and returns it to the pool by itself once the handler goroutine has finished and Fiber has released the context **Migration:** Replace calls like `timeout.New(handler, 2*time.Second)` with `timeout.New(handler, timeout.Config{Timeout: 2 * time.Second})`. ### 🔌 Addons In v3, Fiber introduced Addons. Addons are additional useful packages that can be used in Fiber. #### Retry The Retry addon is a new addon that implements a retry mechanism for unsuccessful network operations. It uses an exponential backoff algorithm with jitter. It calls the function multiple times and tries to make it successful. If all calls are failed, then, it returns an error. It adds a jitter at each retry step because adding a jitter is a way to break synchronization across the client and avoid collision. A field of `retry.ExponentialBackoff` left at zero takes its default, also on a struct literal, so even the zero value calls the function and waits between the attempts as the default config does. A literal that sets only `MaxRetryCount` used to retry at once; it now waits too.
Example ```go package main import ( "fmt" "github.com/gofiber/fiber/v3/addon/retry" "github.com/gofiber/fiber/v3/client" ) func main() { expBackoff := retry.NewExponentialBackoff(retry.Config{}) // Local variables that will be used inside of Retry var resp *client.Response var err error // Retry a network request and return an error to signify to try again err = expBackoff.Retry(func() error { client := client.New() resp, err = client.Get("https://gofiber.io") if err != nil { return fmt.Errorf("GET gofiber.io failed: %w", err) } if resp.StatusCode() != 200 { return fmt.Errorf("GET gofiber.io did not return OK 200") } return nil }) // If all retries failed, panic if err != nil { panic(err) } fmt.Printf("GET gofiber.io succeeded with status code %d\n", resp.StatusCode()) } ```
### 📋 Migration guide To streamline upgrades between Fiber versions, the Fiber CLI ships with a `migrate` command: ```bash go install github.com/gofiber/cli/fiber@latest fiber migrate --to v3 ``` #### Options - `-t, --to string` migrate to a specific version, e.g. `v3.0.0` - `-f, --force` force migration even if already on that version - `-s, --skip_go_mod` skip running `go mod tidy`, `go mod download`, and `go mod vendor` #### Changes Overview - [🚀 App](#-app-1) - [🎣 Hooks](#-hooks-1) - [🚀 Listen](#-listen-1) - [🗺 Router](#-router-1) - [🧠 Context](#-context-1) - [📎 Binding (was Parser)](#-parser) - [🔄 Redirect](#-redirect-1) - [🧾 Log](#-log-1) - [🌎 Client package](#-client-package-1) - [🛠️ Utils](#utils-migration) - [🧬 Middlewares](#-middlewares-1) - [Important Change for Accessing Middleware Data](#important-change-for-accessing-middleware-data) - [BasicAuth](#basicauth-1) - [Cache](#cache-1) - [CORS](#cors-1) - [CSRF](#csrf-1) - [Filesystem](#filesystem-1) - [EnvVar](#envvar-1) - [Favicon](#favicon) - [Healthcheck](#healthcheck-1) - [Monitor](#monitor-1) - [Proxy](#proxy-1) - [Session](#session-1) #### 🚀 App ##### Static Since we've removed `app.Static()`, you need to move methods to static middleware like the example below: ```go // Before app.Static("/", "./public") app.Static("/prefix", "./public") app.Static("/prefix", "./public", Static{ Index: "index.htm", }) app.Static("*", "./public/index.html") ``` ```go // After app.Get("/*", static.New("./public")) app.Get("/prefix*", static.New("./public")) app.Get("/prefix*", static.New("./public", static.Config{ IndexNames: []string{"index.htm", "index.html"}, })) app.Get("*", static.New("./public/index.html")) ``` :::caution You have to put `*` to the end of the route if you don't define static route with `app.Use`. Under a `/prefix*` route such as `/static*`, only `/static` and paths below `/static/` are served; `/static-v2/app.js` or `/staticapp.js` are no longer served. ::: The static middleware opens the name the router matched and does not percent-decode it a second time. Escapes the router keeps encoded, such as `%20`, stay encoded in the file name unless `UnescapePath` is enabled, and an escape of an unreserved character left in the routed path, which only a stray `%` can forge, is answered with `404` rather than decoded into a different file, so `/static/%%370rivate/secret.txt` never opens `private/secret.txt`. A path with an empty first segment, such as `//private/secret.txt` on a root mount, is `404` too, since the router keeps it apart from `/private/secret.txt`; [Static](./middleware/static.md) shows how to collapse a doubled slash for clients that send one. On Windows a path segment ending in a dot or a space is `404` as well, since the OS strips both when it opens a file. See [Static](./middleware/static.md). ##### Trusted Proxies We've renamed `EnableTrustedProxyCheck` to `TrustProxy` and moved `TrustedProxies` to `TrustProxyConfig`. **Important:** To use proxy headers like `X-Forwarded-For` with `c.IP()`, you must configure **all** of `TrustProxy`, `ProxyHeader`, and a trusted proxy via `TrustProxyConfig`. If the proxy is not trusted (for example, if you set only `ProxyHeader` or only `TrustProxy` without configuring `TrustProxyConfig`), proxy headers are ignored and `c.IP()` will return the remote TCP IP instead. ```go // Before app := fiber.New(fiber.Config{ // EnableTrustedProxyCheck enables the trusted proxy check. EnableTrustedProxyCheck: true, // TrustedProxies is a list of trusted proxy IP ranges/addresses. TrustedProxies: []string{"0.8.0.0", "127.0.0.0/8", "::1/128"}, }) ``` ```go // After app := fiber.New(fiber.Config{ // TrustProxy enables the trusted proxy check TrustProxy: true, // ProxyHeader specifies which header to read the real client IP from ProxyHeader: fiber.HeaderXForwardedFor, // TrustProxyConfig allows for configuring trusted proxies. TrustProxyConfig: fiber.TrustProxyConfig{ // Proxies is a list of trusted proxy IP ranges/addresses. Proxies: []string{"0.8.0.0"}, // Trust all loop-back IP addresses (127.0.0.0/8, ::1/128) Loopback: true, // Trust Unix domain socket connections UnixSocket: true, }, }) ``` For detailed proxy configuration guidance, see the [reverse proxy guide](./guide/reverse-proxy.md). #### 🎣 Hooks `OnShutdown` has been replaced by two hooks: `OnPreShutdown` and `OnPostShutdown`. Use them to run cleanup code before and after the server shuts down. When handling shutdown errors, register an `OnPostShutdown` hook and call `app.Listen()` in a goroutine. ```go // Before app.OnShutdown(func() { // Code to run before shutdown }) ``` ```go // After app.Hooks().OnPreShutdown(func() error { // Code to run before shutdown return nil }) ``` #### 🚀 Listen The `Listen` helpers (`ListenTLS`, `ListenMutualTLS`, etc.) were removed. Use `app.Listen()` with `fiber.ListenConfig` and a `tls.Config` when TLS is required. Options such as `ListenerNetwork` and `UnixSocketFileMode` are now configured via this struct. Prefer `TLSConfig` when you need full control, or use `CertFile` and `CertKeyFile` for quick TLS setup. ```go // Before app.ListenTLS(":3000", "cert.pem", "key.pem") ``` ```go // After app.Listen(":3000", fiber.ListenConfig{ CertFile: "./cert.pem", CertKeyFile: "./key.pem", }) ``` #### 🗺 Router ##### Direct `net/http` handlers Route registration helpers now accept native `net/http` handlers. Pass an `http.Handler`, `http.HandlerFunc`, or compatible function directly to methods such as `app.Get`, `Group`, or `RouteChain` and Fiber will adapt it at registration time. Manual wrapping through the adaptor middleware is no longer required for these common cases. :::note Compatibility considerations Adapted handlers stick to `net/http` semantics. They do not interact with `fiber.Ctx` and are slower than native Fiber handlers because of the extra conversion layer. Use them to ease migrations, but prefer Fiber handlers in performance-critical paths. ::: ```go httpHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if _, err := w.Write([]byte("served by net/http")); err != nil { panic(err) } }) app.Get("/", httpHandler) ``` ##### Middleware Registration The signatures for [`Add`](#middleware-registration) and [`Route`](#route-chaining) have been changed. To migrate [`Add`](#middleware-registration) you must change the `methods` in a slice. ```go // Before app.Add(fiber.MethodPost, "/api", myHandler) ``` ```go // After app.Add([]string{fiber.MethodPost}, "/api", myHandler) ``` ##### Mounting In this release, the `Mount` method has been removed. Instead, you can use the `Use` method to achieve similar functionality. ```go // Before app.Mount("/api", apiApp) ``` ```go // After app.Use("/api", apiApp) ``` ##### Route Chaining Refer to the [route chaining](#route-chaining) section for details on the new `RouteChain` helper. The `Route` function now matches its v2 behavior for prefix encapsulation. ```go // Before app.Route("/api", func(apiGrp Router) { apiGrp.Route("/user/:id?", func(userGrp Router) { userGrp.Get("/", func(c fiber.Ctx) error { // Get user return c.JSON(fiber.Map{"message": "Get user", "id": c.Params("id")}) }) userGrp.Post("/", func(c fiber.Ctx) error { // Create user return c.JSON(fiber.Map{"message": "User created"}) }) }) }) ``` ```go // After app.RouteChain("/api").RouteChain("/user/:id?"). Get(func(c fiber.Ctx) error { // Get user return c.JSON(fiber.Map{"message": "Get user", "id": c.Params("id")}) }). Post(func(c fiber.Ctx) error { // Create user return c.JSON(fiber.Map{"message": "User created"}) }) ``` #### 🗺 RebuildTree We introduced a new method that enables rebuilding the route tree stack at runtime. This allows you to add routes dynamically while your application is running and update the route tree to make the new routes available for use. For more details, refer to the [app documentation](./api/app.md#rebuildtree): ##### Example Usage ```go app.Get("/define", func(c fiber.Ctx) error { // Define a new route dynamically app.Get("/dynamically-defined", func(c fiber.Ctx) error { // Adding a dynamically defined route return c.SendStatus(http.StatusOK) }) app.RebuildTree() // Rebuild the route tree to register the new route return c.SendStatus(http.StatusOK) }) ``` In this example, a new route is defined, and `RebuildTree()` is called to ensure the new route is registered and available. Note: Use this method with caution. It is **not** thread-safe and can be very performance-intensive. Therefore, it should be used sparingly and primarily in development mode. It should not be invoke concurrently. ##### RemoveRoute - **RemoveRoute**: Removes route by path - **RemoveRouteByName**: Removes route by name - **RemoveRouteFunc**: Removes route by a function having `*Route` parameter For more details, refer to the [app documentation](./api/app.md#removeroute): #### 🧠 Context Fiber v3 introduces several new features and changes to the Ctx interface, enhancing its functionality and flexibility. - **ParamsInt**: Use `Params` with generic types. - **QueryBool**: Use `Query` with generic types. - **QueryFloat**: Use `Query` with generic types. - **QueryInt**: Use `Query` with generic types. - **Bind**: Now used for binding instead of view binding. Use `c.ViewBind()` for view binding. - **SendFile**: The file name is taken literally and is no longer percent-decoded. With `UnescapePath` off, which is the default, a route parameter keeps the escapes the router keeps, so a handler such as `c.SendFile(filepath.Join(dir, c.Params("name")))` that served `my file.txt` for `/files/my%20file.txt`, `über.txt` for `/files/%C3%BCber.txt` or `100%.txt` for `/files/100%25.txt` now looks for the escaped name and answers `404 Not Found`. Enable `UnescapePath`, or decode the parameter with `url.PathUnescape` and serve it through the `FS` option from an `os.OpenRoot` root, which refuses a decoded name that leaves the directory (see [`SendFile`](./api/ctx.md#sendfile)). The same holds for `Download`. In Fiber v3, the `Ctx` parameter in handlers is now an interface, which means the `*` symbol is no longer used. Here is an example demonstrating this change:
Example **Before**: ```go package main import ( "github.com/gofiber/fiber/v2" ) func main() { app := fiber.New() // Route Handler with *fiber.Ctx app.Get("/", func(c *fiber.Ctx) error { return c.SendString("Hello, World!") }) app.Listen(":3000") } ``` **After**: ```go package main import ( "github.com/gofiber/fiber/v3" ) func main() { app := fiber.New() // Route Handler without *fiber.Ctx app.Get("/", func(c fiber.Ctx) error { return c.SendString("Hello, World!") }) app.Listen(":3000") } ``` **Explanation**: In this example, the `Ctx` parameter in the handler is used as an interface (`fiber.Ctx`) instead of a pointer (`*fiber.Ctx`). This change allows for more flexibility and customization in Fiber v3.
##### 📎 Parser The `Parser` section in Fiber v3 has undergone significant changes to improve functionality and flexibility. ###### Migration Instructions 1. **BodyParser**: Use `c.Bind().Body()` instead of `c.BodyParser()`.
Example ```go // Before app.Post("/user", func(c *fiber.Ctx) error { var user User if err := c.BodyParser(&user); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()}) } return c.JSON(user) }) ``` ```go // After app.Post("/user", func(c fiber.Ctx) error { var user User if err := c.Bind().Body(&user); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()}) } return c.JSON(user) }) ```
2. **ParamsParser**: Use `c.Bind().URI()` instead of `c.ParamsParser()`. Note that the struct tag has changed from `params` to `uri`.
Example ```go // Before type Params struct { ID int `params:"id"` } app.Get("/user/:id", func(c *fiber.Ctx) error { var params Params if err := c.ParamsParser(¶ms); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()}) } return c.JSON(params) }) ``` ```go // After type Params struct { ID int `uri:"id"` } app.Get("/user/:id", func(c fiber.Ctx) error { var params Params if err := c.Bind().URI(¶ms); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()}) } return c.JSON(params) }) ```
3. **QueryParser**: Use `c.Bind().Query()` instead of `c.QueryParser()`.
Example ```go // Before app.Get("/search", func(c *fiber.Ctx) error { var query Query if err := c.QueryParser(&query); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()}) } return c.JSON(query) }) ``` ```go // After app.Get("/search", func(c fiber.Ctx) error { var query Query if err := c.Bind().Query(&query); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()}) } return c.JSON(query) }) ```
4. **CookieParser**: Use `c.Bind().Cookie()` instead of `c.CookieParser()`.
Example ```go // Before app.Get("/cookie", func(c *fiber.Ctx) error { var cookie Cookie if err := c.CookieParser(&cookie); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()}) } return c.JSON(cookie) }) ``` ```go // After app.Get("/cookie", func(c fiber.Ctx) error { var cookie Cookie if err := c.Bind().Cookie(&cookie); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()}) } return c.JSON(cookie) }) ```
##### 🔄 Redirect Fiber v3 enhances the redirect functionality by introducing new methods and improving existing ones. The new redirect methods provide more flexibility and control over the redirection process. ###### Migration Instructions 1. **RedirectToRoute**: Use `c.Redirect().Route()` instead of `c.RedirectToRoute()`.
Example ```go // Before app.Get("/old", func(c *fiber.Ctx) error { return c.RedirectToRoute("newRoute") }) ``` ```go // After app.Get("/old", func(c fiber.Ctx) error { return c.Redirect().Route("newRoute") }) ```
2. **RedirectBack**: Use `c.Redirect().Back()` instead of `c.RedirectBack()`.
Example ```go // Before app.Get("/back", func(c *fiber.Ctx) error { return c.RedirectBack() }) ``` ```go // After app.Get("/back", func(c fiber.Ctx) error { return c.Redirect().Back() }) ```
3. **Redirect**: Use `c.Redirect().To()` instead of `c.Redirect()`.
Example ```go // Before app.Get("/old", func(c *fiber.Ctx) error { return c.Redirect("/new") }) ``` ```go // After app.Get("/old", func(c fiber.Ctx) error { return c.Redirect().To("/new") }) ```
##### 🧾 Log The `ConfigurableLogger` and `AllLogger` interfaces now use generics. You can specify the underlying logger type when implementing these interfaces. While `any` can be used for maximum flexibility in some contexts, when retrieving the concrete logger via `log.DefaultLogger`, you must specify the exact underlying logger type, for example `log.DefaultLogger[*MyLogger]().Logger()`. #### 🌎 Client package Fiber v3 introduces a completely rebuilt client package with numerous new features such as Cookiejar, request/response hooks, and more. Here is a guide to help you migrate from Fiber v2 to Fiber v3. ##### New Features - **Cookiejar**: Manage cookies automatically. - **Request/Response Hooks**: Customize request and response handling. - **Improved Error Handling**: Better error management and reporting. ##### Migration Instructions **Import Path**: Update the import path to the new client package.
Before ```go import "github.com/gofiber/fiber/v2/client" ```
After ```go import "github.com/gofiber/fiber/v3/client" ```
**Common migrations**: 1. **Shared defaults instead of per-call mutation**: Move headers and timeouts into the reusable client and override with `client.Config` when needed.
Example ```go // Before status, body, errs := fiber.Get("https://api.example.com/users"). Set("Authorization", "Bearer "+token). Timeout(5 * time.Second). String() if len(errs) > 0 { return fmt.Errorf("request failed: %v", errs) } fmt.Println(status, body) ``` ```go // After cli := client.New(). AddHeader("Authorization", "Bearer "+token). SetTimeout(5 * time.Second) resp, err := cli.Get("https://api.example.com/users") if err != nil { return err } defer resp.Close() fmt.Println(resp.StatusCode(), resp.String()) ```
2. **Body handling**: Replace `Agent.JSON(...).Struct(&dst)` with request bodies through `client.Config` (or `Request.SetJSON`) and decode the response via `Response.JSON`.
Example ```go // Before var created user status, _, errs := fiber.Post("https://api.example.com/users"). JSON(payload). Struct(&created) if len(errs) > 0 { return fmt.Errorf("request failed: %v", errs) } fmt.Println(status, created) ``` ```go // After cli := client.New() resp, err := cli.Post("https://api.example.com/users", client.Config{ Body: payload, }) if err != nil { return err } defer resp.Close() var created user if err := resp.JSON(&created); err != nil { return fmt.Errorf("decode failed: %w", err) } fmt.Println(resp.StatusCode(), created) ```
3. **Path and query parameters**: Use the new path/query helpers instead of manually formatting URLs.
Example ```go // Before code, body, errs := fiber.Get(fmt.Sprintf("https://api.example.com/users/%s", id)). QueryString("active=true"). String() if len(errs) > 0 { return fmt.Errorf("request failed: %v", errs) } fmt.Println(code, body) ``` ```go // After cli := client.New().SetBaseURL("https://api.example.com") resp, err := cli.Get("/users/:id", client.Config{ PathParam: map[string]string{"id": id}, Param: map[string]string{"active": "true"}, }) if err != nil { return err } defer resp.Close() fmt.Println(resp.StatusCode(), resp.String()) ```
4. **Agent helpers**: `Agent.Bytes`, `AcquireAgent`, and `Agent.Parse` have been removed. Reuse a `client.Client` instance (or pool requests/responses directly) and access response data through the new typed helpers.
Example ```go // Before agent := fiber.AcquireAgent() status, body, errs := agent.Get("https://api.example.com/users").Bytes() fiber.ReleaseAgent(agent) if len(errs) > 0 { return fmt.Errorf("request failed: %v", errs) } var users []user if err := fiber.Parse(body, &users); err != nil { return fmt.Errorf("parse failed: %w", err) } fmt.Println(status, len(users)) ``` ```go // After cli := client.New() resp, err := cli.Get("https://api.example.com/users") if err != nil { return err } defer resp.Close() var users []user if err := resp.JSON(&users); err != nil { return fmt.Errorf("decode failed: %w", err) } fmt.Println(resp.StatusCode(), len(users)) ``` :::tip If you need pooling, use `client.AcquireRequest`, `client.AcquireResponse`, and their corresponding release functions around a long-lived `client.Client` instead of the removed agent pool. :::
5. **Fiber-level shortcuts**: The `fiber.Get`, `fiber.Post`, and similar top-level helpers are no longer exposed from the main module. Use the client package equivalents (`client.Get`, `client.Post`, etc.) which call the shared default client (or pass your own client instance for custom defaults).
Example ```go // Before status, body, errs := fiber.Get("https://api.example.com/health").String() if len(errs) > 0 { return fmt.Errorf("request failed: %v", errs) } fmt.Println(status, body) ``` ```go // After resp, err := client.Get("https://api.example.com/health") if err != nil { return err } defer resp.Close() fmt.Println(resp.StatusCode(), resp.String()) ``` :::note The `client.Get`/`client.Post` helpers use `client.C()` (the default shared client). For custom defaults, construct a client with `client.New()` and invoke its methods instead. :::
##### Complete API Migration Reference
Click to expand full v2 → v3 API mapping tables ###### Core Concepts | Description | v2 | v3 | |-------------|----|----| | Import | `github.com/gofiber/fiber/v2` | `github.com/gofiber/fiber/v3/client` | | Client Concept | `*fiber.Agent` | `*client.Client` + `*client.Request` | | Response Concept | `(code int, body []byte, errs []error)` | `(*client.Response, error)` | ###### Client/Agent Creation | Description | v2 | v3 | |-------------|----|----| | Create Agent/Client | `fiber.AcquireAgent()` | `client.New()` | | Get from pool | `fiber.AcquireAgent()` | `client.AcquireRequest()` | | Release | `fiber.ReleaseAgent(a)` | `client.ReleaseRequest(req)` | | With fasthttp.Client | - | `client.NewWithClient(c)` | | With HostClient | - | `client.NewWithHostClient(hc)` | | With LBClient | - | `client.NewWithLBClient(lb)` | | Get Request object | `a.Request()` | `c.R()` | | Default client | - | `client.C()` | | Replace default | - | `client.Replace(c)` | ###### HTTP Methods | Description | v2 | v3 (Client) | v3 (Request) | |-------------|----|----|--------------| | GET | `fiber.Get(url)` | `c.Get(url, cfg...)` | `req.Get(url)` | | POST | `fiber.Post(url)` | `c.Post(url, cfg...)` | `req.Post(url)` | | PUT | `fiber.Put(url)` | `c.Put(url, cfg...)` | `req.Put(url)` | | PATCH | `fiber.Patch(url)` | `c.Patch(url, cfg...)` | `req.Patch(url)` | | DELETE | `fiber.Delete(url)` | `c.Delete(url, cfg...)` | `req.Delete(url)` | | HEAD | `fiber.Head(url)` | `c.Head(url, cfg...)` | `req.Head(url)` | | OPTIONS | - | `c.Options(url, cfg...)` | `req.Options(url)` | | Custom | - | `c.Custom(url, method, cfg...)` | `req.Custom(url, method)` | ###### URL & Method | Description | v2 | v3 | |-------------|----|----| | Set URL | `req.SetRequestURI(url)` | `req.SetURL(url)` | | Get URL | `req.URI().String()` | `req.URL()` | | Set Method | `req.Header.SetMethod(method)` | `req.SetMethod(method)` | | Set Base URL | - | `c.SetBaseURL(url)` | ###### Request Execution & Response | Description | v2 | v3 | |-------------|----|----| | Parse Request | `a.Parse()` | Not needed | | Execute (bytes) | `a.Bytes()` → `(code, body, errs)` | `req.Send()` → `(*Response, error)` | | Execute (string) | `a.String()` | `resp.String()` | | Execute (struct) | `a.Struct(&v)` | `resp.JSON(&v)` / `resp.XML(&v)` | | Status Code | Return value `code` | `resp.StatusCode()` | | Status Text | - | `resp.Status()` | | Body (bytes) | Return value `body` | `resp.Body()` | | Response Header | `resp.Header.Peek(key)` | `resp.Header(key)` | | All Headers | `resp.Header.VisitAll(fn)` | `resp.Headers()` | | Cookies | - | `resp.Cookies()` | | Save to file | - | `resp.Save(path)` | | Close | - | `resp.Close()` | ###### Headers | Description | v2 | v3 (Client) | v3 (Request) | |-------------|----|----|--------------| | Set Header | `a.Set(k, v)` | `c.SetHeader(k, v)` | `req.SetHeader(k, v)` | | Add Header | `a.Add(k, v)` | `c.AddHeader(k, v)` | `req.AddHeader(k, v)` | | Multiple Headers | - | `c.SetHeaders(map)` | `req.SetHeaders(map)` | | Bytes variants | `a.SetBytesK/V/KV()` | - | - | ###### User-Agent, Referer, Content-Type, Host | Description | v2 | v3 (Client) | v3 (Request) | |-------------|----|----|--------------| | User-Agent | `a.UserAgent(ua)` | `c.SetUserAgent(ua)` | `req.SetUserAgent(ua)` | | Referer | `a.Referer(ref)` | `c.SetReferer(ref)` | `req.SetReferer(ref)` | | Content-Type | `a.ContentType(ct)` | - | `req.SetHeader("Content-Type", ct)` | | Host | `a.Host(host)` | - | `req.SetHeader("Host", host)` | | Connection Close | `a.ConnectionClose()` | - | `req.SetHeader("Connection", "close")` | ###### Cookies | Description | v2 | v3 (Client) | v3 (Request) | |-------------|----|----|--------------| | Set Cookie | `a.Cookie(k, v)` | `c.SetCookie(k, v)` | `req.SetCookie(k, v)` | | Multiple | `a.Cookies(k1, v1, ...)` | `c.SetCookies(map)` | `req.SetCookies(map)` | | With Struct | - | `c.SetCookiesWithStruct(v)` | `req.SetCookiesWithStruct(v)` | | Cookie Jar | - | `c.SetCookieJar(jar)` | - | :::caution Cookie jar path scoping `CookieJar.Set(uri, cookies...)` scopes a cookie that carries no usable `Path` attribute to the request URI's [RFC 6265 §5.1.4](https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.4) default-path, matching how a `Set-Cookie` received for that URI is stored. A cookie set against `/a/b` with no `Path` is scoped to `/a`, not to the whole host, so it is no longer sent to `/other`. Set `Path` explicitly to keep a cookie host-wide: ```go c := fasthttp.AcquireCookie() c.SetKey("session") c.SetValue(token) c.SetPath("/") // without this, the scope comes from uri's directory jar.Set(uri, c) ``` `SetByHost`, `SetKeyValue` and `SetKeyValueBytes` take no URI, so they have no request path to derive a scope from and continue to store such cookies at `/`. ::: ###### Query Parameters | Description | v2 | v3 (Client) | v3 (Request) | |-------------|----|----|--------------| | Query String | `a.QueryString(qs)` | - | - | | Add Param | - | `c.AddParam(k, v)` | `req.AddParam(k, v)` | | Set Param | - | `c.SetParam(k, v)` | `req.SetParam(k, v)` | | With Struct | - | `c.SetParamsWithStruct(v)` | `req.SetParamsWithStruct(v)` | ###### Path Parameters (NEW) | Description | v2 | v3 (Client) | v3 (Request) | |-------------|----|----|--------------| | Set Path Param | - | `c.SetPathParam(k, v)` | `req.SetPathParam(k, v)` | | Multiple | - | `c.SetPathParams(map)` | `req.SetPathParams(map)` | | With Struct | - | `c.SetPathParamsWithStruct(v)` | `req.SetPathParamsWithStruct(v)` | ###### Request Body | Description | v2 | v3 | |-------------|----|----| | Body (bytes) | `a.Body(body)` | `req.SetRawBody(body)` | | Body (string) | `a.BodyString(body)` | `req.SetRawBody([]byte(body))` | | Body Stream | `a.BodyStream(r, size)` | - | | JSON | `a.JSON(v)` | `req.SetJSON(v)` | | XML | `a.XML(v)` | `req.SetXML(v)` | | CBOR (NEW) | - | `req.SetCBOR(v)` | ###### Form Data | Description | v2 | v3 | |-------------|----|----| | Create Args | `fiber.AcquireArgs()` | Direct on Request | | Send Form | `a.Form(args)` | `req.SetFormData(k, v)` | | Add Form Data | `args.Set(k, v)` | `req.AddFormData(k, v)` | | With Map | - | `req.SetFormDataWithMap(map)` | | With Struct | - | `req.SetFormDataWithStruct(v)` | ###### File Upload | Description | v2 | v3 | |-------------|----|----| | Multipart Form | `a.MultipartForm(args)` | Automatic | | Boundary | `a.Boundary(b)` | `req.SetBoundary(b)` | | Send File | `a.SendFile(f, field...)` | `req.AddFile(path)` | | Multiple Files | `a.SendFiles(...)` | `req.AddFiles(files...)` | | With Reader | - | `req.AddFileWithReader(name, r)` | | FileData | `a.FileData(files...)` | `req.AddFiles(files...)` | ###### Timeout & TLS | Description | v2 | v3 (Client) | v3 (Request) | |-------------|----|----|--------------| | Timeout | `a.Timeout(d)` | `c.SetTimeout(d)` | `req.SetTimeout(d)` | | Max Redirects | `a.MaxRedirectsCount(n)` | Via Config | `req.SetMaxRedirects(n)` | | TLS Config | `a.TLSConfig(cfg)` | `c.SetTLSConfig(cfg)` | - | | Skip Verify | `a.InsecureSkipVerify()` | Via `tls.Config` | - | | Certificates | - | `c.SetCertificates(...)` | - | | Root Cert | - | `c.SetRootCertificate(path)` | - | ###### JSON/XML Encoder | Description | v2 | v3 | |-------------|----|----| | JSON Encoder | `a.JSONEncoder(fn)` | `c.SetJSONMarshal(fn)` | | JSON Decoder | `a.JSONDecoder(fn)` | `c.SetJSONUnmarshal(fn)` | | XML Encoder | - | `c.SetXMLMarshal(fn)` | | XML Decoder | - | `c.SetXMLUnmarshal(fn)` | | CBOR (NEW) | - | `c.SetCBORMarshal/Unmarshal(fn)` | ###### Authentication | Description | v2 | v3 | |-------------|----|----| | Basic Auth | `a.BasicAuth(user, pass)` | Via Header (Base64) | ###### Debug & Retry | Description | v2 | v3 | |-------------|----|----| | Debug | `a.Debug(w...)` | `c.Debug()` | | Disable Debug | - | `c.DisableDebug()` | | Logger | - | `c.SetLogger(logger)` | | Retry | `a.RetryIf(fn)` | `c.SetRetryConfig(cfg)` | ###### Reuse & Reset | Description | v2 | v3 | |-------------|----|----| | Reuse Agent | `a.Reuse()` | Use pool | | Reset Client | - | `c.Reset()` | | Dest Buffer | `a.Dest(dest)` | - | ###### NEW in v3 | Feature | v3 API | |---------|--------| | Request Hooks | `c.AddRequestHook(fn)` | | Final Request Hooks | `c.AddFinalRequestHook(fn)` | | Response Hooks | `c.AddResponseHook(fn)` | | Proxy | `c.SetProxyURL(url)` | | Context | `req.SetContext(ctx)` | | Dial Function | `c.SetDial(fn)` | | Raw Request | `req.RawRequest` | | Raw Response | `resp.RawResponse` | ###### Key Differences 1. **Architecture**: v2 `Agent` → v3 separate `Client`, `Request`, `Response` 2. **Error Handling**: v2 `[]error` → v3 single `error` 3. **Response**: v2 tuple `(code, body, errs)` → v3 `*Response` object 4. **No Parse()**: v3 auto-initializes requests 5. **Hooks**: v3 adds request/response middleware 6. **Path Params**: v3 native `:param` support 7. **Cookie Jar**: v3 built-in session management 8. **CBOR**: v3 adds CBOR encoding 9. **Context**: v3 native cancellation support 10. **Iterators**: v3 uses `iter.Seq2` for collections 11. **Bytes variants removed**: v2 `*Bytes*` methods gone
#### 🛠️ Utils {#utils-migration} Fiber v3 removes the in-repo `utils` package in favor of the external [`github.com/gofiber/utils/v2`](https://github.com/gofiber/utils) module. 1. Replace imports: ```go - import "github.com/gofiber/fiber/v2/utils" + import "github.com/gofiber/utils/v2" ``` 1. Review function changes: | v2 function | v3 replacement | | --- | --- | | `AssertEqual` | removed; use testing libraries like [`github.com/stretchr/testify/assert`](https://pkg.go.dev/github.com/stretchr/testify/assert) | | `ToLowerBytes` | `utils.ToLowerBytes` | | `ToUpperBytes` | `utils.ToUpperBytes` | | `TrimRightBytes` | `utils.TrimRight` | | `TrimLeftBytes` | `utils.TrimLeft` | | `TrimBytes` | `utils.Trim` | | `EqualFoldBytes` | `utils.EqualFold` | | `UUID` | `utils.UUID` | | `UUIDv4` | `utils.UUIDv4` | | `FunctionName` | `utils.FunctionName` | | `GetArgument` | `utils.GetArgument` | | `IncrementIPRange` | `utils.IncrementIPRange` | | `ConvertToBytes` | `utils.ConvertToBytes` | | `CopyString` | `utils.CopyString` | | `CopyBytes` | `utils.CopyBytes` | | `ByteSize` | `utils.ByteSize` | | `ToString` | `utils.ToString` | | `UnsafeString` | `utils.UnsafeString` | | `UnsafeBytes` | `utils.UnsafeBytes` | | `GetString` | removed; use `utils.ToString` or the standard library | | `GetBytes` | removed; use `utils.CopyBytes` or `[]byte(s)` | | `ImmutableString` | removed; strings are already immutable | | `GetMIME` | `utils.GetMIME` | | `ParseVendorSpecificContentType` | `utils.ParseVendorSpecificContentType` | | `StatusMessage` | `utils.StatusMessage` | | `IsIPv4` | `utils.IsIPv4` | | `IsIPv6` | `utils.IsIPv6` | | `ToLower` | `utils.ToLower` | | `ToUpper` | `utils.ToUpper` | | `TrimLeft` | `strings.TrimLeft` | | `Trim` | `strings.Trim` | | `TrimRight` | `strings.TrimRight` | | `EqualFold` | `strings.EqualFold` | | `StartTimeStampUpdater` | `utils.StartTimeStampUpdater` (new `utils.Timestamp` provides the current value) | 1. Update your code. For example: ```go // v2 import oldutils "github.com/gofiber/fiber/v2/utils" func demo() { b := oldutils.TrimBytes([]byte(" fiber ")) id := oldutils.UUIDv4() s := oldutils.GetString([]byte("foo")) } // v3 import ( "github.com/gofiber/utils/v2" "strings" ) func demo() { s := utils.TrimSpace(" fiber ") id := utils.UUIDv4() str := utils.ToString([]byte("foo")) t := strings.TrimRight("bar ", " ") } ``` The `github.com/gofiber/utils/v2` module also introduces new helpers like `ParseInt`, `ParseUint`, `Walk`, `ReadFile`, and `Timestamp`. #### 🧬 Middlewares ##### Important Change for Accessing Middleware Data **Change:** In Fiber v2, some middlewares set data in `c.Locals()` using string keys (e.g., `c.Locals("requestid")`). In Fiber v3, to align with Go's context best practices and prevent key collisions, these middlewares now store their specific data in the request's context using unexported keys of custom types. **Impact:** Directly accessing these middleware-provided values via `c.Locals("some_string_key")` will no longer work. **Migration Action:** The `ContextKey` configuration option has been removed from all middlewares. Values are no longer stored under user-defined keys. You must update your code to use the dedicated exported functions provided by each affected middleware to retrieve its data from the context. **Examples of new helper functions to use:** - `requestid.FromContext(c)` - `csrf.TokenFromContext(c)` - `csrf.HandlerFromContext(c)` - `session.FromContext(c)` - `basicauth.UsernameFromContext(c)` - `keyauth.TokenFromContext(c)` **For logging these values:** The recommended approach is to use the `CustomTags` feature of the Logger middleware, which allows you to call these specific `FromContext` functions. Refer to the [Logger section in "What's New"](#logger) for detailed examples. :::note If you were manually setting and retrieving your own application-specific values in `c.Locals()` using string keys, that functionality remains unchanged. This change specifically pertains to how Fiber's built-in (and some contrib) middlewares expose their data. ::: ##### BasicAuth The `Authorizer` callback now receives the current request context. Update custom functions from: ```go Authorizer: func(user, pass string) bool { // v2 style return user == "admin" && pass == "secret" } ``` to: ```go Authorizer: func(user, pass string, _ fiber.Ctx) bool { // v3 style with access to the Fiber context return user == "admin" && pass == "secret" } ``` Passwords configured for BasicAuth must now be pre-hashed. If no prefix is supplied the middleware expects a SHA-256 digest encoded in hex or base64. Common prefixes like `{SHA256}` and `{SHA512}` and bcrypt strings are also supported. Plaintext passwords are no longer accepted. Unauthorized responses also include a `Vary: Authorization` header for correct caching behavior. You can also set the optional `HeaderLimit` and `Charset` options to further control authentication behavior. ##### KeyAuth The keyauth middleware was updated to introduce a configurable `Realm` field for the `WWW-Authenticate` header. The old string-based `KeyLookup` configuration has been replaced with an `Extractor` field, and the `AuthScheme` field has been removed. The auth scheme is now inferred from the extractor used (e.g., `extractors.FromAuthHeader`). Use the helpers of the shared [`extractors`](#-extractors-package) package, such as `extractors.FromHeader`, `extractors.FromAuthHeader`, or `extractors.FromCookie`, to define where the key should be retrieved from. Multiple sources can be combined with `extractors.Chain`. New `Challenge`, `Error`, `ErrorDescription`, `ErrorURI`, and `Scope` options let you customize challenge responses, include Bearer error parameters, and specify required scopes. `ErrorURI` values are validated as absolute, credentials containing whitespace are rejected, and when multiple authorization extractors are chained, all schemes are advertised in the `WWW-Authenticate` header. The middleware defers emitting `WWW-Authenticate` until a 401 status is final, and `extractors.FromAuthHeader` requires exactly one space after the scheme and validates the credential as `token68`. ```go // Before app.Use(keyauth.New(keyauth.Config{ KeyLookup: "header:Authorization", AuthScheme: "Bearer", Validator: validateAPIKey, })) // After app.Use(keyauth.New(keyauth.Config{ Extractor: extractors.FromAuthHeader("Bearer"), Validator: validateAPIKey, })) ``` Combine multiple sources with `extractors.Chain()` when needed. ##### Cache The deprecated `Store` and `Key` fields were removed. Use `Storage` and `KeyGenerator` instead to configure caching backends and cache keys. Defaults also changed: the middleware now emits `Cache-Control` headers, the default `Expiration` increased to `5 minutes` (from `1 minute`), and a new `MaxBytes` limit of `1 MB` (previously unlimited) now caps cached payloads when you call `cache.New()` without a `Config`. A `Config` that leaves `MaxBytes` unset stores bodies without a limit, as in v2. To restore v2 behavior: - Set `DisableCacheControl` to `true` to suppress automatic `Cache-Control` headers. - Configure `Expiration` to `1*time.Minute`. - Set `MaxBytes` to `0` (or a higher value) when caching large responses. - Disable structured key dimensions as needed (for example `DisableQueryKeys: true`), or provide a custom `KeyGenerator`. Additional v3 cache key options: - `Methods`: HTTP methods eligible for caching (default `GET`, `HEAD`) - `DisableQueryKeys`: disable canonicalized query args in keys (default `false`) - `KeyHeaders`: request header allow-list for key partitioning - `KeyCookies`: explicit cookie allow-list for key partitioning - `DisableVaryHeaders`: disable response `Vary` dimensions in lookup/storage partitioning (default `false`) ##### CORS The CORS middleware has been updated to use slices instead of strings for the `AllowOrigins`, `AllowMethods`, `AllowHeaders`, and `ExposeHeaders` fields. Here's how you can update your code: ```go // Before app.Use(cors.New(cors.Config{ AllowOrigins: "https://example.com,https://example2.com", AllowMethods: strings.Join([]string{fiber.MethodGet, fiber.MethodPost}, ","), AllowHeaders: "Content-Type", ExposeHeaders: "Content-Length", })) // After app.Use(cors.New(cors.Config{ AllowOrigins: []string{"https://example.com", "https://example2.com"}, AllowMethods: []string{fiber.MethodGet, fiber.MethodPost}, AllowHeaders: []string{"Content-Type"}, ExposeHeaders: []string{"Content-Length"}, })) ``` ##### Redirect - **Ordered rules**: `Rules map[string]string` is deprecated in favor of `RuleList []Rule`. A map has no order, so which rule answered a path two rules both matched could not be expressed by the author. Rules in a `RuleList` are tried in the order written and the first match wins, exactly as routes are matched. ```go // Before app.Use(redirect.New(redirect.Config{ Rules: map[string]string{ "/old": "/new", "/old/*": "/new/$1", }, })) // After app.Use(redirect.New(redirect.Config{ RuleList: []redirect.Rule{ {From: "/old", To: "/new"}, {From: "/old/*", To: "/new/$1"}, }, })) ``` `Rules` keeps working for the whole of v3. Its order is now decided by a documented heuristic rather than by analyzing each pattern: most path text pinned before the first `*`, then most path text overall, then fewest asterisks, then the key. Configurations written with path text and `*` are unaffected; rules relying on regular-expression syntax beyond `*` may order differently, and `RuleList` gives exact control. Setting both fields panics. Fiber now also warns at startup when a rule can never fire because an earlier one matches every path it does. - **A rule matches the whole path**: both ends of the pattern are anchored, so a rule no longer matches just a suffix of the path, and an alternation such as `/a|/b` cannot match by prefix or suffix alone (#4476). - **A capture may no longer stand inside a target's authority**, which covers the host, the port and any userinfo: `"https://$1.cdn.example.com/"`, `"https://cdn.example.com:$1"` and `"https://$1"` are refused at startup with a warning, and those rules never fire. Whether such a value was safe depended on percent-decoding, IDNA mapping, numeric labels read as IPv4 addresses, IPv6 brackets and userinfo, and each of those was a way to move the host somewhere the target did not name. A capture in the path, query or fragment is unchanged. Where the destination host genuinely varies, pick it in a handler and use `c.Redirect()`, where the value is yours to validate. ##### CSRF - **Field Renaming**: The `Expiration` field in the CSRF middleware configuration has been renamed to `IdleTimeout` to better describe its functionality. Additionally, the default value has been reduced from 1 hour to 30 minutes. Update your code as follows: ```go // Before app.Use(csrf.New(csrf.Config{ Expiration: 10 * time.Minute, })) // After app.Use(csrf.New(csrf.Config{ IdleTimeout: 10 * time.Minute, })) ``` - **Session Key Removal**: The `SessionKey` field has been removed from the CSRF middleware configuration. The session key is now an unexported constant within the middleware to avoid potential key collisions in the session store. - **KeyLookup Field Removal**: The `KeyLookup` field has been removed from the CSRF middleware configuration. This field was deprecated and is no longer needed as the middleware now uses a more secure approach for token management. - **DisableValueRedaction Toggle**: CSRF redacts tokens and storage keys by default; set `DisableValueRedaction` to `true` when diagnostics require the raw values. - **Default KeyGenerator**: Changed from `utils.UUIDv4` to `utils.SecureToken`, producing base64-encoded tokens instead of UUID format. ```go // Before app.Use(csrf.New(csrf.Config{ KeyLookup: "header:X-Csrf-Token", // other config... })) // After - use Extractor instead app.Use(csrf.New(csrf.Config{ Extractor: extractors.FromHeader("X-Csrf-Token"), // other config... })) ``` - **Cookie Extractor Restriction**: The `csrf.FromCookie` extractor has been removed for security reasons, and `csrf.New` panics when its `Extractor` (or any extractor inside a `Chain`) reads the cookie named by `CookieName`. Using cookie-based extraction for the CSRF cookie defeats the purpose of CSRF protection by making the extracted token always match the cookie value. ```go // Before - This was a security vulnerability app.Use(csrf.New(csrf.Config{ Extractor: csrf.FromCookie("csrf_token"), // ❌ Insecure! })) // After - Use secure extractors instead app.Use(csrf.New(csrf.Config{ Extractor: extractors.FromHeader("X-Csrf-Token"), // ✅ Secure })) // or read the token from a form field app.Use(csrf.New(csrf.Config{ Extractor: extractors.FromForm("_csrf"), // ✅ Secure })) // Avoid FromQuery and FromParam for CSRF tokens: URLs end up in logs and browser history. ``` **Security Note**: Rejecting an extractor that reads the CSRF cookie prevents a common misconfiguration that would completely bypass CSRF protection. The middleware uses the Double Submit Cookie pattern, which requires the token to be submitted through a different channel than the cookie to provide meaningful protection. ##### Idempotency - **DisableValueRedaction Toggle**: The idempotency middleware now hides keys in logs and error paths by default, with a `DisableValueRedaction` boolean (default `false`) to reveal them when needed. ##### Timeout The timeout middleware now accepts a configuration struct instead of a duration. Update your code as follows: ```go // Before app.Get("/", timeout.New(handler, 2*time.Second)) // After app.Get("/", timeout.New(handler, timeout.Config{Timeout: 2 * time.Second})) ``` **Important behavioral changes:** - The middleware now returns immediately on timeout without waiting for the handler (using the new Abandon mechanism). - Handlers can detect timeouts by listening on `c.Context().Done()` and return early. - Panics in the handler are caught and converted to `500 Internal Server Error`. ##### Filesystem You need to move filesystem middleware to static middleware due to it has been removed from the core. ```go // Before app.Use(filesystem.New(filesystem.Config{ Root: http.Dir("./assets"), })) app.Use(filesystem.New(filesystem.Config{ Root: http.Dir("./assets"), Browse: true, Index: "index.html", MaxAge: 3600, })) ``` ```go // After app.Use(static.New("", static.Config{ FS: os.DirFS("./assets"), })) app.Use(static.New("", static.Config{ FS: os.DirFS("./assets"), Browse: true, IndexNames: []string{"index.html"}, MaxAge: 3600, })) ``` ##### EnvVar The `ExcludeVars` option has been removed. Remove any references to it and use `ExportVars` to explicitly list environment variables that should be exposed. ##### Healthcheck Previously, the Healthcheck middleware was configured with a combined setup for liveness and readiness probes: ```go //before app.Use(healthcheck.New(healthcheck.Config{ LivenessProbe: func(c fiber.Ctx) bool { return true }, LivenessEndpoint: "/live", ReadinessProbe: func(c fiber.Ctx) bool { return serviceA.Ready() && serviceB.Ready() && ... }, ReadinessEndpoint: "/ready", })) ``` With the new version, each health check endpoint is configured separately, allowing for more flexibility: ```go // after // Default liveness endpoint configuration app.Get(healthcheck.LivenessEndpoint, healthcheck.New(healthcheck.Config{ Probe: func(c fiber.Ctx) bool { return true }, })) // Default readiness endpoint configuration app.Get(healthcheck.ReadinessEndpoint, healthcheck.New()) // New default startup endpoint configuration // Default endpoint is /startupz app.Get(healthcheck.StartupEndpoint, healthcheck.New(healthcheck.Config{ Probe: func(c fiber.Ctx) bool { return serviceA.Ready() && serviceB.Ready() && ... }, })) // Custom liveness endpoint configuration app.Get("/live", healthcheck.New()) ``` ##### Monitor Since v3 the Monitor middleware has been moved to the [Contrib package](https://github.com/gofiber/contrib/tree/main/monitor) ```go // Before import "github.com/gofiber/fiber/v2/middleware/monitor" app.Use("/metrics", monitor.New()) ``` You only need to change the import path to the contrib package. ```go // After import "github.com/gofiber/contrib/monitor" app.Use("/metrics", monitor.New()) ``` ##### Proxy In previous versions, TLS settings for the proxy middleware were set using the `WithTlsConfig` method. This method has been removed in favor of a more idiomatic configuration via the `TLSConfig` field in the `Config` struct. ##### Before (v2 usage) ```go proxy.WithTlsConfig(&tls.Config{ InsecureSkipVerify: true, }) // Forward to url app.Get("/gif", proxy.Forward("https://i.imgur.com/IWaBepg.gif")) ``` ##### After (v3 usage) ```go proxy.WithClient(&fasthttp.Client{ TLSConfig: &tls.Config{InsecureSkipVerify: true}, }) // Forward to url app.Get("/gif", proxy.Forward("https://i.imgur.com/IWaBepg.gif")) ``` `proxy.Balancer` also adopts the common middleware signature pattern and now accepts an optional variadic config: call `proxy.Balancer()` to use the defaults or continue passing a single `proxy.Config` value as in v2. ##### Rewrite - **Captures decoded once**: with `UnescapePath` enabled a wildcard capture is escaped again before it is inserted into `To`, so the rewritten path is not decoded a second time. `/files/%252e%252e/secret` rewritten by `/files/*` to `/public/$1` now reaches the handler as `/public/%2e%2e/secret` instead of `/secret`. - **Ordered rules**: `Rules map[string]string` is deprecated in favor of `RuleList []Rule`. A map has no order, so which rule answered a path two rules both matched was decided by map iteration, which Go randomizes per run: the same request could be rewritten differently from one call to the next. Rules in a `RuleList` are tried in the order written and the first match wins, exactly as routes are matched. ```go // Before app.Use(rewrite.New(rewrite.Config{ Rules: map[string]string{ "/old": "/new", "/old/*": "/new/$1", }, })) // After app.Use(rewrite.New(rewrite.Config{ RuleList: []rewrite.Rule{ {From: "/old", To: "/new"}, {From: "/old/*", To: "/new/$1"}, }, })) ``` `Rules` keeps working for the whole of v3; its keys are ranked most path text before the first `*`, then most path text overall, then fewest asterisks, then the key. Setting both fields panics. - **A rule is path text**: `From` is no longer compiled as an unescaped regular expression. `*` matches a run of any bytes but a newline and every other byte stands for itself, so `/preis-1.000-euro` rewrites that path and no longer also `/preis-1X000-euro`, and `/faq?` no longer rewrites `/fa`. This also completes the anchoring fix from #4476: `"^" + "/a|/b" + "$"` parsed as `(^/a)|(/b$)`, so a rule could match by prefix or suffix alone. Rules written with path text and `*` are unaffected; a rule relying on regular-expression syntax now matches the literal characters it spells. ##### Session `session.New()` now returns a middleware handler. When using the store pattern, create a store with `session.NewStore()` or call `Store()` on the middleware. Sessions obtained from a store must be released manually via `sess.Release()`. Additionally, replace the deprecated `KeyLookup` option with extractor functions from the shared `extractors` package such as `extractors.FromCookie()` or `extractors.FromHeader()`. Multiple extractors can be combined with `extractors.Chain()`. ```go // Before app.Use(session.New(session.Config{ KeyLookup: "cookie:session_id", Store: session.NewStore(), })) ``` ```go // After app.Use(session.New(session.Config{ Extractor: extractors.FromCookie("session_id"), Store: session.NewStore(), })) ``` `CookieSameSite` is now read the same way as it is for `Ctx.Cookie`: - **`"Disabled"` omits the attribute.** The session used to ignore `CookieSameSiteDisabled` and send `SameSite=Lax`, while `Ctx.Cookie` honored it, so one constant meant two things. A session cookie configured this way now carries no `SameSite` attribute, which is weaker than what was sent before. Set `CookieSameSite: "Lax"` explicitly to keep the old cookie. - **Any other value panics** during configuration instead of silently falling back to `Lax`, matching how `AbsoluteTimeout` is already validated. `Disabled`, `Lax`, `Strict` and `None` are accepted case-insensitively. See the [Session Middleware Migration Guide](./middleware/session.md#migration-guide) for complete details. --- ## 404 Handler ## Custom 404 Not Found Handler Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/404-handler) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/404-handler) This example demonstrates how to implement a custom 404 Not Found handler using the [Fiber](https://gofiber.io) web framework in Go. The purpose of this example is to show how to handle requests to undefined routes gracefully by returning a 404 status code. ### Description In web applications, it's common to encounter requests to routes that do not exist. Handling these requests properly is important to provide a good user experience and to inform the user that the requested resource is not available. This example sets up a simple Fiber application with a custom 404 handler to manage such cases. ### Requirements - [Go](https://golang.org/dl/) 1.18 or higher - [Git](https://git-scm.com/downloads) ### Running the Example To run the example, use the following command: ```bash go run main.go ``` The server will start and listen on `localhost:3000`. ### Example Routes - **GET /hello**: Returns a simple greeting message. - **Undefined Routes**: Any request to a route not defined will trigger the custom 404 handler. ### Custom 404 Handler The custom 404 handler is defined to catch all undefined routes and return a 404 status code with a "Not Found" message. ### Code Overview #### `main.go` ```go package main import ( "log" "github.com/gofiber/fiber/v3" ) func main() { // Fiber instance app := fiber.New() // Routes app.Get("/hello", hello) // 404 Handler app.Use(func(c fiber.Ctx) error { return c.SendStatus(fiber.StatusNotFound) // => 404 "Not Found" }) // Start server log.Fatal(app.Listen(":3000")) } // Handler func hello(c fiber.Ctx) error { return c.SendString("I made a ☕ for you!") } ``` ### Conclusion This example provides a basic setup for handling 404 Not Found errors in a Fiber application. It can be extended and customized further to fit the needs of more complex applications. ### References - [Fiber Documentation](https://docs.gofiber.io) - [GitHub Repository](https://github.com/gofiber/fiber) --- ## 👋 Overview ![Fiber Recipes](https://raw.githubusercontent.com/gofiber/recipes/master/.github/logo-dark.svg#gh-dark-mode-only) ![Fiber Recipes](https://raw.githubusercontent.com/gofiber/recipes/master/.github/logo.svg#gh-light-mode-only) ## 🍳 Recipes for [Fiber](https://github.com/gofiber/fiber) **Welcome to the official Fiber cookbook**! Here you can find the most **delicious** recipes to cook delicious meals using our web framework. ### 🌽 Table of contents - [404 Handler](./404-handler/README.md) - Custom 404 error page handling. - [Air Live Reloading](./air/README.md) - Live reloading for Go applications. - [Asynq](./asynq/README.md) - Enqueue background jobs from Fiber and process them with an Asynq worker. - [Auth + Docker + Postgres + JWT](./auth-docker-postgres-jwt/README.md) - Authentication with Docker, Postgres, and JWT. - [Auth + JWT](./auth-jwt/README.md) - Simple JWT authentication. - [Autocert](./autocert/README.md) - Automatic TLS certificate management. - [AWS Elastic Beanstalk](./aws-eb/README.md) - Deploying to AWS Elastic Beanstalk. - [AWS SAM Container](./aws-sam-container/README.md) - Containerized serverless applications with AWS SAM. - [AWS SES Email Sender](./aws-ses-sender/README.md) - AWS SES-based Golang email delivery service. Provides email dispatch processing, status tracking, scheduled sending, and result analysis capabilities. - [Bootstrap](./bootstrap/README.md) - Integrating Bootstrap. - [Clean Architecture](./clean-architecture/README.md) - Implementing clean architecture in Go. - [Clean Code](./clean-code/README.md) - Implementing clean code in Go. - [Cloud Run](./cloud-run/README.md) - Deploying to Google Cloud Run. - [Cloudflare Container Workers with Go Fiber](./cloudflare-workers/README.md) - Run a Go Fiber v3 app in a Cloudflare Container Worker with a Worker proxy. - [Colly Gorm](./colly-gorm/README.md) - Web scraping with Colly and GORM. - [CSRF](./csrf/README.md) - Cross-Site Request Forgery (CSRF) protection. - [CSRF + Session](./csrf-with-session/README.md) - Cross-Site Request Forgery (CSRF) protection with session management. - [Docker + MariaDB](./docker-mariadb-clean-arch/README.md) - Dockerized MariaDB with Clean Architecture. - [Docker + Nginx](./docker-nginx-loadbalancer/README.md) - Load balancing with Docker and Nginx. - [Dummy JSON Proxy](./dummyjson/README.md) - Proxying dummy JSON data. - [Email Verification Service](./email-verification/README.md) - Email verification service with code generation and validation - [Entgo ORM (MySQL)](./ent-mysql/README.md) - Using Entgo ORM with MySQL - [Entgo Sveltekit](./entgo-sveltekit/README.md) - A full-stack Todo application built using Sveltekit, Tailwind CSS, Entgo, and SQLite. - [Envoy External Authorization](./envoy-extauthz/README.md) - External authorization with Envoy. - [File Server](./file-server/README.md) - Serving static files. - [Firebase Authentication](./firebase-auth/README.md) - Firebase authentication integration. - [Firebase Functions](./firebase-functions/README.md) - Using Firebase Functions. - [Form Data](./form-data/README.md) - Parsing application/x-www-form-urlencoded bodies into structs, slices and maps. - [Firebase GCloud](./gcloud/README.md) - Integrating Firebase with Google Cloud. - [Google Cloud Firebase](./gcloud-firebase/README.md) - Firebase services on Google Cloud. - [GeoIP](./geoip/README.md) - Geolocation using ip-api.com. - [GeoIP + MaxMind](./geoip-maxmind/README.md) - Geolocation with GeoIP and MaxMind databases. - [GORM](./gorm/README.md) - Using GORM with SQLite database. - [GORM MySQL](./gorm-mysql/README.md) - Using GORM with MySQL database. - [GORM + PostgreSQL](./gorm-postgres/README.md) - Using GORM with PostgreSQL database. - [Graceful shutdown](./graceful-shutdown/README.md) - Graceful shutdown of applications. - [GraphQL](./graphql/README.md) - Setting up a GraphQL server. - [gRPC](./grpc/README.md) - Using Fiber as a client to a gRPC server. - [Hello World](./hello-world/README.md) - A simple "Hello, World!" application. - [Heroku](./heroku/README.md) - Deploying to Heroku. - [Hexagonal Architecture](./hexagonal/README.md) - A Hexagonal Software Architecture in Golang and MongoDB. - [HTTPS with PKCS12 TLS](./https-pkcs12-tls/README.md) - Setting up an HTTPS server with PKCS12 TLS certificates. - [HTTPS with TLS](./https-tls/README.md) - Setting up an HTTPS server with self-signed TLS certificates. - [I18n](./i18n/README.md) - Internationalization support. - [JWT](./jwt/README.md) - Using JSON Web Tokens (JWT) for authentication. - [Kubernetes](./k8s/README.md) - Deploying applications to Kubernetes. - [Todo App + Auth + GORM + Testcontainers](./local-development-testcontainers/README.md) - A Todo application with authentication using GORM and Postgres. - [Memgraph](./memgraph/README.md) - Using Memgraph. - [MinIO](./minio/README.md) - A simple application for uploading and downloading files from MinIO. - [MongoDB](./mongodb/README.md) - Connecting to a MongoDB database. - [Monitoring with Apitally](./monitoring-with-apitally/README.md) - A simple REST API with monitoring and request logging using Apitally. - [Multiple Ports](./multiple-ports/README.md) - Running an application on multiple ports. - [MySQL](./mysql/README.md) - Connecting to a MySQL database. - [Neo4j](./neo4j/README.md) - Connecting to a Neo4j database. - [OAuth2](./oauth2/README.md) - Implementing GitHub OAuth2 authentication with GoFiber. - [Google OAuth2](./oauth2-google/README.md) - Implementing Google OAuth2 authentication. - [OpenAPI](./openapi/README.md) - Generate OpenAPI 3 documentation and JSON schema for your application. - [Optional Parameter](./optional-parameter/README.md) - Handling optional parameters. - [Parsley](./parsley/README.md) - Using Parsley for dependency injection in an application. - [PostgreSQL](./postgresql/README.md) - Connecting to a PostgreSQL database. - [Prefork](./prefork/README.md) - Running an application in prefork mode. - [RabbitMQ](./rabbitmq/README.md) - Using RabbitMQ with Fiber to publish messages to a queue. - [React](./react-router/README.md) - Using React. - [Recover Middleware](./recover/README.md) - Recover middleware for error handling. - [RSS Feed](./rss-feed/README.md) - Generating an RSS feed. - [Seenode](./seenode/README.md) - Deploying to Seenode cloud platform. - [Server Timing](./server-timing/README.md) - Adding Server Timing headers to an application. - [Sessions + SQLite3](./sessions-sqlite3/README.md) - Using SQLite3 as a storage engine for user sessions. - [Socketio](./socketio/README.md) - A chatroom application using Socket.IO. - [Single Page Application (SPA)](./spa/README.md) - Setting up a Single Page Application (SPA) using React for the frontend and Go for the backend. - [Sqlboiler](./sqlboiler/README.md) - Using Sqlboiler ORM. - [Sqlc](./sqlc/README.md) - Using Sqlc to generate Go code from SQL queries. - [Server-Sent Events](./sse/README.md) - Implementing Server-Sent Events in an application. - [Stream Request Body](./stream-request-body/README.md) - Streaming request bodies. - [Svelte Netlify](./svelte-netlify/README.md) - Deploying a Svelte + Fiber application on Netlify. - [Sveltekit Embed](./sveltekit-embed/README.md) - A full-stack application built using Sveltekit and Tailwind CSS. - [Swagger](./swagger/README.md) - Generate Swagger documentation for your application. - [Tableflip Example](./tableflip/README.md) - Use tableflip for graceful upgrades in a Go application. - [Template](./template/README.md) - Setting up a Go application with template rendering. - [Template Asset Bundling](./template-asset-bundling/README.md) - Setting up a Go application with template rendering and asset bundling. - [Todo App + Auth + GORM](./todo-app-with-auth-gorm/README.md) - A Todo application with authentication using GORM. - [Unit Testing](./unit-test/README.md) - Writing unit tests for a Go Fiber application. - [File Upload](./upload-file/README.md) - Handling file uploads in a Go application. - [URL Shortener](./url-shortener-api/README.md) - URL shortening service with a simple API. - [Validation](./validation/README.md) - Input validation using go-playground/validator. - [Vercel](./vercel/README.md) - Deploy a Go application to Vercel. - [WebSocket](./websocket/README.md) - Real-time communication application using WebSockets. - [WebSocket Chat](./websocket-chat/README.md) - Real-time chat application using WebSockets. ### 👩‍🍳 Have a delicious recipe? If you have found an amazing recipe for **Fiber** — share it with others! We are ready to accept your [PR](https://github.com/gofiber/recipes/pulls) and add your recipe to the cookbook (both on [website](https://docs.gofiber.io) and this repository). --- ## Air Live Reloading ## Live Reloading with Air Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/air) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/air) This example demonstrates how to set up live reloading for a Go application using the [Air](https://github.com/air-verse/air) tool. The purpose of this example is to show how to automatically reload your application during development whenever you make changes to the source code. ### Description Live reloading is a useful feature during development as it saves time by automatically restarting the application whenever changes are detected. This example sets up a simple Fiber application and configures Air to watch for changes and reload the application. ### Requirements - [Go](https://golang.org/dl/) 1.18 or higher - [Git](https://git-scm.com/downloads) - [Air](https://github.com/air-verse/air) ### Setup 1. Clone the repository: ```bash git clone https://github.com/gofiber/recipes.git cd recipes/air ``` 2. Install the dependencies: ```bash go mod download ``` 3. Install Air: ```bash go install github.com/air-verse/air@latest ``` ### Configuration Air is configured using the `air/.air.conf` file. This file specifies the build command, binary name, and directories to watch for changes. The configuration files for different operating systems are provided: - `air/.air.windows.conf` for Windows - `air/.air.linux.conf` for Linux ### Running the Example To run the example with live reloading, use the following command: ```bash air -c .air.linux.conf ``` or for Windows: ```bash air -c .air.windows.conf ``` The server will start and listen on `localhost:3000`. Any changes to the source code will automatically trigger a rebuild and restart of the application. ### Example Routes - **GET /**: Returns a simple greeting message. ### Code Overview #### `main.go` ```go package main import ( "log" "github.com/gofiber/fiber/v3" ) func main() { // Create new Fiber instance app := fiber.New() // Create new GET route on path "/" app.Get("/", func(c fiber.Ctx) error { return c.SendString("Hello, World!") }) // Start server on http://localhost:3000 log.Fatal(app.Listen(":3000")) } ``` ### Conclusion This example provides a basic setup for live reloading a Go application using Air. It can be extended and customized further to fit the needs of more complex applications. ### References - [Air Documentation](https://github.com/air-verse/air) - [Fiber Documentation](https://docs.gofiber.io) - [GitHub Repository](https://github.com/gofiber/fiber) --- ## Asynq ## Fiber and Asynq example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/asynq) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/asynq) ### Description This example shows how to run background jobs with [Asynq](https://github.com/hibiken/asynq) (a Redis-backed task queue) from a [Fiber](https://github.com/gofiber/fiber) HTTP server. The API enqueues a job and returns immediately; a separate worker process consumes the queue and does the slow work, with retries and priorities handled by Asynq. ### How it works - The Fiber API exposes `POST /enqueue` with a JSON body: `{"user_id": "...", "email": "..."}`. - Each request enqueues a `email:welcome` task onto Redis and returns the task id — it never does the work inline. - A separate **worker** process pulls tasks from Redis and runs them, retrying on failure with backoff. - The task type and payload live in a shared `task` package, so the API and the worker can't drift apart. The worker uses weighted queues (`critical` drained ~6x as often as `low`), which is the usual way to keep a noisy low-priority job from starving important ones. ### Requirements - [Go](https://golang.org/dl/) 1.25 or higher - A running Redis instance (or use the provided `docker-compose.yml`) ### Running the example #### With Docker Compose ```bash docker compose up --build ``` This starts Redis, the API, and the worker together. #### Manually Start Redis, then in two terminals: ```bash # terminal 1 — API make run-api # terminal 2 — worker make run-worker ``` Set `REDIS_ADDR` if Redis isn't on `localhost:6379`. ### Trying it out ```bash curl -X POST http://localhost:3000/enqueue \ -H "Content-Type: application/json" \ -d '{"user_id":"42","email":"jane@example.com"}' # {"enqueued":true,"task_id":"...","queue":"default"} ``` The worker terminal logs: ```text sending welcome email for user 42 ``` A request with a missing or invalid body returns `400`. ### Notes - The handler returns an error to signal a retry; returning `asynq.SkipRetry` (as it does for a malformed payload) tells Asynq not to bother retrying something that will never succeed. - Enqueue options like `asynq.MaxRetry` and `asynq.Queue` are set per call, so different endpoints can enqueue onto different queues with different retry policies. --- ## Auth + Docker + Postgres + JWT ## Auth Docker Postgres JWT Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/auth-docker-postgres-jwt) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/auth-docker-postgres-jwt) This example demonstrates a boilerplate setup for a Go Fiber application that uses Docker, PostgreSQL, and JWT for authentication. ### Description This project provides a starting point for building a web application with user authentication using JWT. It leverages Docker for containerization and PostgreSQL as the database. ### Requirements - [Docker](https://www.docker.com/get-started) - [Docker Compose](https://docs.docker.com/compose/install/) - [Go](https://golang.org/dl/) 1.21 or higher ### Setup 1. Clone the repository: ```bash git clone https://github.com/gofiber/recipes.git cd recipes/auth-docker-postgres-jwt ``` 2. Set the environment variables in a `.env` file (see `.env.example` for reference): ```env DB_PORT=5432 DB_USER=example_user DB_PASSWORD=example_password DB_NAME=example_db SECRET=example_secret ``` 3. Build and start the Docker containers: ```bash docker-compose build docker-compose up ``` The API and the database should now be running. ### Database Management You can manage the database via `psql` with the following command: ```bash docker-compose exec db psql -U ``` Replace `` with the value from your `.env` file. ### API Endpoints The following endpoints are available in the API: - **POST /api/user**: Register a new user. - **POST /api/auth/login**: Authenticate a user and return a JWT. - **GET /api/user/:id**: Get a user (requires a valid JWT). - **PATCH /api/user/:id**: Update a user (requires a valid JWT). - **DELETE /api/user/:id**: Delete a user (requires a valid JWT). ### Example Usage 1. Register a new user: ```bash curl -X POST http://localhost:3000/api/user -d '{"username":"testuser", "password":"testpassword", "email": "test@email.com"}' -H "Content-Type: application/json" ``` 2. Login to get a JWT: ```bash curl -X POST http://localhost:3000/api/auth/login -d '{"username":"testuser", "password":"testpassword"}' -H "Content-Type: application/json" ``` 3. Access a protected route: ```bash curl -H "Authorization: Bearer " http://localhost:3000/api/user/1 ``` Replace `` with the token received from the login endpoint. ### Conclusion This example provides a basic setup for a Go Fiber application with Docker, PostgreSQL, and JWT authentication. It can be extended and customized further to fit the needs of more complex applications. ### References - [Fiber Documentation](https://docs.gofiber.io) - [Docker Documentation](https://docs.docker.com) - [PostgreSQL Documentation](https://www.postgresql.org/docs/) - [JWT Documentation](https://jwt.io/introduction/) --- ## Auth + JWT ## Auth JWT Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/auth-jwt) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/auth-jwt) This example demonstrates a boilerplate setup for a Go Fiber application that uses JWT for authentication. ### Description This project provides a starting point for building a web application with user authentication using JWT. It leverages Fiber for the web framework and GORM for ORM. ### Requirements - [Go](https://golang.org/dl/) 1.18 or higher - [Git](https://git-scm.com/downloads) ### Setup 1. Clone the repository: ```bash git clone https://github.com/gofiber/recipes.git cd recipes/auth-jwt ``` 2. Set the environment variables in a `.env` file (see `.env.sample`): ```env DB_HOST=localhost DB_PORT=5432 DB_USER=example_user DB_PASSWORD=example_password DB_NAME=example_db DB_SSLMODE=disable SECRET=example_secret ACCESS_TOKEN_TTL_MINUTES=15 ``` > **Note:** `DB_SSLMODE` controls the PostgreSQL SSL connection mode (`disable`, `require`, `verify-full`, etc.). Set to `require` or `verify-full` in production. 3. Install the dependencies: ```bash go mod download ``` 4. Run the application: ```bash go run main.go ``` The API should now be running on `http://localhost:3000`. ### Database Management You can manage the database via `psql` with the following command: ```bash psql -U -d -h localhost -p ``` Replace ``, ``, and `` with the values from your `.env` file. ### API Endpoints The following endpoints are available in the API: - **POST /api/auth/register**: Register a new user. - **POST /api/auth/login**: Authenticate a user and return a JWT. - **POST /api/auth/logout**: Logout a user and revoke their JWT. - **POST /api/auth/refresh-token**: Refreshes a user token and returns a JWT. - **GET /api/users/:id**: Get a user (requires a valid JWT). - **PATCH /api/users/:id**: Update a user (requires a valid JWT). - **DELETE /api/users/:id**: Delete a user (requires a valid JWT). - **GET /api/products**: Get all products. - **GET /api/products/:id**: Get a product. - **POST /api/products**: Create a new product (requires a valid JWT). - **DELETE /api/products/:id**: Delete a product (requires a valid JWT). ### Example Usage 1. Register a new user: ```bash curl -X POST http://localhost:3000/api/auth/register -d '{"username":"testuser", "password":"testpassword", "email":"test@example.com"}' -H "Content-Type: application/json" ``` 2. Login to get a JWT: ```bash curl -X POST http://localhost:3000/api/auth/login -d '{"email":"test@example.com", "password":"testpassword"}' -H "Content-Type: application/json" ``` 3. Access a protected route: ```bash curl -H "Authorization: Bearer " http://localhost:3000/api/users/1 ``` Replace `` with the token received from the login endpoint. ### Conclusion This example provides a basic setup for a Go Fiber application with JWT authentication. It can be extended and customized further to fit the needs of more complex applications. ### References - [Fiber Documentation](https://docs.gofiber.io) - [GORM Documentation](https://gorm.io/docs/) - [JWT Documentation](https://jwt.io/introduction/) --- ## Autocert ## Autocert Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/autocert) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/autocert) This example demonstrates how to set up a secure Go Fiber application using Let's Encrypt for automatic TLS certificate management with `autocert`. ### Description This project provides a starting point for building a secure web application with automatic TLS certificate management using Let's Encrypt. It leverages Fiber for the web framework and `autocert` for certificate management. ### Requirements - [Go](https://golang.org/dl/) 1.21 or higher - [Git](https://git-scm.com/downloads) - A publicly accessible domain name pointing to your server - Ports 80 and 443 open and accessible from the internet (required by Let's Encrypt) ### Setup 1. Clone the repository: ```bash git clone https://github.com/gofiber/recipes.git cd recipes/autocert ``` 2. Install the dependencies: ```bash go mod download ``` 3. Update the `HostPolicy` in `main.go` with your domain: ```go m := &autocert.Manager{ Prompt: autocert.AcceptTOS, HostPolicy: autocert.HostWhitelist("yourdomain.com"), // Replace with your domain Cache: autocert.DirCache("./certs"), } ``` 4. Run the application: ```bash go run main.go ``` The application should now be running on `https://yourdomain.com`. ### Example Usage 1. Open your browser and navigate to `https://yourdomain.com` (replace with your actual domain). 2. You should see the message: `This is a secure server 👮`. ### Conclusion This example provides a basic setup for a Go Fiber application with automatic TLS certificate management using Let's Encrypt. It can be extended and customized further to fit the needs of more complex applications. ### References - [Fiber Documentation](https://docs.gofiber.io) - [Let's Encrypt Documentation](https://letsencrypt.org/docs/) - [Autocert Documentation](https://pkg.go.dev/golang.org/x/crypto/acme/autocert) --- ## AWS Elastic Beanstalk ## AWS Elastic Beanstalk Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/aws-eb) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/aws-eb) This example demonstrates how to deploy a Go Fiber application to AWS Elastic Beanstalk. ### Description This project provides a starting point for deploying a Go Fiber application to AWS Elastic Beanstalk. It includes necessary configuration files and scripts to build and deploy the application. ### Requirements - [AWS CLI](https://aws.amazon.com/cli/) - [Elastic Beanstalk CLI](https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/eb-cli3-install.html) - [Go](https://golang.org/dl/) 1.25 or higher - [Git](https://git-scm.com/downloads) ### Setup 1. Clone the repository: ```bash git clone https://github.com/gofiber/recipes.git cd recipes/aws-eb ``` 2. Initialize Elastic Beanstalk: ```bash eb init ``` 3. Create an Elastic Beanstalk environment: ```bash eb create ``` 4. Deploy the application: ```bash eb deploy ``` ### Build Process The build process is defined in the `Buildfile` and `build.sh` scripts. - `Buildfile`: ```ruby make: ./build.sh ``` - `build.sh`: ```bash #!/bin/bash -xe # Get dependencies go mod download # Build the binary go build -o application application.go # Modify permissions to make the binary executable. chmod +x application ``` ### Application Code The main application code is in `application.go`: ```go package main import ( "log" "os" "github.com/gofiber/fiber/v3" ) func main() { // Initialize the application app := fiber.New() // Hello, World! app.Get("/", func(c fiber.Ctx) error { return c.SendString("Hello, World!") }) // Listen and Serve on 0.0.0.0:$PORT port := os.Getenv("PORT") if port == "" { port = "5000" } log.Fatal(app.Listen(":" + port)) } ``` ### .gitignore The `.gitignore` file includes configurations to ignore Elastic Beanstalk specific files: ```plaintext # Elastic Beanstalk Files .elasticbeanstalk/* !.elasticbeanstalk/*.cfg.yml !.elasticbeanstalk/*.global.yml ``` ### Conclusion This example provides a basic setup for deploying a Go Fiber application to AWS Elastic Beanstalk. It can be extended and customized further to fit the needs of more complex applications. ### References - [AWS Elastic Beanstalk Documentation](https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/Welcome.html) - [Fiber Documentation](https://docs.gofiber.io) --- ## AWS SAM Container [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/aws-sam-container) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/aws-sam-container) This is a sample template for app - Below is a brief explanation of what we have generated for you: ```bash . ├── README.md <-- This instructions file ├── app <-- Source code for a lambda function │ ├── main.go <-- Lambda function code │ └── Dockerfile <-- Dockerfile ├── samconfig.toml <-- SAM CLI configuration file └── template.yaml ``` ### Features - [x] Use distroless image to build, The image size is only a few MB. - [x] Migrate to AWS SAM without changing your faber code using [aws-lambda-adapter](https://github.com/awslabs/aws-lambda-web-adapter). ### Requirements * AWS CLI already configured with Administrator permission * [Docker installed](https://www.docker.com/community-edition) * SAM CLI - [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) You may need the following for local testing. * [Golang](https://golang.org) ### Setup process #### Installing dependencies & building the target In this example we use the built-in `sam build` to build a docker image from a Dockerfile and then copy the source of your application inside the Docker image. Read more about [SAM Build here](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-build.html) #### Local development **Invoking function locally through local API Gateway** ```bash docker run -it -p 80:3000 lambdafunction curl http://localhost Hello, World! ``` ### Packaging and deployment ```bash sam deploy --guided ``` The command will package and deploy your application to AWS, with a series of prompts: * **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. * **AWS Region**: The AWS region you want to deploy your app to. * **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. * **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modifies IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. * **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. You can find your API Gateway Endpoint URL in the output values displayed after deployment. ### Add Permission to the Lambda Function for Public Access After deploying your Lambda function with an associated function URL, you might encounter a scenario where the function URL is not accessible due to missing permissions for public access. This is common when the authentication type for the function URL is set to "None," indicating that the function is intended to be publicly accessible without authentication. To ensure your Lambda function URL can be invoked publicly, you need to add the necessary permission that allows unauthenticated requests. This step is crucial when your function URL's authentication type is "None" but lacks the requisite permissions for public invocation. Manually Configuring Permissions You can manually configure permissions through the AWS Lambda console by creating a resource-based policy that grants the lambda:invokeFunctionUrl permission to all principals (*). This approach is straightforward but not suitable for automation within deployment pipelines. Automating Permission Configuration For a more automated approach, especially useful in CI/CD pipelines, you can use the AWS CLI or SDKs to add the necessary permissions after deploying your Lambda function. This can be incorporated into your deployment scripts or CI/CD workflows. Here is an example AWS CLI command that adds the required permission for public access to your Lambda function URL: ```shell aws lambda add-permission \ --function-name \ --action lambda:InvokeFunctionUrl \ --principal "*" \ --function-url-auth-type "NONE" \ --statement-id unique-statement-id ``` This command grants permission to all principals (*) to invoke your Lambda function URL, enabling public access as intended. ## Appendix #### Golang installation Please ensure Go 1.25 (or newer in the Go 1.x series) is installed as per the instructions on the official golang website: https://golang.org/doc/install A quickstart way would be to use Homebrew, chocolatey or your linux package manager. ##### Homebrew (Mac) Issue the following command from the terminal: ```shell brew install golang ``` If it's already installed, run the following command to ensure it's the latest version: ```shell brew update brew upgrade golang ``` ##### Chocolatey (Windows) Issue the following command from the powershell: ```shell choco install golang ``` If it's already installed, run the following command to ensure it's the latest version: ```shell choco upgrade golang ``` --- ## AWS SES Email Sender [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/aws-ses-sender) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/aws-ses-sender) This is an AWS SES-based Golang email delivery service that extracts only the basic sending functionality from [my open-source project](https://github.com/lee-lou2/aws-ses-sender-go). ### Features - [x] Email sending using AWS SES - [x] Designed with daily limits and per-second sending rates in mind - [x] Scheduled sending and message grouping - [x] Email open tracking and result collection - [x] View daily sending counts and delivery results by message group ### Flowchart ```mermaid flowchart TD A[Client] -->|Email Send Request| B[API Server] B -->|DB Storage| C[Scheduler] C -->|Query Pending| D[Sender] D -->|SES Send| E[AWS SES] E -->|Email Received| F[Recipient] E -->|SNS Callback| G[API Server] G -->|DB Storage| H[Results/Statistics] F -->|Open Tracking| I[API Server] I -->|DB Storage| J[Open Events] H -->|Stats/Results Query| K[Client] J -->|Stats/Results Query| K[Client] ``` ### Requirements #### Essential Requirements - Go 1.25 or higher - AWS account and configuration - AWS SES service activated - Sender email or domain verification completed - IAM user with SES permissions - AWS Access Key and Secret Key - PostgreSQL 14.0 or higher - (Optional) Docker #### AWS SES Configuration 1. Verify sender email/domain in AWS SES console 2. Request removal from SES sandbox mode (for production) 3. Create SNS topic and set up SES feedback notifications 4. Grant following permissions to IAM user - `ses:SendEmail` - `ses:SendRawEmail` - `sns:Publish` (if using SNS for delivery notifications) - `sns:Subscribe` (if using SNS for delivery notifications) ### Project Structure ```plaintext aws-ses-sender/ ├── main.go # Application entry point ├── api/ # HTTP API related code │ ├── handler.go # API handler functions │ ├── route.go # API routing configuration │ ├── server.go # HTTP server setup/execution │ └── middlewares.go # API authentication middleware ├── cmd/ # Background job code │ ├── scheduler.go # Pending email scheduler │ └── sender.go # SES email sending processor ├── config/ # Application settings │ ├── env.go # Environment variable management │ └── db.go # Database connection settings ├── model/ # Database models │ └── email.go # GORM model definitions └── pkg/aws/ # AWS service integration └── ses.go # SES email sending ``` ### Setup #### Prerequisites - Go language development environment - AWS account and SES service configuration - Sender email/domain verification - IAM user creation with SES permissions - PostgreSQL database - (Optional) Sentry DSN #### Configuration Create a `.env` file in the project root and set the following environment variables: ```env # AWS Related AWS_ACCESS_KEY_ID=your_access_key AWS_SECRET_ACCESS_KEY=your_secret_key AWS_REGION=ap-northeast-2 EMAIL_SENDER=sender@example.com # Server and API SERVER_PORT=3000 API_KEY=your_api_key SERVER_HOST=http://localhost:3000 # Database (PostgreSQL) DB_HOST=localhost DB_PORT=5432 DB_USER=postgres DB_PASSWORD=postgres DB_NAME=postgres # Sending rate per second EMAIL_RATE=14 # Sentry (Optional) SENTRY_DSN=your_sentry_dsn ``` #### Installation and Execution 1. Clone repository: ```bash git clone cd aws-ses-sender ``` 2. Install dependencies: ```bash go mod tidy ``` 3. Run application: ```bash go run main.go ``` ### API Endpoints #### Email Sending Request ```http POST /v1/messages ``` Request body example: ```json { "messages": [ { "topicId": "promotion-event-2024", "emails": ["recipient1@example.com", "recipient2@example.com"], "subject": "Special Promotion Notice", "content": "

Hello!

Check out our special promotion details.

", "scheduledAt": "2024-12-25T10:00:00+09:00" } ] } ``` #### View Topic Sending Statistics ```http GET /v1/topics/:topicId ``` #### Email Open Tracking ```http GET /v1/events/open?requestId={requestId} ``` #### View Sending Statistics ```http GET /v1/events/counts/sent?hours={hours} ``` #### Receive Sending Results (AWS SNS) ```http POST /v1/events/results ``` --- ## Bootstrap [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/bootstrap) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/bootstrap) Fiber bootstrap for rapid development using Go-Fiber / Gorm / Validator. ### Components * Fiber * Html Engine Template * Logger * Monitoring * Gorm * PGSQL Driver * Validator * Env File ### Router API Router `/api` with rate limiter middleware Http Router `/` with CORS and CSRF middleware ### Setup 1. Copy the example env file over: ``` cp .env.example .env ``` 2. Modify the env file you just copied `.env` with the correct credentials for your database. Make sure the database you entered in `DB_NAME` has been created. 3. Run the API: ``` go run main.go ``` Your api should be running at `http://localhost:4000/` if the port is in use you may modify it in the `.env` you just created. --- ## Clean Architecture ## Clean Architecture Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/clean-architecture) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/clean-architecture) This example demonstrates a Go Fiber application following the principles of Clean Architecture. ### Description This project provides a starting point for building a web application with a clean architecture. It leverages Fiber for the web framework, MongoDB for the database, and follows the Clean Architecture principles to separate concerns and improve maintainability. ### Requirements - [Go](https://golang.org/dl/) 1.18 or higher - [MongoDB](https://www.mongodb.com/try/download/community) - [Git](https://git-scm.com/downloads) ### Project Structure - `api/`: Contains the HTTP handlers, routes, and presenters. - `pkg/`: Contains the core business logic and entities. - `cmd/`: Contains the main application entry point. ### Setup 1. Clone the repository: ```bash git clone https://github.com/gofiber/recipes.git cd recipes/clean-architecture ``` 2. Set the environment variables in a `.env` file (see `.env.example`): ```env MONGO_URI=mongodb://localhost:27017 ``` 3. Install the dependencies: ```bash go mod download ``` 4. Run the application: ```bash go run main.go ``` The API should now be running on `http://localhost:3000`. ### API Endpoints The following endpoints are available in the API: - **GET /books**: List all books. - **POST /books**: Add a new book. - **PUT /books**: Update an existing book. - **DELETE /books**: Remove a book. ### Example Usage 1. Add a new book: ```bash curl -X POST http://localhost:3000/books -d '{"title":"Book Title", "author":"Author Name"}' -H "Content-Type: application/json" ``` 2. List all books: ```bash curl http://localhost:3000/books ``` 3. Update a book: ```bash curl -X PUT http://localhost:3000/books -d '{"id":"", "title":"Updated Title", "author":"Updated Author"}' -H "Content-Type: application/json" ``` 4. Remove a book: ```bash curl -X DELETE http://localhost:3000/books -d '{"id":""}' -H "Content-Type: application/json" ``` Replace `` with the actual ID of the book. ### Clean Architecture Principles Clean Architecture is a software design philosophy that emphasizes the separation of concerns, making the codebase more maintainable, testable, and scalable. In this example, the Go Fiber application follows Clean Architecture principles by organizing the code into distinct layers, each with its own responsibility. #### Layers in Clean Architecture 1. **Entities (Core Business Logic)** - Located in the `pkg/entities` directory. - Contains the core business logic and domain models, which are independent of any external frameworks or technologies. 2. **Use Cases (Application Logic)** - Located in the `pkg/book` directory. - Contains the application-specific business rules and use cases. This layer orchestrates the flow of data to and from the entities. 3. **Interface Adapters (Adapters and Presenters)** - Located in the `api` directory. - Contains the HTTP handlers, routes, and presenters. This layer is responsible for converting data from the use cases into a format suitable for the web framework (Fiber in this case). 4. **Frameworks and Drivers (External Interfaces)** - Located in the `cmd` directory. - Contains the main application entry point and any external dependencies like the web server setup. #### Example Breakdown - **Entities**: The `entities.Book` struct represents the core business model for a book. - **Use Cases**: The `book.Service` interface defines the methods for interacting with books, such as `InsertBook`, `UpdateBook`, `RemoveBook`, and `FetchBooks`. - **Interface Adapters**: The `handlers` package contains the HTTP handlers that interact with the `book.Service` to process HTTP requests and responses. - **Frameworks and Drivers**: The `cmd/main.go` file initializes the Fiber application and sets up the routes using the `routes.BookRouter` function. #### Code Example ##### `entities/book.go` ```go package entities import "go.mongodb.org/mongo-driver/bson/primitive" type Book struct { ID primitive.ObjectID `json:"id" bson:"_id,omitempty"` Title string `json:"title"` Author string `json:"author"` } ``` ##### `pkg/book/service.go` ```go package book import "clean-architecture/pkg/entities" type Service interface { InsertBook(book *entities.Book) (*entities.Book, error) UpdateBook(book *entities.Book) (*entities.Book, error) RemoveBook(ID string) error FetchBooks() (*[]entities.Book, error) } ``` ##### `api/handlers/book_handler.go` ```go package handlers import ( "clean-architecture/pkg/book" "clean-architecture/pkg/entities" "clean-architecture/api/presenter" "github.com/gofiber/fiber/v3" "net/http" "errors" ) func AddBook(service book.Service) fiber.Handler { return func(c fiber.Ctx) error { var requestBody entities.Book err := c.Bind().Body(&requestBody) if err != nil { c.Status(http.StatusBadRequest) return c.JSON(presenter.BookErrorResponse(err)) } if requestBody.Author == "" || requestBody.Title == "" { c.Status(http.StatusBadRequest) return c.JSON(presenter.BookErrorResponse(errors.New("Please specify title and author"))) } result, err := service.InsertBook(&requestBody) if err != nil { c.Status(http.StatusInternalServerError) return c.JSON(presenter.BookErrorResponse(err)) } return c.JSON(presenter.BookSuccessResponse(result)) } } ``` ##### `main.go` ```go package main import ( "clean-architecture/api/routes" "clean-architecture/pkg/book" "github.com/gofiber/fiber/v3" ) func main() { app := fiber.New() bookRepo := book.NewRepo(collection) bookService := book.NewService(bookRepo) routes.BookRouter(app, bookService) app.Listen(":3000") } ``` By following Clean Architecture principles, this example ensures that each layer is independent and can be modified or replaced without affecting the other layers, leading to a more maintainable and scalable application. ### Conclusion This example provides a basic setup for a Go Fiber application following Clean Architecture principles. It can be extended and customized further to fit the needs of more complex applications. ### References - [Fiber Documentation](https://docs.gofiber.io) - [MongoDB Documentation](https://docs.mongodb.com/) - [Clean Architecture](https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html) --- ## Clean Code ## Clean Code Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/clean-code) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/clean-code) This is an example of a RESTful API built using the Fiber framework (https://gofiber.io/) and PostgreSQL as the database. ### Description of Clean Code Clean code is a philosophy and set of practices aimed at writing code that is easy to understand, maintain, and extend. Key principles of clean code include: - **Readability**: Code should be easy to read and understand. - **Simplicity**: Avoid unnecessary complexity. - **Consistency**: Follow consistent coding standards and conventions. - **Modularity**: Break down code into small, reusable, and independent modules. - **Testability**: Write code that is easy to test. This Fiber app is a good example of clean code because: - **Modular Structure**: The code is organized into distinct modules, making it easy to navigate and understand. - **Clear Separation of Concerns**: Different parts of the application (e.g., routes, handlers, services) are clearly separated, making the codebase easier to maintain and extend. - **Error Handling**: Proper error handling is implemented to ensure the application behaves predictably. ### Start 1. Build and start the containers: ```sh docker compose up --build ``` 1. The application should now be running and accessible at `http://localhost:3000`. ### Endpoints - `GET /api/v1/books`: Retrieves a list of all books. ```sh curl -X GET http://localhost:3000/api/v1/books ``` - `POST /api/v1/books`: Adds a new book to the collection. ```sh curl -X POST http://localhost:3000/api/v1/books \ -H "Content-Type: application/json" \ -d '{"title":"Title"}' ``` --- ## Cloud Run ## Cloud Run Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/cloud-run) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/cloud-run) This example demonstrates how to deploy a Go Fiber application to Google Cloud Run. ### Description This project provides a starting point for deploying a Go Fiber application to Google Cloud Run. It includes necessary configuration files and scripts to build and deploy the application using Docker and Google Cloud Build. ### Requirements - [Go](https://golang.org/dl/) 1.18 or higher - [Docker](https://www.docker.com/get-started) - [Google Cloud SDK](https://cloud.google.com/sdk/docs/install) - [Git](https://git-scm.com/downloads) ### GCP Prerequisites Before deploying, ensure the following GCP APIs are enabled and IAM roles are granted: **Enable APIs:** ```bash gcloud services enable run.googleapis.com \ cloudbuild.googleapis.com \ containerregistry.googleapis.com ``` **IAM roles required for the Cloud Build service account (`[PROJECT_NUMBER]@cloudbuild.gserviceaccount.com`):** - `roles/run.admin` — deploy Cloud Run services - `roles/iam.serviceAccountUser` — act as the Cloud Run runtime service account - `roles/storage.admin` — push images to Container Registry ### Setup 1. Clone the repository: ```bash git clone https://github.com/gofiber/recipes.git cd recipes/cloud-run ``` 2. Install the dependencies: ```bash go mod download ``` 3. Build the Docker image: ```bash docker build -t cloud-run-example . ``` 4. Run the Docker container: ```bash docker run -p 3000:3000 cloud-run-example ``` The application should now be running on `http://localhost:3000`. > **PORT environment variable:** Cloud Run injects the `PORT` env variable at runtime. The application reads `PORT` and falls back to `3000` if unset, so it works both locally and on Cloud Run without any code changes. ### Deploy to Google Cloud Run 1. Set up Google Cloud SDK and authenticate: ```bash gcloud auth login gcloud config set project [YOUR_PROJECT_ID] ``` 2. Build and push the Docker image using Google Cloud Build: ```bash gcloud builds submit --tag gcr.io/[YOUR_PROJECT_ID]/cloud-run-example ``` 3. Deploy the image to Cloud Run: ```bash gcloud run deploy cloud-run-example --image gcr.io/[YOUR_PROJECT_ID]/cloud-run-example --platform managed --region [YOUR_REGION] --allow-unauthenticated ``` > **Note:** `--allow-unauthenticated` makes the service publicly accessible. Remove this flag in production and use IAM-based access control instead. Replace `[YOUR_PROJECT_ID]` and `[YOUR_REGION]` with your Google Cloud project ID and desired region. ### Cloud Build Configuration The `cloudbuild.yaml` file defines the steps to build and deploy the application using Google Cloud Build: ```yaml steps: - name: 'gcr.io/kaniko-project/executor:latest' id: 'build-and-push' args: - '--destination=asia.gcr.io/$PROJECT_ID/$_SERVICE_NAME:$SHORT_SHA' - '--destination=asia.gcr.io/$PROJECT_ID/$_SERVICE_NAME:latest' - '--dockerfile=Dockerfile' - '--context=.' - '--cache=true' - '--cache-ttl=120h' - id: 'Deploy to Cloud Run' name: 'gcr.io/cloud-builders/gcloud' entrypoint: 'bash' args: - '-c' - | gcloud run deploy $_SERVICE_NAME \ --image=asia.gcr.io/$PROJECT_ID/$_SERVICE_NAME:$SHORT_SHA \ --region=$_REGION --platform managed \ --allow-unauthenticated \ --port=3000 # NOTE: --allow-unauthenticated is for demo purposes only. # Remove this flag in production and use IAM-based access control instead. options: substitutionOption: ALLOW_LOOSE substitutions: _SERVICE_NAME: cloud-run-example _REGION: asia-southeast1 ``` ### Example Usage 1. Open your browser and navigate to the Cloud Run service URL provided after deployment. 2. You should see the message: `Hello, World!`. ### Conclusion This example provides a basic setup for deploying a Go Fiber application to Google Cloud Run. It can be extended and customized further to fit the needs of more complex applications. ### References - [Google Cloud Run Documentation](https://cloud.google.com/run/docs) - [Fiber Documentation](https://docs.gofiber.io) - [Docker Documentation](https://docs.docker.com/) --- ## Cloudflare Container Workers with Go Fiber [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/cloudflare-workers) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/cloudflare-workers) This example demonstrates how to use [Go Fiber v3](https://github.com/gofiber/fiber) with [Cloudflare Container Workers](https://developers.cloudflare.com/containers/). ### Features - **Go Fiber v3** framework - **Distroless container** for minimal attack surface (`gcr.io/distroless/static-debian12`) - **JSON API** response - **Environment variables** support - **Logger and Recover** middleware ### Prerequisites - Bun - Go 1.25+ - Wrangler CLI - Cloudflare account with Container Workers access ### Getting Started 1. Install dependencies: ```bash bun install ``` 2. Run locally: ```bash bun run dev ``` 3. Deploy to Cloudflare: ```bash bun run deploy ``` ### Project Structure ```text . ├── src/index.ts # Worker entry point ├── container_src/ │ ├── main.go # Go Fiber application │ ├── go.mod # Go module file │ └── go.sum # Go dependencies ├── Dockerfile # Container configuration └── wrangler.jsonc # Cloudflare Workers configuration ``` ### How it Works 1. The Worker (TypeScript) receives HTTP requests. 2. Requests are forwarded to the Go Fiber container. 3. The container responds with JSON data, including environment variables. ### Container Configuration The container is configured with: - 2-minute sleep timeout for inactivity - Environment variable `MESSAGE` passed from the container class - Port 8080 (default) ### Learn More - [Fiber Documentation](https://docs.gofiber.io/) - [Cloudflare Container Workers](https://developers.cloudflare.com/containers/) - [Cloudflare Workers](https://developers.cloudflare.com/workers/) --- ## Colly Gorm ## Simple Web Scraping Colly App with Fiber [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/colly-gorm) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/colly-gorm) A Go application using [Fiber](https://gofiber.io), [Colly v2](https://go-colly.org/), and [GORM](https://gorm.io) to scrape websites and persist data in PostgreSQL. ### Prerequisites - [Docker](https://docs.docker.com/get-docker/) and Docker Compose ### How to Run 1. Clone the repository. 2. Navigate to the project directory: `cd colly-gorm` 3. Copy the example env file: `cp app/app.env.example app/app.env` 4. Start the stack: `docker compose up --build` ### Project Structure ``` colly-gorm/ ├── app/ │ ├── app.env.example # Environment variable template │ ├── Dockerfile │ ├── go.mod │ ├── cmd/ │ │ └── api/ │ │ └── main.go # App entry point, Fiber routes │ └── internals/ │ ├── consts/ │ │ └── consts.go # Config loading via Viper │ └── services/ │ ├── database/ │ │ ├── database.go # GORM connection │ │ └── models.go # Quote and Course models │ └── scrapers/ │ ├── toscrape.go # Quotes scraper │ └── coursera_courses.go # Coursera scraper ├── db/ │ └── create_db.sql # DB initialization └── docker-compose.yml ``` ### API Endpoints | Method | Path | Description | |--------|------|-------------| | `GET` | `/api/healthchecker` | Health check — returns service status | | `GET` | `/scrape/quotes` | Triggers async scraping of [quotes.toscrape.com](http://quotes.toscrape.com) and stores results in PostgreSQL | | `GET` | `/scrape/coursera` | Triggers async scraping of [coursera.org/browse](https://www.coursera.org/browse) and stores course data in PostgreSQL | Scraping jobs run asynchronously; the endpoint returns immediately while scraping continues in the background. ### Database Models **Quote** - `author` — quote author - `quote` — quote text **Course** - `title`, `description`, `creator`, `url`, `rating` ### Environment Variables See `app/app.env.example`: ```env POSTGRES_HOST=colly_db POSTGRES_PORT=5432 POSTGRES_USER=postgres POSTGRES_PASSWORD=postgres POSTGRES_DB=colly ``` ### What It Does - Registers Colly HTML callbacks before visiting pages (correct callback order). - Scrapes data from websites and stores it in a PostgreSQL database via GORM. - Uses Fiber middleware (logger, CORS) applied globally before sub-app routing. --- ## CSRF (Csrf) ## CSRF Examples [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/csrf) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/csrf) Example Cross Site Request Forgery (CSRF) vulnerabilities in action. ### Requirements * [git](https://git-scm.com/downloads) * [Golang](https://golang.org/) ### Install Go Modules Like any golang project, you will need to download and install the required modules for the project to run. Change into the "csrf" directory: ```bash cd csrf ``` And then: ```bash go mod vendor && go mod download && go mod tidy ``` This command installs the golang dependencies needed to run the project in a new directory named `vendor`. Once the modules have finished installing, you can run the project like this: ```bash go run main.go ``` OR ```bash go run main.go withoutCsrf ``` You should see the following if everything is OK: ``` Server started and listening at localhost:3000 ``` ### Try the demo Start the server without csrf, to see the dangers of these attacks ```bash go run main.go withoutCsrf ``` Open your browser to and navigate to [localhost:3000](http://localhost:3000). Login using the test account: * Username: `bob` * Password: `test` In a new tab, navigate to [localhost:3001](http://localhost:3001) to view some examples of CSRF exploits. You will notice that the balance goes down everytime you load that page. This is because the page is successfully exploiting a CSRF vulnerability. ### See the "fixed" version To see the csrf version of this demo, just stop the server by pressing __CTRL + C__ to kill the server process and then run ```bash go run main.go ``` Navigate again to [localhost:3000](http://localhost:3000) and login to the test account. And once more try the page with the CSRF exploits: [localhost:3001](http://localhost:3001). You will notice now that the account balance is unchanged. ### Going further Here are some useful links where you can learn more about this topic: * https://en.wikipedia.org/wiki/Cross-site_request_forgery * https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF) --- ## CSRF + Session ## CSRF-with-session Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/csrf-with-session) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/csrf-with-session) Example GoFiber web app using Cross Site Request Forgery (CSRF) middleware with session. This example impliments multiple best-practices for CSRF protection: - CSRF Tokens are linked to the user's session. - Pre-sessions are used, so that CSRF tokens are always available, even for anonymous users (eg for login forms). - Cookies are set with a defense-in-depth approach: - Secure: true - HttpOnly: true - SameSite: Lax - Expiration: 30 minutes (of inactivity) - Cookie names are prefixed with "__Host-" (see [MDN-Set-Cookie](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie) for more information)) ### Requirements * [git](https://git-scm.com/downloads) * [Golang](https://golang.org/) ### Install Go Modules Like any golang project, you will need to download and install the required modules for the project to run. Change into the "csrf-with-session" directory: ```bash cd csrf-with-session ``` And then: ```bash go mod vendor && go mod download && go mod tidy ``` This command installs the golang dependencies needed to run the project in a new directory named `vendor`. Once the modules have finished installing, you can run the project like this: ```bash go run main.go ``` You should see the following if everything is OK: ``` Server started and listening at 127.0.0.1:8443 ``` ### Try the demo Start the server by running: ```bash go run main.go ``` Open your browser to and navigate to [127.0.0.1:8443](http://127.0.0.1:8443). #### Accept the self-signed certificate warning and visit the site. In Chrome: - Click on "Advanced" - Click on "Proceed to 127.0.0.1:8443 (unsafe)" In Firefox: - Click on "Advanced" - Click on "Accept the Risk and Continue" In Safari: - Click on "Show Details" - Click on "visit this website" #### Try to access the /protected page Login using one of the test accounts: * Username: `user1` * Password: `password1` OR * Username: `user2` * Password: `password2` Once logged in, you will be able to see the /protected page. #### Submit the form on the /protected page Once logged in, you will be able to see the /protected page. The /protected page contains a form that submits to the /protected page. If you try to submit the form without a valid CSRF token, you will get a 403 Forbidden error. ### CSRF Protection All methods except GET, HEAD, OPTIONS, and TRACE are checked for the CSRF token. If the token is not present or does not match the token in the session, the request is aborted with a 403 Forbidden error. ### Token Lifecycle The CSRF token is generated when the user visits any page on the site. The token is stored in the session and is valid for until it expires, or the authorization scope changes (e.g. the user logs in, or logs out). It is important that CSRF tokens do not persist beyond the scope of the user's session, that a new session is created when the user logs in, and that the session is destroyed when the user logs out. The CSRF middleware has a `SingleUseToken` configuration option that can be used to generate a new token for each request. This is useful for some applications, but is not used in this example. Single use tokens have usability implications in scenarios where the user has multiple tabs open, or when the user uses the back button in their browser. ### Session Storage Sessions are stored in memory for this example, but you can use any session store you like. See the [Fiber session documentation](https://docs.gofiber.io/api/middleware/session) for more information. #### Note on pre-sessions GoFiber's CSRF middleware will automatically create a session if one does not exist. That means that we always have pre-sessions when using the CSRF middleware. In this example we set a session variable `loggedIn` to `true` when the user logs in, in order to distinguish between logged in and logged out users. ### Going further Here are some useful links where you can learn more about this topic: * https://en.wikipedia.org/wiki/Cross-site_request_forgery * https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF) --- ## Docker + MariaDB ## Docker MariaDB Clean Architecture [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/docker-mariadb-clean-arch) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/docker-mariadb-clean-arch) A slightly complex REST application with Fiber to showcase Clean Architecture with MariaDB as a dependency with Docker. ### Prerequisites - Docker Compose for running the application. - Shell that supports `sh`, `make`, and `curl` for end-to-end testing. UNIX systems or WSL should work fine. - Postman if you want to test this API with GUI. ### Application This application is a slightly complex example of a REST API that have four major endpoints. A public user can access the `User`, `Auth`, and `Misc` major endpoints, but they cannot access the `City` endpoint (as it is protected). If one wants to access said endpoint, they have to log in first via the `Auth` endpoint, and only after that they can access the `City` endpoint. This application uses MariaDB as a database (dockerized), and JWT as an authentication mechanism. This application also showcases how to perform 1-to-many relational mapping in Clean Architecture (one user can have multiple cities), and also the implementation of `JOIN` SQL clause in Go in general. ### Clean Architecture ![Clean Architecture](./assets/CleanArchitecture.jpg) Clean Architecture is a concept introduced by Robert C. Martin or also known as Uncle Bob. Simply put, the purpose of this architecture is to perform complete separation of concerns. Systems made this way can be independent of frameworks, testable (easy to write unit tests), independent of UI, independent of database, and independent of any external agency. When you use this architecture, it is simple to change the UI, the database, or the business logic. One thing that you should keep in mind when using this architecture is about Dependency Rule. In Clean Architecture, source code dependency can only point inwards. This means that the 'inner circle' of the system cannot know at all about the outside world. For example, in the diagram above, use-cases knows about entities, but entities cannot know about use-cases. Data formats used in outer circle should not be used by an inner circle. Because of this, when you change something that is located the innermost of the circle (entities for example), usually you have to change the outer circles. However, if you change something that is not the innermost of the circle (controllers for example), you do not need to change the use-cases and the entities (you may have to change the frameworks and drivers as they are dependent on each other). If you want to learn more about Clean Architecture, please see the articles that I have attached below as references. ### System Architecture For the sake of clearness, here is the diagram that showcases the system architecture of this API. ![System Architecture](./assets/SystemArchitecture.png) Please refer to below table for terminologies / filenames for each layers that are used in this application. The project structure is referred from [this project](https://github.com/golang-standards/project-layout). In the `internal` package, there are packages that are grouped according to their functional responsibilities. If you open the package, you will see the files that represents the Clean Architecture layers. For the dependency graph, it is straightforward: handler/middleware depends on service, service depends on repository, and repository depends on domain and the database (via dependency injection). All of the layers are implemented with the said infrastructure (Fiber, MariaDB, and Authentication Service) in above image. I have slightly modified the layers in this application to conform to my own taste of Clean Architecture. | Architecture Layer | Equivalent Layer | Filename | | :-----------------: | :--------------------: | :------------------------------: | | External Interfaces | Presenters and Drivers | `middleware.go` and `handler.go` | | Controllers | Business Logic | `service.go` | | Use Cases | Repositories | `repository.go` | | Entities | Entities | `domain.go` | Basically, a request will have to go through `handler.go` (and `middleware.go`) first. After that, the program will call a repository or a use-case that is requested with `service.go`. That controller (`service.go`) will call `repository.go` that conforms to the `domain.go` in order to fulfill the request that the `service.go` asked for. The result of the request will be returned back to the user by `handler.go`. In short: - `handler.go` and `middleware.go` is used to receive and send requests. - `service.go` is business-logic or controller (some might have different opinions, but this is my subjective opinion). - `repository.go` is used to interact to the database (use-case). - `domain.go` is the 'shape' of the data models that the program use. For the sake of completeness, here are the functional responsibilities of the project structure. - `internal/auth` is used to manage authentication. - `internal/city` is used to manage cities. This endpoint **is protected**. - `internal/infrastructure` is used to manage infrastructure of the application, such as MariaDB and Fiber. - `internal/misc` is used to manage miscellaneous endpoints. - `internal/user` is used to manage users. This endpoint is **not protected**. Please refer to the code itself for further details. I commented everything in the code, so I hope it is clear enough! ### API Endpoints / Features This API is divided into four 'major endpoints', which are miscellaneous, users, authentication, and cities. #### Miscellaneous Endpoints classified here are miscellaneous endpoints. - `GET /api/v1` for health check. #### Users Endpoints classified here are endpoints to perform operation on 'User' domain. - `GET /api/v1/users` to get all users. - `POST /api/v1/users` to create a user. - `GET /api/v1/users/` to get a user. - `PUT /api/v1/users/` to update a user. - `DELETE /api/v1/users/` to delete a user. #### Authentication Endpoints classified here are endpoints to perform authentication. In my opinion, this is framework-layer / implementation detail, so there is no 'domain' regarding this endpoint and you can use this endpoint as an enhancement to other endpoints. Authentication in this API is done using JSON Web Tokens. - `POST /api/v1/auth/login` to log in as the user with ID of 1 in the database. Will return JWT and said JWT will be stored in a cookie. - `POST /api/v1/auth/logout` to log out. This route removes the JWT from the cookie. - `GET /api/v1/auth/private` to access a private route which displays information about the current (valid) JWT. #### Cities Endpoints classified here are endpoints to perform operation on `City` domain. **Endpoints here are protected via JWT in the cookie**, so if you are going to use this endpoint, make sure you are logged in first (or at least have a valid JWT). - `GET /api/v1/cities` to get all cities. - `POST /api/v1/cities` to create a new city. - `GET /api/v1/cities/` to get a city. - `PUT /api/v1/cities/` to update a city. - `DELETE /api/v1/cities/` to delete a city. ### Installation In order to run this application, you just need to do the following commands. - Clone the repository. ```bash git clone git@github.com:gofiber/recipes.git ``` - Switch to this repository. ```bash cd recipes/docker-mariadb-clean-arch ``` - Run immediately with Docker. After you run this command, migration script will be automatically run to populate your dockerized MariaDB. ```bash make start ``` - Test with Postman (set the request URL to `localhost:8080`) or with the created end-to-end testing script. Keep in mind that the end-to-end script is only available for the first run. If you are trying to run it the second time, you might not be able to get all of the perfect results (because of the auto-increment in the MariaDB). Please run `make stop` and `make start` first if you want to run the test suite again. ```bash make test ``` - Teardown or stop the container. This will also delete the Docker volume created and will also delete the created image. ```bash make stop ``` You're done! ### FAQ Some frequently asked questions that I found scattered on the Internet. Keep in mind that the answers are mostly subjective. **Q: Is this the right way to do Clean Architecture?** A: Nope. There are many ways to perform clean architecture - this example being one of them. Some projects might be better than this example. **Q: Why is authentication an implementation detail?** A: Authentication is an implementation detail because it does not interact with the use-case or the repository / interface layer. Authentication is a bit strange that it can be implemented in any other routes as a middleware. Keep in mind that this is my subjective opinion. **Q: Is this the recommended way to structure Fiber projects?** A: Nope. Just like any other Gophers, I recommend you to start your project by using a single `main.go` file. Some projects do not require complicated architectures. After you start seeing the need to branch out, I recommend you to [split your code based on functional responsibilities](https://rakyll.org/style-packages/). If you need an even more strict structure, then you can try to adapt Clean Architecture or any other architectures that you see fit, such as Onion, Hexagonal, etcetera. **Q: Is this only for Fiber?** A: Nope. You can simply adjust `handler.go` and `middleware.go` files in order to change the external interfaces / presenters and drivers layer to something else. You can use `net/http`, `gin-gonic`, `echo`, and many more. If you want to change or add your database, you just need to adjust the `repository.go` file accordingly. If you want to change your business logic, simply change the `service.go` file. As long as you the separation of concerns is done well, you should have no need to change a lot of things. **Q: Is this production-ready?** A: I try to make this as production-ready as possible 😉 ### Improvements Several further improvements that could be implemented in this project: - Add more tests and mocks, especially unit tests (Clean Architecture is the best for performing unit tests). - Add more API endpoints. - Add a caching mechanism to the repository layer, such as Redis. - Add transaction support. - Maybe try to integrate S3 backend to the repository layer (MinIO is a good choice). - Maybe add a `domain` folder in the `internal` package where we can leave the entities there? ### Discussion Feel free to create an issue in this repository (or maybe ask in Fiber's Discord Server) in order to discuss this together! ### References Thanks to articles and their writers that I have read and found inspiration in! - [Clean Architecture by Angad Sharma](https://medium.com/gdg-vit/clean-architecture-the-right-way-d83b81ecac6) - [Clean Architecture by Uncle Bob](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) - [Clean Architecture with Go by Elton Minetto](https://dev.to/eminetto/clean-architecture-using-golang-5791) - [Clean Architecture with Go Part 2 by Elton Minetto](https://dev.to/eminetto/clean-architecture-2-years-later-4een) - [Creating Clean Architecture using Go by @namkount](https://hackernoon.com/creating-clean-architecture-using-golang-9h5i3wgr) - [Dive to Clean Architecture with Go by Kenta Takeuchi](https://dev.to/bmf_san/dive-to-clean-architecture-with-golang-cd4) - [Go and Clean Architecture by Reshef Sharvit](https://itnext.io/golang-and-clean-architecture-19ae9aae5683) - [Go Microservices with Clean Architecture by Jin Feng](https://medium.com/@jfeng45/go-microservice-with-clean-architecture-application-design-68f48802c8f) - [Go Project Layout Repository](https://github.com/golang-standards/project-layout) - [Trying Clean Architecture on Go by Imam Tumorang](https://hackernoon.com/golang-clean-archithecture-efd6d7c43047) --- ## Docker + Nginx [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/docker-nginx-loadbalancer) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/docker-nginx-loadbalancer) ### Features - **Docker and Nginx** : Deploy in docker using 5 replicas and load balancer with Nginx - **Logger**: The application includes a request logger for monitoring HTTP requests. ### Endpoints | Name | Rute | Parameters | State | Protected | Method | |--------------|----------| ---------- | --------- | --------- |--------| | Hello | /hello | No | Completed | No | GET | ### Getting Started To get a local copy up and running, follow these steps: 1. Clone the repository to your local machine. 2. Navigate to the project directory. 3. Build the Docker image with docker compose 4. Run the Docker compose composition ```bash docker compose up --build ``` 5. Access the application at `http://localhost:8080/hello`. --- ## Dummy JSON Proxy ## Simple Fiber Proxy Server [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/dummyjson) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/dummyjson) This is a basic Go application using the Fiber framework to create a proxy server. The server listens on port 3000 and has a single route (`GET /proxy`) that accepts an optional `?url=` query parameter, fetches data from that URL, and forwards it to the client. Without the parameter it falls back to a default upstream. > **Run this locally only.** The `url` parameter is forwarded to the HTTP client without validation, so anyone who can reach the route can make the server fetch arbitrary addresses on its behalf, including internal ones. That is a [server-side request forgery](https://owasp.org/API-Security/editions/2023/en/0xa7-server-side-request-forgery/) proxy. The example is kept minimal on purpose; before exposing anything like it, restrict the allowed schemes and hosts and reject private and loopback ranges. ### Prerequisites Ensure you have the following installed: - Go 1.25 or newer, required by Fiber v3 - [Fiber](https://github.com/gofiber/fiber) package ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/dummyjson ``` 2. Install dependencies: ```sh go get ``` ### Running the Application Start the server: ```sh go run main.go ``` The server listens on port 3000. ### Usage Call the route without parameters to use the default upstream: ```sh curl "http://localhost:3000/proxy" ``` Pass a `url` query parameter to override it: ```sh curl "http://localhost:3000/proxy?url=https://dummyjson.com/products/2" ``` Either way the server fetches the data from the external service and forwards the response to the client. #### Error Handling - Returns 500 Internal Server Error if anything goes wrong during the fetch. - Returns the same status code as the external service if it is not 200 OK. ### References - [Fiber Documentation](https://docs.gofiber.io) - [DummyJSON](https://dummyjson.com) --- ## Email Verification Service ## Email Verification Service with Fiber [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/email-verification) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/email-verification) A clean architecture based email verification service that generates and validates verification codes. ### Features - Clean Architecture implementation - In-memory verification code storage - SMTP email service integration - Code generation and hashing - Configurable code expiration - Thread-safe operations ### Project Structure ``` email-verification/ ├── api/ │ └── handlers/ # HTTP handlers ├── application/ # Application business logic ├── domain/ # Domain models and interfaces ├── infrastructure/ # External implementations │ ├── code/ # Code generation │ ├── email/ # SMTP service │ └── repository/ # Data storage └── config/ # Configuration ``` ### Configuration The application is configured via environment variables. Copy `.env.example` to `.env` and fill in your SMTP credentials: ```bash cp .env.example .env ``` | Variable | Required | Default | Description | |-------------|----------|----------------|------------------------------| | `SMTP_HOST` | No | `smtp.gmail.com` | SMTP server hostname | | `SMTP_PORT` | No | `587` | SMTP server port | | `SMTP_USER` | **Yes** | — | SMTP username / email address | | `SMTP_PASS` | **Yes** | — | SMTP password or app-password | The application exits with a fatal error on startup if `SMTP_USER` or `SMTP_PASS` are not set. ### API Endpoints | Method | URL | Description | |--------|----------------------------|--------------------------------| | POST | /verify/send/:email | Send verification code | | POST | /verify/check/:email/:code | Verify the received code | ### Example Usage 1. Send verification code: ```bash curl -X POST http://localhost:3000/verify/send/user@example.com ``` 2. Verify code: ```bash curl -X POST http://localhost:3000/verify/check/user@example.com/123456 ``` ### Response Examples Success: ```json { "message": "Code verified successfully" } ``` Error: ```json { "error": "invalid code" } ``` ### How to Run 1. Copy `.env.example` to `.env` and set your SMTP credentials. 2. Export the environment variables and run the application: ```bash export $(cat .env | xargs) go run main.go ``` ### Dependencies - [Fiber v3](https://github.com/gofiber/fiber) - Go 1.23+ --- ## Entgo ORM (MySQL) ## Example ent ORM for fiber with MySQL [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/ent-mysql) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/ent-mysql) A sample program how to connect ent ORM ### How to start (If no ent dir) Execute command first ```bash go run -mod=mod entgo.io/ent/cmd/ent new Book ``` go to `./ent/schema/book.go` and add fields(you want) to Book Schema ```go // Fields of the Book. func (Book) Fields() []ent.Field { return []ent.Field{ field.String("title").NotEmpty(), field.String("author").NotEmpty(), } } ``` Execute command ```bash go generate ./ent ``` #### Endpoints | Method | URL | Description | |--------|-------------|-----------------| | GET | /book | All Books Info | | GET | /book/:id | One Book Info | | POST | /create | One Book Add | | PUT | /update/:id | One Book Update (reads fields from query params) | | DELETE | /delete/:id | One Book Delete | --- ## Entgo Sveltekit ## Todo Application [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/entgo-sveltekit) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/entgo-sveltekit) ![image](https://github.com/ugurkorkmaz/gofiber-recipes/assets/40540244/08c6ee52-724a-4cf4-8352-9cf6f5b007ef) This Todo application is a full-stack project built using Sveltekit, Tailwind CSS, Fiber, Entgo, and SQLite. It showcases the construction of a monolithic architecture for a full-stack application. ### Run the Project To run the project, follow these steps: 1. Execute the following command to run all the necessary commands for building and running the application: ```bash go run ./bin all ``` 2. Once the build process is complete, you can start the application by running: ```bash ./app ``` ### Available Commands The following commands are available to manage the project: | Command | Description | | --- | --- | | `go-run` | Run the Golang project. | | `go-build` | Build the Golang project. | | `go-test` | Run tests for the Golang project. | | `svelte-run` | Run the SvelteKit project. | | `svelte-build` | Build the SvelteKit project. | | `generate-ent` | Generate entity files. | | `all` | Run all commands (`generate-ent`, `svelte-build`, `go-test`, `go-build`). | ### Usage To use this application, run the following command: ```bash go run ./bin ``` API Routes ---------- The Go Fiber application provides the following API routes: | Method | Endpoint | Handler Function | Description | | --- | --- | --- | --- | | GET | /api/v1/todo/list | todoHandler.GetAllTodos | Get a list of all todos | | GET | /api/v1/todo/get/:id | todoHandler.GetTodoByID | Get a specific todo by its ID | | POST | /api/v1/todo/create | todoHandler.CreateTodo | Create a new todo | | PUT | /api/v1/todo/update/:id | todoHandler.UpdateTodoByID | Update an existing todo by its ID | | DELETE | /api/v1/todo/delete/:id | todoHandler.DeleteTodoByID | Delete a todo by its ID | Go Dependencies --------------- - **Go Modules:** Go's built-in package manager used to manage dependencies for Go projects. - **Entgo:** A Golang Object Relational Mapping (ORM) tool used to define and generate database schemas. - **Fiber:** A fast and minimalist web framework for Golang. - **Sqlite:** A small, lightweight, embedded SQL database engine. Npm Dependencies ---------------- - **SvelteKit:** A JavaScript framework used to build modern web applications. - **Tailwind CSS:** A fast and customizable CSS styling library. Can be used in SvelteKit projects. ---------------- Author: [@ugurkorkmaz](https://github.com/ugurkorkmaz) --- ## SvelteKit and Tailwind CSS Project This is a SvelteKit project that utilizes Tailwind CSS for styling. SvelteKit is a framework for building modern web applications, and Tailwind CSS is a utility-first CSS framework. Together, they provide a powerful combination for creating responsive and visually appealing web interfaces. ### Available Scripts The following scripts are available in the project's `package.json` file: | Script | Description | | ------------- | ---------------------------------------------------------------------------------------- | | `dev` | Starts the development server and hot-reloads the application for a seamless development experience. | | `build` | Builds the project for production, generating optimized and minified files. | | `preview` | Starts a server to preview the production-ready build locally before deployment. | | `check` | Runs the Svelte compiler and type-checker to validate the project's TypeScript configuration. | | `check:watch` | Similar to `check`, but watches for changes and performs continuous type-checking. | ### Usage To use the available scripts, you need to have Node.js and Npm (or Pnpm) installed on your system. Follow these steps: 1. Install the project dependencies by running the following command in the project's root directory: ```bash npm install ``` or ```bash pnpm install ``` 2. Once the installation is complete, you can run the desired script using the following command: ```bash npm run (code) ``` or ```bash pnpm run (code) ``` Replace `(code)` with one of the available scripts mentioned in the table above. 3. The corresponding action will be executed, and you can see the output in the terminal. Please note that specific configurations and additional steps might be required depending on your project setup or requirements. Refer to the project documentation for more information. --- ## Envoy External Authorization ## Fiber as an Envoy External Authorization HTTP Service [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/envoy-extauthz) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/envoy-extauthz) One way of extending the popular [Envoy](https://www.envoyproxy.io) proxy is by developing an [external authorization service](https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto). This example illustrates using `fiber` and the `keyauth` middleware as an authorization service for a front proxy (the configuration could also be used for an L2 / Sidecar proxy). See `authz`. It also uses `fiber` as a sample upstream service, with the following endpoints. See `app`. ### Architecture ``` Client │ │ HTTP request (port 8000) ▼ Envoy (front-envoy) │ ├──► AuthZ service (fiber_authz :1337) │ Checks x-api-key header via keyauth middleware. │ Returns 200 OK → Envoy forwards request upstream. │ Returns 403 Forbidden → Envoy rejects request immediately. │ └──► App service (fiber_app) — only reached when AuthZ approves Serves /health (unprotected) and /api/resource (protected). ``` All three services run in the same Docker network (`envoymesh`). Envoy is the sole ingress point; the app service is never exposed directly. ### Prerequisites - [Docker](https://docs.docker.com/get-docker/) with Compose plugin (or `docker-compose` v1) ### Endpoints | Name | Rute | Protected | Method | | --------- | ------------- | --------- | ------ | | Health | /health | No | GET | | Resource | /api/resource | Yes | GET | ### Run `docker-compose up --build -d` ### Test | Name | Command | Status | | --------------- | ----------------------------------------------------------------- | ------ | | Not protected | `curl localhost:8000/health -i` | 200 | | Missing API key | `curl localhost:8000/api/resource -i` | 403 | | Invalid API key | `curl localhost:8000/api/resource -i -H "x-api-key: invalid-key"` | 403 | | Valid API key | `curl localhost:8000/api/resource -i -H "x-api-key: valid-key"` | 200 | ### Stop `docker-compose down` --- ## File Server ## File Server Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/file-server) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/file-server) This project demonstrates how to set up a simple file server in a Go application using the Fiber framework. ### Prerequisites Ensure you have the following installed: - Golang - [Fiber](https://github.com/gofiber/fiber) package ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/file-server ``` 2. Install dependencies: ```sh go get ``` ### Running the Application 1. Start the application: ```sh go run main.go ``` 2. Access the file server at `http://localhost:3000`. ### Example Here is an example `main.go` file for the Fiber application serving static files: ```go package main import ( "log" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/static" ) func main() { app := fiber.New() // Serve static files from the "files" directory app.Get("/*", static.New("./files")) log.Fatal(app.Listen(":3000")) } ``` ### References - [Fiber Documentation](https://docs.gofiber.io) - [Golang Documentation](https://golang.org/doc/) --- ## Firebase Authentication ## Go Fiber Firebase Authentication Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/firebase-auth) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/firebase-auth) This example demonstrates how to protect Fiber routes with Firebase Authentication by verifying Firebase ID tokens. ### Requirements - A Firebase project with Authentication enabled - A Google Service Account credential JSON file (download from Firebase Console → Project Settings → Service Accounts) ### Setting Up Copy `example.env` to `.env` and set the path to your service account credential file: ``` GOOGLE_SERVICE_ACCOUNT=path/to/serviceAccountKey.json ``` ### Start ```bash go run main.go ``` ### Endpoints | Method | Path | Auth required | Description | |--------|-----------------|---------------|--------------------------------------| | GET | /salut | No | Public greeting (French) | | POST | /ciao | No | Public greeting (Italian) | | GET | /salanthe | No | Public greeting (Sinhalese) | | GET | /api/hello | Yes | Protected greeting (English) | | GET | /api/ayubowan | Yes | Protected greeting with user claims | ### curl Examples #### Public endpoint ```bash curl http://localhost:3001/salut ``` #### Protected endpoint — obtain a Firebase ID token first, then: ```bash curl -H "Authorization: Bearer " \ http://localhost:3001/api/hello ``` ```bash curl -H "Authorization: Bearer " \ http://localhost:3001/api/ayubowan ``` --- ## Firebase Functions ## Deploying GoFiber Application to Firebase Functions [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/firebase-functions) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/firebase-functions) Welcome to this step-by-step guide on deploying a GoFiber application to Firebase Functions. If you’re looking to leverage the power of GoFiber, a fast and lightweight web framework for Go, and host your application on Firebase, you’re in the right place. In this tutorial, we’ll walk through the process of setting up your GoFiber app to run seamlessly on Firebase Functions. ### Prerequisites 1. Go installed on your machine. 2. Firebase CLI installed. 3. A Firebase project created. 4. Firestore and Cloud Functions enabled. ### Create a GoFiber App Start by initializing your GoFiber application. Use the following commands in your terminal: ```bash go mod init example.com/GofiberFirebaseBoilerplate ``` ### Server Configuration Create a server file `(src/server.go)` with a `CreateServer` function that sets up your GoFiber server. ```go package src import ( "example.com/GofiberFirebaseBoilerplate/src/routes" "github.com/gofiber/fiber/v3" ) func CreateServer() *fiber.App { version := "v1.0.0" app := fiber.New(fiber.Config{ ServerHeader: "Gofiber Firebase Boilerplate", AppName: "Gofiber Firebase Boilerplate " + version, }) app.Get("/", func(c fiber.Ctx) error { return c.SendString("Gofiber Firebase Boilerplate " + version) }) routes.New().Setup(app) return app } ``` ### Routes Configuration Now that your GoFiber application is initialized, let’s delve into setting up and configuring routes. This section is crucial for defining how your application handles incoming requests. Open the `src/routes/routes.go` file to manage your routes. ```go package routes import ( "example.com/GofiberFirebaseBoilerplate/src/database" "example.com/GofiberFirebaseBoilerplate/src/models" "example.com/GofiberFirebaseBoilerplate/src/repositories" "github.com/gofiber/fiber/v3" ) type Routes struct { mainRepository *repositories.MainRepository } func New() *Routes { db := database.NewConnection() return &Routes{mainRepository: &repositories.MainRepository{DB: db}} } func (r *Routes) Setup(app *fiber.App) { app.Post("message", r.insertMessage) } func (r *Routes) insertMessage(c fiber.Ctx) error { var body models.MessageInputBody if err := c.Bind().JSON(&body); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()}) } if err := r.mainRepository.InsertMessage(&body); err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } return c.Status(fiber.StatusCreated).JSON(fiber.Map{"id": body}) } ``` ### Database Configuration Configure your Firestore database connection in the `src/database/database.go` file. Authentication uses Application Default Credentials (ADC), which are provided automatically by Cloud Functions / Cloud Run. For local development, run `gcloud auth application-default login`. ```go package database import ( "context" "log" "cloud.google.com/go/firestore" firebase "firebase.google.com/go" ) // NewConnection initializes a Firestore client using Application Default Credentials (ADC). // On Cloud Functions / Cloud Run, ADC is provided automatically by the runtime environment. // For local development, run: gcloud auth application-default login func NewConnection() *firestore.Client { ctx := context.Background() // No explicit credentials option needed — ADC resolves credentials automatically. app, err := firebase.NewApp(ctx, nil) if err != nil { log.Fatalf("functions.init: NewApp %v\n", err) } db, err := app.Firestore(ctx) if err != nil { log.Fatalf("functions.init: Database init : %v\n", err) } return db } ``` ### Repository Pattern Implement the repository pattern in the `src/repositories/main.repository.go` file to interact with Firestore. This file includes an example of inserting a message into the database. ```go package repositories import ( "context" "cloud.google.com/go/firestore" "example.com/GofiberFirebaseBoilerplate/src/models" "github.com/google/uuid" ) type MainRepository struct { DB *firestore.Client } func (r *MainRepository) InsertMessage(body *models.MessageInputBody) error { id := uuid.New().String() _, err := r.DB.Collection("messages").Doc(id).Set(context.Background(), body) return err } ``` ### Model Definition Define a message input model in src/models/message_input_body.go to structure the data you'll be working with. ```go package models type MessageInputBody struct { From string `json:"from"` To string `json:"to"` Message string `json:"message"` } ``` ### Functions for Cloud Integration In `functions.go`, convert Google Cloud Function requests to Fiber and route them to your application. This file includes functions to facilitate the integration of Google Cloud Functions and GoFiber. ```go package app import ( "net/http" "github.com/gofiber/fiber/v3" adaptor "github.com/gofiber/fiber/v3/middleware/adaptor" ) // CloudFunctionRouteToFiber route cloud function http.Handler to *fiber.App // Internally, google calls the function with the /execute base URL func CloudFunctionRouteToFiber(fiberApp *fiber.App, w http.ResponseWriter, r *http.Request) { adaptor.FiberApp(fiberApp)(w, r) } ``` ### Main Application Entry In `main.go`, initialize your GoFiber app and start the server. This file also includes an exported Cloud Function handler for deployment. ```go package app import ( "fmt" "net/http" "strings" "example.com/GofiberFirebaseBoilerplate/src" "github.com/gofiber/fiber/v3" ) var app *fiber.App func init() { app = src.CreateServer() } // Start start Fiber app with normal interface func Start(addr string) error { if -1 == strings.IndexByte(addr, ':') { addr = ":" + addr } return app.Listen(addr) } // ServerFunction Exported http.HandlerFunc to be deployed to as a Cloud Function func ServerFunction(w http.ResponseWriter, r *http.Request) { CloudFunctionRouteToFiber(app, w, r) } ``` ### Development For local development, utilize the `cmd/main.go` file. If you prefer hot reloading, the `.air.toml` configuration file is included for use Air. ### cmd/main.go ```go package main import ( "log" "os" app "example.com/GofiberFirebaseBoilerplate" ) func main() { port := "3001" if envPort := os.Getenv("PORT"); envPort != "" { port = envPort } if err := app.Start(port); err != nil { log.Fatalf("app.Start: %v\n", err) } } ``` ### .air.toml ```go root = "." testdata_dir = "testdata" tmp_dir = "tmp" [build] args_bin = [] bin = "./tmp/main" cmd = "go build -o ./tmp/main ./cmd" delay = 1000 exclude_dir = ["assets", "tmp", "vendor", "testdata"] exclude_file = [] exclude_regex = ["_test.go"] exclude_unchanged = false follow_symlink = false full_bin = "" include_dir = [] include_ext = ["go", "tpl", "tmpl", "html"] include_file = [] kill_delay = "0s" log = "build-errors.log" poll = false poll_interval = 0 post_cmd = [] pre_cmd = [] rerun = false rerun_delay = 500 send_interrupt = false stop_on_error = false [color] app = "" build = "yellow" main = "magenta" runner = "green" watcher = "cyan" [log] main_only = false time = false [misc] clean_on_exit = false [screen] clear_on_rebuild = false keep_scroll = true ``` ### Deployment Deploy your Cloud Function using the following commands, replacing `` with your Firebase project ID: ```bash gcloud config set project gcloud functions deploy MyCloudFunction --runtime go122 --trigger-http ``` ### Conclusion Congratulations! You’ve successfully configured and deployed a GoFiber application on Firebase Functions. This powerful combination allows you to build fast and efficient serverless applications. Experiment further with GoFiber features and Firebase integrations to unlock the full potential of your serverless architecture. Happy coding! ### Medium Post https://medium.com/@kmltrk07/how-to-deploy-gofiber-app-to-firebase-functions-8d4d537a4464 --- ## Form Data ## Form Data with [Fiber](https://gofiber.io) [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/form-data) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/form-data) This example demonstrates how to parse `application/x-www-form-urlencoded` request bodies into Go structs, slices and maps using Fiber v3's `Bind().Body()`. ### Description This project shows four ways a urlencoded form body can be parsed: a flat struct, a repeated field into a `[]string`, a nested struct plus a slice of structs, and dynamic keys into a map via `net/url.ParseQuery`. ### Requirements - [Go](https://golang.org/dl/) 1.26 or higher - [Git](https://git-scm.com/downloads) ### Project Structure - `main.go`: The main application entry point. - `go.mod`: The Go module file. ### Setup 1. Clone the repository: ```bash git clone https://github.com/gofiber/recipes.git cd recipes/form-data ``` 2. Install the dependencies: ```bash go mod download ``` 3. Run the application: ```bash go run main.go ``` The application should now be running on `http://localhost:3000`. ### Example Usage 1. Send a POST request to `http://localhost:3000/struct` with a flat form: ```bash curl -X POST http://localhost:3000/struct -d "name=Jane&email=jane@example.com&age=30" ``` ```json {"name":"Jane","email":"jane@example.com","age":30} ``` 2. Send a POST request to `http://localhost:3000/slice` with a repeated field: ```bash curl -X POST http://localhost:3000/slice -d "tags=go&tags=web" ``` ```json {"tags":["go","web"]} ``` 3. Send a POST request to `http://localhost:3000/nested` with a nested struct and a slice of structs: ```bash curl -X POST http://localhost:3000/nested -d "customer=Jane&address.street=Main+St&address.city=Springfield&items.0.name=Widget&items.0.qty=2&items.1.name=Gadget&items.1.qty=1" ``` ```json {"customer":"Jane","address":{"street":"Main St","city":"Springfield"},"items":[{"name":"Widget","qty":2},{"name":"Gadget","qty":1}]} ``` 4. Send a POST request to `http://localhost:3000/map` with keys that have no matching struct field: ```bash curl -X POST http://localhost:3000/map -d "color=blue&size=M&size=L" ``` ```json {"color":["blue"],"size":["M","L"]} ``` 5. A field that cannot convert to its declared Go type returns a 400 with the bind error: ```bash curl -X POST http://localhost:3000/struct -d "name=Jane&email=jane@example.com&age=notanumber" ``` ```json {"error":"bind \"age\" from body: schema: error converting value for \"age\""} ``` ### Code Overview #### `main.go` The main Go file sets up the Fiber application and four routes, each binding a urlencoded form body into a different Go shape: - `POST /struct` binds a flat form into a struct with `Bind().Body()`. - `POST /slice` binds a repeated field (`tags=go&tags=web`) into a `[]string`. - `POST /nested` binds a dotted key path (`address.street`) into a nested struct, and a dotted, indexed key path (`items.0.qty`) into a slice of structs. - `POST /map` parses the raw body into a `map[string][]string` using `net/url.ParseQuery` as a standard-library alternative (Fiber's `Bind().Body()` also natively supports binding form bodies into maps). ### Conclusion This example provides a minimal reference for binding `application/x-www-form-urlencoded` bodies in a Go Fiber application, covering scalar fields, repeated fields, nested structs, slices of structs, and dynamic keys. ### References - [Fiber Documentation](https://docs.gofiber.io) - [gofiber/schema](https://github.com/gofiber/schema) --- ## Firebase GCloud ## Deploy Fiber to Google Cloud with Firebase [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/gcloud) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/gcloud) Examples on how to run an application using Fiber on Google Cloud. ### Prerequisites - [gcloud CLI](https://cloud.google.com/sdk/docs/install) installed and authenticated (`gcloud auth login`) - A Google Cloud project created and billing enabled - Required APIs enabled: Cloud Run, App Engine, or Cloud Functions (depending on deployment method) - Set your project: `gcloud config set project [YOUR_PROJECT_ID]` ### Running Locally * Run on the command line: ``` go run cmd/main.go ``` ### Deploy using Google Cloud Run This step will build a Docker Image, publish to Google Cloud Registry and deploy on Cloud Run Managed enviroment. [![Run on Google Cloud](https://storage.googleapis.com/cloudrun/button.svg)](https://console.cloud.google.com/cloudshell/editor?shellonly=true&cloudshell_image=gcr.io/cloudrun/button&cloudshell_git_repo=https://github.com/gofiber/recipes&cloudshell_working_dir=gcloud) After deploying the server on Cloud Run, you can get it's url on GCP Console ([link](https://console.cloud.google.com/run)) and select the service `gcloud-fiber` that we just deployed. Them copy the URL will look like `https://{project-id}-{some-random-hash-string}.a.run.app`. Or you can do it manually with those steps: * Run on the command line: ``` export GCLOUD_PROJECT=[YOUR_PROJECT_ID] gcloud builds submit --tag gcr.io/$GCLOUD_PROJECT/gcloud-fiber . gcloud beta run deploy --platform managed --image gcr.io/$GCLOUD_PROJECT/gcloud-fiber ``` ### Deploy using Google App Engine This step will deploy the app to Google App Engine Standard Go enviroment. The app configuration and additional configurations can be tweaked on the `app.yaml` file. * Run on the command line: ``` gcloud app deploy ``` ### Deploy using Google Cloud Function This step will deploy a HTTP Cloud Function using Go enviroment. You can use the `deploy.sh` script. Just edit your project id on it. For the Cloud Functions env, Google enforces us to deploy a function that is a `http.HandlerFunc`, so on the file `functions.go` there is a workaround to reroute the HTTP call to the Fiber app instance. * Run on the command line: ``` gcloud functions deploy MyCloudFunction --runtime go122 --trigger-http ``` --- ## Google Cloud Firebase ## Deploy Fiber to Google Cloud with Firebase [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/gcloud-firebase) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/gcloud-firebase) Examples on how to run an application using Fiber on Google Cloud and connecting to Firebase Realtime Database. ### Running Locally * Run on the command line: ``` go run cmd/main.go ``` ### Deploy using Google Cloud Run This step will build a Docker Image, publish to Google Cloud Registry and deploy on Cloud Run Managed enviroment. Just follow the steps and fill the `GCP_PROJECT` variable with your Google Cloud Platform project ID. That variable is needed to connect to Firebase. [![Run on Google Cloud](https://storage.googleapis.com/cloudrun/button.svg)](https://console.cloud.google.com/cloudshell/editor?shellonly=true&cloudshell_image=gcr.io/cloudrun/button&cloudshell_git_repo=https://github.com/gofiber/recipes&cloudshell_working_dir=gcloud-firebase) After deploying the server on Cloud Run, you can get it's url on GCP Console ([link](https://console.cloud.google.com/run)) and select the service `gcloud-fiber-firebase` that we just deployed. Then copy the URL. It will look like `https://{project-id}-{some-random-hash-string}.a.run.app`. Or you can do it manually with those steps: * Run on the command line: ``` export GCLOUD_PROJECT=[YOUR_PROJECT_ID] gcloud builds submit --tag gcr.io/$GCLOUD_PROJECT/gcloud-fiber-firebase . gcloud beta run deploy --platform managed --image gcr.io/$GCLOUD_PROJECT/gcloud-fiber-firebase \ --set-env-vars GCP_PROJECT=$GCLOUD_PROJECT ``` ### Deploy using Google App Engine This step will deploy the app to Google App Engine Standard Go enviroment. The app configuration and additional configurations can be tweaked on the `app.yaml` file. * Run on the command line: ``` gcloud app deploy ``` ### Deploy using Google Cloud Function This step will deploy a HTTP Cloud Function using Go enviroment. You can use the `deploy.sh` script. Just edit your project id on it. For the Cloud Functions env, Google enforces us to deploy a function that is a `http.HandlerFunc`, so on the file `functions.go` there is a workaround to reroute the HTTP call to the Fiber app instance. * Run on the command line: ``` gcloud functions deploy HeroesAPI --runtime go122 --trigger-http ``` --- ## GeoIP ## GeoIP Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/geoip) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/geoip) This recipe demonstrates how to build a GeoIP lookup service with [Fiber](https://github.com/gofiber/fiber). It proxies requests to the [ip-api.com](http://ip-api.com) JSON API and caches responses for 10 minutes using Fiber's built-in cache middleware. > **Note:** This recipe depends on the free [ip-api.com](http://ip-api.com) service. The free tier is limited to **1000 requests per minute** from a single IP address. For higher traffic, consider a paid plan or a self-hosted alternative such as [geoip-maxmind](../geoip-maxmind/). ### Prerequisites - Go 1.21+ - Internet access (ip-api.com is called at runtime — no local database required) ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/geoip ``` 2. Install dependencies: ```sh go get ``` ### Running the Application ```sh go run main.go ``` The server starts on port `3000` by default. Set the `PORT` environment variable to override: ```sh PORT=8080 go run main.go ``` Open `http://localhost:3000` in a browser to use the web UI. ### Example Look up geolocation data for an IP address via the `/geo` endpoint: ```sh curl "http://localhost:3000/geo?ip=178.62.56.160" ``` Example response: ```json { "status": "success", "country": "United Kingdom", "countryCode": "GB", "region": "ENG", "regionName": "England", "city": "London", "zip": "EC1A", "lat": 51.5085, "lon": -0.1257, "timezone": "Europe/London", "isp": "DigitalOcean, LLC", "org": "DigitalOcean, LLC", "as": "AS14061 DigitalOcean, LLC", "query": "178.62.56.160" } ``` Omit the `ip` query parameter to look up the caller's own IP address: ```sh curl "http://localhost:3000/geo" ``` ### References - [Fiber Documentation](https://docs.gofiber.io) - [ip-api.com Documentation](http://ip-api.com/docs) - [ip-api.com Rate Limits](http://ip-api.com/docs/api:json#usage_limits) --- ## GeoIP + MaxMind ## GeoIP (with MaxMind databases) [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/geoip-maxmind) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/geoip-maxmind) This is an alternative method to resolve IP addresses to real-world location data using MaxMind GeoLite2 City databases. ### Prerequisites Before you run this, you must first download a database from the MaxMind website - https://dev.maxmind.com/geoip/geoip2/geolite2/. To do this, you need to register for a free MaxMind account. > **Note:** The GeoLite2 database is provided by MaxMind under the [GeoLite2 End User License Agreement](https://www.maxmind.com/en/geolite2/eula). A free MaxMind account is required to download the database file. The database you need to download is the one with the edition ID `GeoLite2-City`. Place it in this folder and run ``` go run . ``` ### Usage Make a request to `http://127.0.0.1:3000/geo/178.62.56.160`, for example. You can omit an IP address to use your current IP address, or replace to use another. If the IP address is invalid, a HTTP 400 is returned. The response fields can be modified from the `ipLookup` struct, found in the `handlers/handlers.go` file. #### Example response ```json { "City": { "GeoNameID": 2643743, "Names": { "de": "London", "en": "London", "es": "Londres", "fr": "Londres", "ja": "ロンドン", "pt-BR": "Londres", "ru": "Лондон", "zh-CN": "伦敦" } }, "Country": { "IsoCode": "GB" }, "Location": { "AccuracyRadius": 50 } } ``` --- ## GORM ## GORM Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/gorm) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/gorm) This is a sample program demonstrating how to use GORM as an ORM to connect to a SQLite database with the Fiber web framework. ### Prerequisites - Go 1.25 or higher - Go modules ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/gorm ``` 2. Install dependencies: ```sh go mod tidy ``` ### Running the Application 1. Run the application: ```sh go run main.go ``` 2. The server will start on `http://localhost:3000`. ### Endpoints | Method | URL | Description | | ------ | ---------------- | -------------------------- | | GET | /api/v1/book | Retrieves all books | | GET | /api/v1/book/:id | Retrieves a book by ID | | POST | /api/v1/book | Creates a new book | | DELETE | /api/v1/book/:id | Deletes a book | ### Example Requests #### Get All Books ```sh curl -X GET http://localhost:3000/api/v1/book ``` #### Get Book by ID ```sh curl -X GET http://localhost:3000/api/v1/book/1 ``` #### Create a New Book ```sh curl -X POST http://localhost:3000/api/v1/book -d '{"title": "New Book", "author": "Author Name"}' -H "Content-Type: application/json" ``` #### Delete a Book ```sh curl -X DELETE http://localhost:3000/api/v1/book/1 ``` --- ## GORM MySQL ## GORM MySQL Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/gorm-mysql) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/gorm-mysql) This is a sample program demonstrating how to use GORM as an ORM to connect to a MySQL database with the Fiber web framework. ### Prerequisites - Go 1.25 or higher - MySQL database - Go modules ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/gorm-mysql ``` 2. Install dependencies: ```sh go mod tidy ``` 3. Configure the database connection via the `DB_DSN` environment variable: ```sh export DB_DSN="user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local" ``` If `DB_DSN` is not set, the application falls back to the default DSN above. ### Running the Application 1. Run the application: ```sh go run app.go ``` 2. The server will start on `http://localhost:3000`. ### Endpoints | Method | URL | Description | | ------ | --------- | -------------------------- | | GET | /hello | Returns a hello message | | GET | /allbooks | Retrieves all books | | GET | /book/:id | Retrieves a book by ID | | POST | /book | Creates a new book | | PUT | /book/:id | Updates an existing book | | DELETE | /book/:id | Deletes a book | ### Example Requests #### Get All Books ```sh curl -X GET http://localhost:3000/allbooks ``` #### Get Book by ID ```sh curl -X GET http://localhost:3000/book/1 ``` #### Create a New Book ```sh curl -X POST http://localhost:3000/book -d '{"title": "New Book", "author": "Author Name"}' -H "Content-Type: application/json" ``` #### Update a Book ```sh curl -X PUT http://localhost:3000/book/1 -d '{"title": "Updated Book", "author": "Updated Author"}' -H "Content-Type: application/json" ``` #### Delete a Book ```sh curl -X DELETE http://localhost:3000/book/1 ``` --- ## GORM + PostgreSQL ## GORM with PostgreSQL Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/gorm-postgres) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/gorm-postgres) This project demonstrates how to set up a Go application using the Fiber framework with GORM and PostgreSQL. ### Prerequisites Ensure you have the following installed: - Golang - [Fiber](https://github.com/gofiber/fiber) package - [GORM](https://gorm.io/) package - PostgreSQL ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/gorm-postgres ``` 2. Install dependencies: ```sh go mod tidy ``` 3. Set up PostgreSQL and create a database: ```sh createdb go-db ``` 4. Configure the database connection via the `DB_DSN` environment variable: ```sh export DB_DSN="host=localhost user=postgres password='' dbname=go-db port=5432 sslmode=disable" ``` If `DB_DSN` is not set, the application falls back to the default DSN above. ### Running the Application 1. Start the application: ```sh go run main.go ``` 2. Access the application at `http://localhost:3000`. ### Endpoints | Method | URL | Description | | ------ | ---------- | -------------------------- | | GET | /hello | Returns a hello message | | GET | /allbooks | Retrieves all books | | GET | /book/:id | Retrieves a book by ID | | POST | /book | Creates a new book | | PUT | /book/:id | Updates an existing book | | DELETE | /book/:id | Deletes a book | ### Example Requests #### Get All Books ```sh curl -X GET http://localhost:3000/allbooks ``` #### Get Book by ID ```sh curl -X GET http://localhost:3000/book/1 ``` #### Create a New Book ```sh curl -X POST http://localhost:3000/book \ -d '{"title": "New Book", "author": "Author Name"}' \ -H "Content-Type: application/json" ``` #### Update a Book ```sh curl -X PUT http://localhost:3000/book/1 \ -d '{"title": "Updated Book", "author": "Updated Author"}' \ -H "Content-Type: application/json" ``` #### Delete a Book ```sh curl -X DELETE http://localhost:3000/book/1 ``` ### References - [Fiber Documentation](https://docs.gofiber.io) - [GORM Documentation](https://gorm.io/docs/) - [PostgreSQL Documentation](https://www.postgresql.org/docs/) --- ## Graceful shutdown ## Graceful shutdown in Fiber [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/graceful-shutdown) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/graceful-shutdown) ``` fiberRecipes/graceful-shutdown on graceful-shutdown (f0834df) [?] via 🐹 v1.15.2 took 4s ❯ go run graceful-shutdown ┌───────────────────────────────────────────────────┐ │ Fiber v3.0.0 │ │ http://127.0.0.1:3000 │ │ │ │ Handlers ............. 2 Threads ............. 8 │ │ Prefork ....... Disabled PID .............. 2540 │ └───────────────────────────────────────────────────┘ ^CGracefully shutting down... Running cleanup tasks... ``` This shows how to implement a graceful shutdown with Fiber and the `os/signal` package. ### Explanation This example relies on the use of channels, a data type in Go that allows you to send and receive data to/from specific places in an application (read more about them [here](https://tour.golang.org/concurrency/2)). A channel is created, and registered with `signal.Notify` so that when the program receives an interrupt (for example, when `CTRL+C` is pressed), a notification is sent to the channel. Once this is received, `app.Shutdown` is called to close all active connections and return from `app.Listen`. After this point, cleanup functions can be run and the program eventually quits. --- ## GraphQL ## GraphQL Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/graphql) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/graphql) This project demonstrates how to set up a GraphQL server in a Go application using the Fiber framework and the [graphql-go](https://github.com/graphql-go/graphql) library. ### Prerequisites Ensure you have the following installed: - Golang - [Fiber](https://github.com/gofiber/fiber) package - [graphql-go](https://github.com/graphql-go/graphql) package ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/graphql ``` 2. Install dependencies: ```sh go get ``` ### Running the Application 1. Start the application: ```sh go run main.go ``` 2. The server listens on port `9090` and exposes a single endpoint at `/` that accepts both GET and POST requests. ### Usage #### GET request Pass the GraphQL query as a URL-encoded `query` parameter: ```sh curl 'http://localhost:9090/?query=query%7Bhello%7D' ``` #### POST request Send the query as a JSON body with `Content-Type: application/json`: ```sh curl 'http://localhost:9090/' \ --header 'content-type: application/json' \ --data-raw '{"query":"query{hello}"}' ``` Both return a JSON response like: ```json {"data":{"hello":"world"}} ``` ### References - [Fiber Documentation](https://docs.gofiber.io) - [graphql-go Documentation](https://github.com/graphql-go/graphql) - [GraphQL Documentation](https://graphql.org/) --- ## gRPC ## Example for fiber as a client to gRPC server. [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/grpc) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/grpc) A sample program to showcase fiber as a client to a gRPC server. ### Prerequisites - Go 1.25 or higher - Go modules ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/grpc ``` 2. Install dependencies: ```sh go mod tidy ``` ### Running the Application 1. Run the gRPC server: ```sh go run server/main.go ``` 2. Run the Fiber client: ```sh go run client/main.go ``` 3. The server will start on `http://localhost:3000`. ### Endpoints | Method | URL | Return value | | ------ | ------------- | ------------ | | GET | /add/:a/:b | a + b | | GET | /mult/:a/:b | a \* b | #### Output ```bash -> curl http://localhost:3000/add/33445/443234 {"result":"476679"} -> curl http://localhost:3000/mult/33445/443234 {"result":"14823961130"} ``` ### Regenerating Proto Files If you modify `proto/service.proto`, regenerate the Go bindings with one of the following methods: #### Using protoc Install the required tools: ```sh go install google.golang.org/protobuf/cmd/protoc-gen-go@latest go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest ``` Then regenerate: ```sh protoc --go_out=. --go_opt=paths=source_relative \ --go-grpc_out=. --go-grpc_opt=paths=source_relative \ proto/service.proto ``` #### Using buf Install buf: https://buf.build/docs/installation ```sh buf generate ``` ### Additional Information gRPC (gRPC Remote Procedure Calls) is a high-performance, open-source universal RPC framework initially developed by Google. It uses HTTP/2 for transport, Protocol Buffers as the interface description language, and provides features such as authentication, load balancing, and more. For more information, visit the [official gRPC documentation](https://grpc.io/docs/). --- ## Hello World ## Hello World Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/hello-world) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/hello-world) This project demonstrates a simple "Hello, World!" application using the Fiber framework in Go. ### Prerequisites Ensure you have the following installed: - Golang - [Fiber](https://github.com/gofiber/fiber) package ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/hello-world ``` 2. Install dependencies: ```sh go get ``` ### Running the Application 1. Start the application: ```sh go run main.go ``` 2. Access the application at `http://localhost:3000`. ### Example Here is an example `main.go` file for the Fiber application: ```go package main import ( "log" "github.com/gofiber/fiber/v3" ) func main() { // Fiber instance app := fiber.New() // Routes app.Get("/", hello) // Start server log.Fatal(app.Listen(":3000")) } // Handler func hello(c fiber.Ctx) error { return c.SendString("Hello, World 👋!") } ``` ### References - [Fiber Documentation](https://docs.gofiber.io) - [Golang Documentation](https://golang.org/doc/) --- ## Heroku ## Heroku Deployment Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/heroku) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/heroku) This project demonstrates how to deploy a Go application using the Fiber framework on Heroku. > **Note:** Heroku removed its free tier in November 2022. A paid plan is required to deploy applications. ### Prerequisites Ensure you have the following installed: - Golang - [Fiber](https://github.com/gofiber/fiber) package - [Heroku CLI](https://devcenter.heroku.com/articles/heroku-cli) ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/heroku ``` 2. Install dependencies: ```sh go get ``` 3. Log in to Heroku: ```sh heroku login ``` 4. Create a new Heroku application: ```sh heroku create ``` 5. Build the binary and add a `Procfile` to the project directory: ```sh go build -o bin/main . ``` `Procfile`: ``` web: bin/main ``` 6. Deploy the application to Heroku: ```sh git add . git commit -m "Deploy to Heroku" git push heroku master ``` ### Running the Application 1. Open the application in your browser: ```sh heroku open ``` ### Example Here is an example `main.go` file for the Fiber application: ```go package main import ( "log" "os" "github.com/gofiber/fiber/v3" ) func main() { app := fiber.New() app.Get("/", func(c fiber.Ctx) error { return c.SendString("Hello, Heroku!") }) log.Fatal(app.Listen(":" + getPort())) } func getPort() string { port := os.Getenv("PORT") if port == "" { port = "3000" } return port } ``` ### References - [Fiber Documentation](https://docs.gofiber.io) - [Heroku Documentation](https://devcenter.heroku.com/) --- ## Hexagonal Architecture ## A Hexagonal Software Architecture in Golang and MongoDB [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/hexagonal) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/hexagonal) This project presents a simple product catalogue microservice to demonstrate the principles of a hexagonal software architecture. The microservice exposes a RESTful API that allows consuming applications to perform CRUD operations on a product catalogue. The microservice is developed in Golang, and the product catalogue data is persisted in a MongoDB repository. ![Hexagonal Architecture](Hexagonal-Arch.png) ### Prerequisites - Go 1.25 or higher - MongoDB - Go modules ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/hexagonal ``` 2. Install dependencies: ```sh go mod tidy ``` 3. Configure the MongoDB connection in the `config.yaml` file: ```yaml database: url: "your_mongodb_uri" db: "your_db_name" timeout: 30 ``` ### Running the Application 1. Run the application: ```sh go run main.go ``` 2. The server will start on `http://localhost:8080`. ### Endpoints | Method | URL | Description | | ------ | ---------------- | -------------------------------- | | GET | /api/v1/products | Retrieves all products | | GET | /api/v1/product/:id | Retrieves a product by ID | | POST | /api/v1/product | Creates a new product | | PUT | /api/v1/product/:id | Updates an existing product | | DELETE | /api/v1/product/:id | Deletes a product | ### Example Requests #### Get All Products ```sh curl -X GET http://localhost:8080/api/v1/products ``` #### Get Product by ID ```sh curl -X GET http://localhost:8080/api/v1/product/1 ``` #### Create a New Product ```sh curl -X POST http://localhost:8080/api/v1/product -d '{"name": "New Product", "price": 100}' -H "Content-Type: application/json" ``` #### Update a Product ```sh curl -X PUT http://localhost:8080/api/v1/product/1 -d '{"name": "Updated Product", "price": 150}' -H "Content-Type: application/json" ``` #### Delete a Product ```sh curl -X DELETE http://localhost:8080/api/v1/product/1 ``` Hexagonal architecture, also known as ports and adapters architecture, is a design pattern used to create loosely coupled application components that can be easily connected to their software environment by means of ports and adapters. This architecture allows an application to be equally driven by users, programs, automated tests, or batch scripts, and to be developed and tested in isolation from its eventual runtime devices and databases. ### Additional Information For more information on hexagonal architecture, you can refer to the following resources: - [Hexagonal Architecture](https://alistair.cockburn.us/hexagonal-architecture/) - [Hexagonal Architecture in Golang](https://medium.com/@matryer/hexagonal-architecture-in-go-2b5e0df2d8f8) --- ## HTTPS with PKCS12 TLS ## HTTPS with PKCS12 TLS Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/https-pkcs12-tls) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/https-pkcs12-tls) This project demonstrates how to set up an HTTPS server with PKCS12 TLS in a Go application using the Fiber framework. ### Prerequisites Ensure you have the following installed: - Golang - [Fiber](https://github.com/gofiber/fiber) package - PKCS12 certificate file (`cert.p12`) ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/https-pkcs12-tls ``` 2. Install dependencies: ```sh go get ``` 3. Place your PKCS12 certificate file (`server.p12`) in the `security/` directory. 4. Optionally set the PKCS12 password via environment variable (defaults to `changeit`): ```sh export PKCS12_PASSWORD=yourpassword ``` ### Running the Application 1. Start the application: ```sh go run main.go ``` 2. Access the application at `https://localhost:443`. ### Example Here is an example of how to set up an HTTPS server with PKCS12 TLS in a Fiber application: ```go package main import ( "crypto" "crypto/tls" "log" "os" "github.com/gofiber/fiber/v3" "golang.org/x/crypto/pkcs12" ) func main() { path := "./security/server.p12" password := os.Getenv("PKCS12_PASSWORD") if password == "" { password = "changeit" } // Read and decode PKCS12 file pkcs12Data, err := os.ReadFile(path) if err != nil { log.Fatal(err) } key, cert, err := pkcs12.Decode(pkcs12Data, password) if err != nil { log.Fatal(err) } tlsCert := tls.Certificate{ Certificate: [][]byte{cert.Raw}, PrivateKey: key.(crypto.PrivateKey), Leaf: cert, } config := &tls.Config{Certificates: []tls.Certificate{tlsCert}} app := fiber.New() app.Get("/", func(c fiber.Ctx) error { return c.SendString("This page is being served over TLS using a PKCS12 store type!") }) ln, err := tls.Listen("tcp", ":443", config) if err != nil { log.Fatal(err) } log.Fatal(app.Listener(ln)) } ``` ### References - [Fiber Documentation](https://docs.gofiber.io) - [TLS in Go](https://golang.org/pkg/crypto/tls/) - [PKCS12 in Go](https://pkg.go.dev/golang.org/x/crypto/pkcs12) --- ## HTTPS with TLS ## HTTPS with TLS Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/https-tls) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/https-tls) This project demonstrates how to set up an HTTPS server with TLS in a Go application using the Fiber framework. ### Prerequisites Ensure you have the following installed: - Golang - [Fiber](https://github.com/gofiber/fiber) package - TLS certificates (self-signed or from a trusted CA) ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/https-tls ``` 2. Install dependencies: ```sh go get ``` 3. Generate a self-signed certificate and key: ```sh mkdir -p certs openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout certs/ssl.key -out certs/ssl.cert \ -subj "/CN=localhost" ``` ### Running the Application 1. Start the application: ```sh go run main.go ``` 2. Access the application at `https://localhost:443`. ### Example Here is an example of how to set up an HTTPS server with TLS in a Fiber application: ```go package main import ( "crypto/tls" "log" "github.com/gofiber/fiber/v3" ) func main() { app := fiber.New() app.Get("/", func(c fiber.Ctx) error { return c.SendString(c.Protocol()) // => https }) // Create tls certificate cer, err := tls.LoadX509KeyPair("certs/ssl.cert", "certs/ssl.key") if err != nil { log.Fatal(err) } config := &tls.Config{Certificates: []tls.Certificate{cer}} // Create custom listener ln, err := tls.Listen("tcp", ":443", config) if err != nil { log.Fatal(err) } // Start server with https/ssl enabled on http://localhost:443 log.Fatal(app.Listener(ln)) } ``` ### References - [Fiber Documentation](https://docs.gofiber.io) - [TLS in Go](https://golang.org/pkg/crypto/tls/) --- ## I18n (I18n) ## Fiber with i18n [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/i18n) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/i18n) This is a quick example of how to use [nicksnyder/go-i18n](https://github.com/nicksnyder/go-i18n) package to translate your Fiber application into multiple languages. ### Demo - Run Fiber application; - Open `http://127.0.0.1:3000/?unread=1` and see: ```bash Hello Bob I have 1 unread email. Bob has 1 unread email. ``` - Next, go to `http://127.0.0.1:3000/?unread=4` and see pluralization of your message: ```bash Hello Bob I have 4 unread emails. Bob has 4 unread emails. ``` - OK. Try translation of other languages, just add `&lang=es` (or `&lang=ru`) query to the URL: ```bash Hola Bob Tengo 4 correos electrónicos no leídos Bob tiene 4 correos electrónicos no leídos ``` ### Getting Started ```bash go run main.go ``` Set `ENV=development` to enable template hot-reload: ```bash ENV=development go run main.go ``` ### File Structure ``` i18n/ ├── lang/ │ ├── active.en.toml # English translations (default) │ ├── active.es.toml # Spanish translations │ └── active.ru.toml # Russian translations ├── templates/ │ └── index.html # HTML template ├── main.go # Application entry point ├── go.mod └── go.sum ``` ### go-i18n docs - [Translating a new language](https://github.com/nicksnyder/go-i18n#translating-a-new-language); - [Translating a new messages (updating)](https://github.com/nicksnyder/go-i18n#translating-new-messages); --- ## JWT (Jwt) ## Fiber with JWT [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/jwt) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/jwt) This example demonstrates how to use JSON Web Tokens (JWT) for authentication in a Fiber application. ### Prerequisites - Go 1.25 or higher - Go modules ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/jwt ``` 2. Install dependencies: ```sh go mod tidy ``` ### Running the Application 1. Run the application: ```sh go run main.go ``` 2. The server will start on `http://localhost:3000`. ### Endpoints | Method | URL | Description | | ------ | ------------- | -------------------------- | | POST | /login | Authenticates a user and returns a JWT | | GET | /restricted | Accesses a restricted route with JWT | ### Example Requests #### Login ```sh curl -X POST http://localhost:3000/login -d '{"username": "user", "password": "pass"}' -H "Content-Type: application/json" ``` #### Access Restricted Route ```sh curl -X GET http://localhost:3000/restricted -H "Authorization: Bearer " ``` ### Postman Collection You can find Postman examples [here](https://www.getpostman.com/collections/0e83876e0f2a0c8ecd70). --- ## Kubernetes ## Kubernetes Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/k8s) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/k8s) This project demonstrates how to deploy a Go application using the Fiber framework on a Kubernetes cluster. ### Prerequisites Ensure you have the following installed: - Golang - [Fiber](https://github.com/gofiber/fiber) package - Docker - Kubernetes - kubectl - [Minikube](https://minikube.sigs.k8s.io/docs/start/) (for local development) ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/k8s ``` 2. Install dependencies: ```sh go get ``` 3. Build the Docker image: ```sh docker build -t fiber-k8s-example . ``` 4. Start Minikube (if using Minikube): ```sh minikube start ``` 5. Deploy the application to Kubernetes: ```sh kubectl apply -f my-service.yaml ``` ### Running the Application 1. Check the status of the pods: ```sh kubectl get pods ``` 2. Forward the port to access the application: ```sh kubectl port-forward svc/fiber-k8s-example 3000:3000 ``` 3. Access the application at `http://localhost:3000`. ### Example Here is an example `main.go` file for the Fiber application: ```go package main import ( "context" "log" "os/signal" "syscall" "github.com/gofiber/fiber/v3" ) func main() { app := fiber.New() app.Get("/", func(c fiber.Ctx) error { return c.SendString("Hello, Kubernetes!") }) app.Get("/healthz", func(c fiber.Ctx) error { return c.SendStatus(fiber.StatusOK) }) ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() go func() { if err := app.Listen(":3000"); err != nil { log.Printf("Listen error: %v\n", err) } }() <-ctx.Done() log.Println("Gracefully shutting down...") if err := app.ShutdownWithContext(ctx); err != nil { log.Printf("Shutdown error: %v\n", err) } } ``` Here is an example `Dockerfile` for the application: ```Dockerfile FROM golang:1.25-alpine AS builder WORKDIR /app COPY go.mod ./ COPY go.sum ./ RUN go mod download COPY *.go ./ RUN go build -o /fiber-k8s-example EXPOSE 3000 CMD ["/fiber-k8s-example"] ``` Here is an example `my-service.yaml` file for deploying the application to Kubernetes: ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: fiber-k8s-example spec: replicas: 2 selector: matchLabels: app: fiber-k8s-example template: metadata: labels: app: fiber-k8s-example spec: containers: - name: fiber-k8s-example image: fiber-k8s-example:latest ports: - containerPort: 3000 readinessProbe: httpGet: path: /healthz port: 3000 initialDelaySeconds: 5 periodSeconds: 10 livenessProbe: httpGet: path: /healthz port: 3000 initialDelaySeconds: 10 periodSeconds: 20 --- apiVersion: v1 kind: Service metadata: name: fiber-k8s-example spec: type: NodePort selector: app: fiber-k8s-example ports: - protocol: TCP port: 3000 targetPort: 3000 nodePort: 30001 ``` ### References - [Fiber Documentation](https://docs.gofiber.io) - [Kubernetes Documentation](https://kubernetes.io/docs/) - [Docker Documentation](https://docs.docker.com/) --- ## Todo App + Auth + GORM + Testcontainers ## Todo App with Auth using GORM and Testcontainers [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/local-development-testcontainers) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/local-development-testcontainers) This project demonstrates a Todo application with authentication using GORM and Testcontainers. The database is a Postgres instance created using the GoFiber's [Testcontainers Service module](https://github.com/gofiber/contrib/testcontainers). The instance is reused across multiple runs of the application, allowing to develop locally without having to wait for the database to be ready. When using the `air` command to run the application, the database is automatically started alongside the Fiber application, and it's automatically stopped when the air command is interrupted. ### Prerequisites Ensure you have the following installed and available in your `GOPATH`: - Golang - [Air](https://github.com/air-verse/air) for hot reloading ### Installation 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/local-development-testcontainers ``` 2. Install dependencies: ```sh go get ``` ### Running the Application 1. Start the application: ```sh air ``` ### Environment Variables Create a `.env` file in the root directory and add the following variables: ```shell # PORT returns the server listening port # default: 8000 PORT= # DB returns the name of the PostgreSQL database # default: postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable # TOKENKEY returns the jwt token secret TOKENKEY= # TOKENEXP returns the jwt token expiration duration. # Should be time.ParseDuration string. Source: https://golang.org/pkg/time/#ParseDuration # default: 10h TOKENEXP= # TESTCONTAINERS_RYUK_DISABLED disables the Ryuk container, to avoid removing the database container when the application is stopped. # default: true TESTCONTAINERS_RYUK_DISABLED=true ``` --- ## Memgraph ## Fiber and Memgraph [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/memgraph) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/memgraph) This is a cookbook recipe for setting up Fiber backend and Memgraph database. 🚀 ### Prerequisites Go is an obvious prerequisite. Make sure it is installed and configured properly. After that you need two Go packages: Fiber and Neo4j driver for Go. You can install them with the following commands: ``` go get -u github.com/gofiber/fiber/v3 go get github.com/neo4j/neo4j-go-driver/v5 ``` ### Run Memgraph The easiest way to run Memgraph is to use Docker. Once docker is installed on your machine, you can run Memgraph with the following command: ``` docker run –name memgraph -it -p 7687:7687 -p 7444:7444 -p 3000:3000 -v mg_lib:/var/lib/memgraph memgraph/memgraph-platform ``` ### Run the recipe After you have installed all the prerequisites, you can run the recipe with the following command: ``` cd memgraph go run ./main.go ``` This will do the following: 1. Connect Fiber backend to Memgraph database 2. Generate mock data to populate the database 3. Define two request handlers: one for getting the graph and one for getting developer nodes ### Test the recipe Once Fiber app is running, you can test the recipe by sending a GET request to the following endpoints: ``` http://localhost:3000/graph http://localhost:3000/developer/Andy ``` ### Additional resources For extra information use the documentation on the following links: - Fiber: https://docs.gofiber.io/ - Memgraph: https://memgraph.com/docs --- ## MinIO ## MinIO File Upload & Download Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/minio) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/minio) This example demonstrates a simple Go Fiber application that includes modules for uploading both single and multiple files, as well as downloading files from MinIO. Each module provides REST API endpoints for file upload and retrieval, serving as a foundation for applications requiring file storage and access. ### Prerequisites Ensure you have the following installed: - [Go](https://golang.org/dl/): (version 1.22 or higher) installed - [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/): for running a local MinIO instance - [Git](https://git-scm.com/downloads) ### Project Structure - `single/main.go`: Example for uploading and downloading a single file to/from MinIO. - `multiple/main.go`: Example for uploading multiple files to MinIO and downloading files from MinIO. - `go.mod`: Go module file managing project dependencies. ### Getting Started #### 1. Clone the Repository Clone the repository and navigate to the example directory: ```bash git clone https://github.com/gofiber/recipes.git cd recipes/minio ``` #### 2. Start MinIO with Docker Compose A `docker-compose.yml` is provided for running a local MinIO instance: ```bash docker-compose up -d ``` This starts MinIO on port `9000` (API) and `9001` (web console). Access the console at [http://localhost:9001](http://localhost:9001) with credentials `minioadmin` / `minioadmin`. To stop MinIO: ```bash docker-compose down ``` #### 3. Install Dependencies Use Go’s module system to install dependencies: ```bash go mod download ``` ### Running the Examples #### Uploading and Downloading a Single File 1. Go to the `single` directory: ```bash cd single ``` 2. Start the application: ```bash go run main.go ``` 3. Upload a file using `curl` or `Postman`: ```bash curl -F "document=@/path/to/your/file" http://localhost:3000/upload ``` 4. Download the file by specifying its name in the request: ```bash curl -O http://localhost:3000/file/ ``` #### Uploading Multiple Files and Downloading Files 1. Go to the `multiple` directory: ```bash cd multiple ``` 2. Start the application: ```bash go run main.go ``` 3. Upload multiple files using `curl` or `Postman`: ```bash curl -F "documents=@/path/to/your/file1" -F "documents=@/path/to/your/file2" http://localhost:3000/upload ``` 4. Download a file by specifying its name in the request. ```bash curl -O http://localhost:3000/file/ ``` ### Code Overview #### `single/main.go` - Defines routes to handle a single file upload and download. - Includes error handling for file validation, MinIO connection, and bucket management. #### `multiple/main.go` - Handles uploading multiple files in a single request and allows for file downloads. - Validates each file and provides detailed responses for both successful and failed uploads. ### Conclusion This example offers a approach for managing file uploads and downloads with Go Fiber and MinIO. It can be expanded to support additional features, such as adding metadata, handling large files, or restricting access to files. ### References - [Fiber Documentation](https://docs.gofiber.io) - [Fiber storage](https://github.com/gofiber/storage) - [MinIO Documentation](https://min.io/docs/) --- ## MongoDB ## MongoDB Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/mongodb) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/mongodb) This project demonstrates how to connect to a MongoDB database in a Go application using the Fiber framework. ### Prerequisites Ensure you have the following installed: - Golang - [Fiber](https://github.com/gofiber/fiber) package - MongoDB - [MongoDB Go Driver](https://github.com/mongodb/mongo-go-driver) ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/mongodb ``` 2. Install dependencies: ```sh go get ``` 3. Start MongoDB using Docker: ```sh docker-compose up -d ``` 4. (Optional) Set the `MONGO_URI` environment variable. Defaults to `mongodb://localhost:27017/fiber_test`: ```sh export MONGO_URI="mongodb://localhost:27017/fiber_test" ``` ### Running the Application 1. Start the application: ```sh go run main.go ``` ### Example Here is an example of how to connect to a MongoDB database in a Fiber application: ```go package main import ( "context" "log" "time" "github.com/gofiber/fiber/v3" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) func main() { // MongoDB connection ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://localhost:27017")) if err != nil { log.Fatal(err) } defer client.Disconnect(context.Background()) // Fiber instance app := fiber.New() // Routes app.Get("/", func(c fiber.Ctx) error { return c.SendString("Hello, MongoDB!") }) // Start server log.Fatal(app.Listen(":3000")) } ``` ### References - [Fiber Documentation](https://docs.gofiber.io) - [MongoDB Documentation](https://docs.mongodb.com) - [MongoDB Go Driver Documentation](https://pkg.go.dev/go.mongodb.org/mongo-driver) --- ## Monitoring with Apitally [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/monitoring-with-apitally) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/monitoring-with-apitally) This project showcases a simple REST API built with the Fiber framework in Go, featuring monitoring and request logging via Apitally. [Apitally](https://apitally.io/fiber) is a lightweight monitoring and analytics tool that helps developers track API usage, performance, and errors with minimal setup. ### Prerequisites Ensure you have Golang installed. ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/monitoring-with-apitally ``` 2. Install dependencies: ```sh go get ``` 3. Obtain a client ID from [Apitally](https://apitally.io/fiber) by signing up and creating a new app in the dashboard. ### Running the application 1. Start the application: ```sh APITALLY_CLIENT_ID=your-client-id go run main.go ``` 2. Make requests to the API: ```sh curl -X GET -H "Authorization: Bearer d7e123f5a2b9c4e8d6a7b2c1f5e9d3a4" http://localhost:3000/v1/books ``` ### Dashboard The Apitally dashboard will show the requests you've made to the API. It provides detailed insights into the API's usage, errors, and performance. Individual requests can be inspected in the request log. You can also set up custom alerts. ![Apitally screenshots](https://assets.apitally.io/screenshots/overview.png) ### References - [Apitally Documentation](https://docs.apitally.io/setup-guides/fiber) - [Fiber Documentation](https://docs.gofiber.io) --- ## Multiple Ports ## Multiple Ports Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/multiple-ports) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/multiple-ports) This project demonstrates how to run a Go application using the Fiber framework on multiple ports. ### Prerequisites Ensure you have the following installed: - Golang - [Fiber](https://github.com/gofiber/fiber) package ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/multiple-ports ``` 2. Install dependencies: ```sh go get ``` ### Running the Application 1. Start the application: ```sh go run main.go ``` ### Example Here is an example of how to run a Fiber application on multiple ports: ```go package main import ( "log" "sync" "github.com/gofiber/fiber/v3" ) func main() { app := fiber.New() app.Get("/", func(c fiber.Ctx) error { return c.SendString("Hello, World!") }) ports := []string{":3000", ":3001"} var wg sync.WaitGroup for _, port := range ports { wg.Add(1) go func(p string) { defer wg.Done() if err := app.Listen(p); err != nil { log.Printf("Error starting server on port %s: %v", p, err) } }(port) } wg.Wait() } ``` In this example: - The application listens on multiple ports (`:3000` and `:3001`). - A `sync.WaitGroup` is used to wait for all goroutines to finish. ### References - [Fiber Documentation](https://docs.gofiber.io) --- ## MySQL ## MySQL Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/mysql) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/mysql) This project demonstrates how to connect to a MySQL database in a Go application using the Fiber framework. ### Prerequisites Ensure you have the following installed: - Golang - [Fiber](https://github.com/gofiber/fiber) package - MySQL - [Go MySQL Driver](https://github.com/go-sql-driver/mysql) ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/mysql ``` 2. Install dependencies: ```sh go get ``` 3. Set up your MySQL database and update the connection string in the code. ### Running the Application 1. Start the application: ```sh go run main.go ``` ### Example Here is an example of how to connect to a MySQL database in a Fiber application: ```go package main import ( "database/sql" "log" "github.com/gofiber/fiber/v3" _ "github.com/go-sql-driver/mysql" ) func main() { // Database connection dsn := "username:password@tcp(127.0.0.1:3306)/dbname" db, err := sql.Open("mysql", dsn) if err != nil { log.Fatal(err) } defer db.Close() // Fiber instance app := fiber.New() // Routes app.Get("/", func(c fiber.Ctx) error { var greeting string err := db.QueryRow("SELECT 'Hello, World!'").Scan(&greeting) if err != nil { return err } return c.SendString(greeting) }) // Start server log.Fatal(app.Listen(":3000")) } ``` ### References - [Fiber Documentation](https://docs.gofiber.io) - [MySQL Documentation](https://dev.mysql.com/doc/) - [Go MySQL Driver Documentation](https://pkg.go.dev/github.com/go-sql-driver/mysql) --- ## Neo4j ## Neo4j Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/neo4j) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/neo4j) This project demonstrates how to connect to a Neo4j database in a Go application using the Fiber framework. ### Prerequisites Ensure you have the following installed: - Golang - [Fiber](https://github.com/gofiber/fiber) package - Neo4j - [Neo4j Go Driver](https://github.com/neo4j/neo4j-go-driver) ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/neo4j ``` 2. Install dependencies: ```sh go get ``` 3. Set up your Neo4j database and export connection settings: ```sh export NEO4J_URI=neo4j://localhost:7687 export NEO4J_USER=neo4j export NEO4J_PASSWORD=password export NEO4J_DATABASE=movies ``` Or start Neo4j via Docker Compose: ```sh docker compose up -d ``` ### Running the Application 1. Start the application: ```sh go run main.go ``` ### Example Here is an example of how to connect to a Neo4j database in a Fiber application: ```go package main import ( "context" "fmt" "log" "os" "github.com/gofiber/fiber/v3" "github.com/neo4j/neo4j-go-driver/v5/neo4j" ) type Movie struct { Title string `json:"title"` Tagline string `json:"tagline"` Released int64 `json:"released"` Director string `json:"director"` } func envOrDefault(key, fallback string) string { if v := os.Getenv(key); v != "" { return v } return fallback } func main() { uri := envOrDefault("NEO4J_URI", "neo4j://localhost:7687") user := envOrDefault("NEO4J_USER", "neo4j") password := envOrDefault("NEO4J_PASSWORD", "password") database := envOrDefault("NEO4J_DATABASE", "movies") driver, err := neo4j.NewDriverWithContext(uri, neo4j.BasicAuth(user, password, "")) if err != nil { log.Fatal(err) } defer func() { if closeErr := driver.Close(context.Background()); closeErr != nil { log.Printf("failed to close neo4j driver: %v", closeErr) } }() if err := driver.VerifyConnectivity(context.Background()); err != nil { log.Fatal(err) } app := fiber.New() app.Post("/movie", func(c fiber.Ctx) error { movie := new(Movie) if err := c.Bind().Body(movie); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()}) } ctx := c.Context() session := driver.NewSession(ctx, neo4j.SessionConfig{ DatabaseName: database, AccessMode: neo4j.AccessModeWrite, }) defer func() { _ = session.Close(ctx) }() query := `CREATE (n:Movie {title: $title, tagline: $tagline, released: $released, director: $director})` _, err := session.ExecuteWrite(ctx, func(tx neo4j.ManagedTransaction) (any, error) { _, runErr := tx.Run(ctx, query, map[string]any{ "title": movie.Title, "tagline": movie.Tagline, "released": movie.Released, "director": movie.Director, }) return nil, runErr }) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } return c.Status(fiber.StatusCreated).JSON(movie) }) app.Get("/movie/:title", func(c fiber.Ctx) error { title := c.Params("title") ctx := c.Context() session := driver.NewSession(ctx, neo4j.SessionConfig{ DatabaseName: database, AccessMode: neo4j.AccessModeRead, }) defer func() { _ = session.Close(ctx) }() query := `MATCH (n:Movie {title: $title}) RETURN n.title AS title, n.tagline AS tagline, n.released AS released, n.director AS director` result, err := session.ExecuteRead(ctx, func(tx neo4j.ManagedTransaction) (any, error) { res, runErr := tx.Run(ctx, query, map[string]any{"title": title}) if runErr != nil { return nil, runErr } if !res.Next(ctx) { if res.Err() != nil { return nil, res.Err() } return nil, nil } row := res.Record().AsMap() return Movie{ Title: row["title"].(string), Tagline: row["tagline"].(string), Released: row["released"].(int64), Director: row["director"].(string), }, nil }) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } if result == nil { return c.SendStatus(fiber.StatusNotFound) } return c.Status(fiber.StatusOK).JSON(result) }) log.Fatal(app.Listen(":3000")) } ``` #### curl Examples **Create a movie:** ```sh curl -X POST http://localhost:3000/movie \ -H "Content-Type: application/json" \ -d '{"title":"The Matrix","tagline":"Welcome to the Real World","released":1999,"director":"Lana Wachowski"}' ``` **Get a movie by title:** ```sh curl http://localhost:3000/movie/The%20Matrix ``` ### References - [Fiber Documentation](https://docs.gofiber.io) - [Neo4j Documentation](https://neo4j.com/docs/) - [Neo4j Go Driver Documentation](https://pkg.go.dev/github.com/neo4j/neo4j-go-driver/v5/neo4j) --- ## OAuth2 ## OAuth2 (GitHub) [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/oauth2) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/oauth2) This project demonstrates how to implement GitHub OAuth2 authentication in a GoFiber application. ### Prerequisites - Go 1.21+ - A [GitHub OAuth App](https://github.com/settings/developers) - Set **Authorization callback URL** to `http://localhost:8080/oauth/redirect` ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/oauth2 ``` 2. Copy the example env file and fill in your credentials: ```sh cp .env.example .env ``` 3. Install dependencies: ```sh go mod download ``` ### Running the Application ```sh go run app.go ``` Then open `http://localhost:8080` in your browser. ### Environment Variables Create a `.env` file in the root directory (see `.env.example`): ```shell # GitHub OAuth2 App credentials CLIENT_ID=your_github_client_id CLIENT_SECRET=your_github_client_secret ``` ### OAuth2 Flow ``` Browser → GET /oauth/begin → generates CSRF state, stores in session → redirects to https://github.com/login/oauth/authorize GitHub → GET /oauth/redirect?code=...&state=... → validates CSRF state → exchanges code for access token via GitHub API → stores token in session → redirects to /welcome.html GET /protected → OAUTHProtected middleware checks session token ``` ### Example: GitHub OAuth2 token exchange ```go // POST https://github.com/login/oauth/access_token // with client_id, client_secret, and code // Response: // {"access_token":"gho_...","token_type":"bearer","scope":""} ``` ### References - [GitHub OAuth Apps documentation](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps) - [GoFiber documentation](https://docs.gofiber.io) --- ## Google OAuth2 ## Fiber with Google OAuth2 [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/oauth2-google) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/oauth2-google) This example demonstrates how to implement Google OAuth2 authentication in a Fiber application. ### Prerequisites - Go 1.25 or higher - Go modules ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/oauth2-google ``` 2. Install dependencies: ```sh go mod tidy ``` 3. Obtain OAuth credentials from [Google Developers Console](https://console.developers.google.com/). 4. Create a `.env` file in the root directory and add your Google OAuth credentials: ```env APP_PORT=3300 GOOGLE_CLIENT_ID=your_client_id GOOGLE_CLIENT_SECRET=your_client_secret GOOGLE_REDIRECT_URL=http://localhost:3300/api/auth/google/callback ``` ### Running the Application 1. Run the application: ```sh go run main.go ``` 2. The server will start on `http://localhost:3300`. ### Endpoints | Method | URL | Description | | ------ | ---------------------------- | ------------------------------------------------ | | GET | /api/ | Redirects to Google login URL | | GET | /api/auth/google/callback | Handles Google OAuth2 callback and returns user's email | ### Example Requests #### Redirect to Google Login ```sh curl -X GET http://localhost:3300/api/ ``` #### Google OAuth2 Callback ```sh curl -X GET http://localhost:3300/api/auth/google/callback?state=state&code=code ``` ### Security #### OAuth2 State Validation (CSRF Protection) The login handler generates a random `state` parameter and stores it in an `HttpOnly`, `Secure`, `SameSite=Lax` cookie before redirecting to Google. The callback handler compares the `state` query parameter returned by Google against the stored cookie value and returns `403 Forbidden` if they do not match. This prevents [Cross-Site Request Forgery (CSRF)](https://owasp.org/www-community/attacks/csrf) attacks against the OAuth2 flow. ### Packages Used - [Godotenv](https://github.com/joho/godotenv) - [Fiber](https://github.com/gofiber/fiber) - [OAuth2](https://github.com/golang/oauth2) --- ## OpenAPI ## OpenAPI Documentation [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/openapi) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/openapi) This project demonstrates how to add OpenAPI 3 documentation to a Go application using [Huma](https://github.com/danielgtaylor/huma). This project got inspired by the [swagger recipe](https://github.com/gofiber/recipes/tree/master/swagger). ### Prerequisites Ensure you have the following installed: - Golang ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/openapi ``` 2. Download Go modules: ```sh go mod tidy ``` ### Running the Application 1. Start the application: ```sh go run main.go ``` 2. Access the API Documentation: Open your browser and navigate to `http://localhost:3000/docs`. 3. OpenAPI Specs: - OpenAPI 3.1 JSON: `http://localhost:3000/openapi.json`. - OpenAPI 3.1 YAML: `http://localhost:3000/openapi.yaml`. - OpenAPI 3.0.3 JSON: `http://localhost:3000/openapi-3.0.json`. - OpenAPI 3.0.3 YAML: `http://localhost:3000/openapi-3.0.yaml`. 4. Generating TypeScript schema: ```sh npx openapi-typescript http://localhost:3000/openapi.json -o schema.ts ``` ### Example Here is a minimal example of adding huma to a existing Fiber codebase: #### `routes.go` ```go import ( ... "github.com/gofiber/fiber/v2" "github.com/danielgtaylor/huma/v2" "github.com/danielgtaylor/huma/v2/adapters/humafiber" ) func New() *fiber.App { app := fiber.New() api := humafiber.New(app, huma.DefaultConfig("Book API", "1.0.0")) // app.Get("/books", handlers.GetAllBooks) // 👈 your existing code huma.Get(api, "/books", handlers.GetAllBooks) // 👈 huma version return app } ``` #### `handlers/book.go` ```go // func GetAllBooks(c *fiber.Ctx) error {} // 👈 your existing code // 👇 huma version func GetAllBooks(ctx context.Context, _ *struct{}) (*GetAllBooksResponse, error) { return &GetAllBooksResponse{Body: books}, nil } ``` ### Enhancing Documentation You can use `huma.Register` to add more information to the OpenAPI specification, such as descriptions with Markdown, examples, tags, and more. ```go // huma.Get(group, "/books/{id}", handlers.GetBookByID) huma.Register(api, huma.Operation{ OperationID: "get-book-by-id", Method: http.MethodGet, Path: "/book/{id}", Summary: "Get a book", Description: "Get a book by book ID.", Tags: []string{"Books"}, }, handlers.GetBookByID) ``` ### References - [Huma Documentation](https://github.com/danielgtaylor/huma) - [Huma Fiber Adapter](https://huma.rocks/features/bring-your-own-router) - [Enhancing Documentation](https://huma.rocks/tutorial/your-first-api/#enhancing-documentation) --- ## Optional Parameter ## Optional Parameter Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/optional-parameter) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/optional-parameter) This project demonstrates how to handle optional parameters in a Go application using the Fiber framework. ### Prerequisites Ensure you have the following installed: - Golang - [Fiber](https://github.com/gofiber/fiber) package ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/optional-parameter ``` 2. Install dependencies: ```sh go get ``` ### Running the Application 1. Start the application: ```sh go run main.go ``` ### Example Here is an example of how to handle optional parameters in a Fiber application: ```go package main import ( "log" "strconv" "github.com/gofiber/fiber/v3" ) func main() { // user list users := [...]string{"Alice", "Bob", "Charlie", "David"} // Fiber instance app := fiber.New() // Route to profile app.Get("/:id?", func(c fiber.Ctx) error { id, err := strconv.Atoi(c.Params("id")) // transform id to array index if err != nil || id < 0 || id >= len(users) { return c.SendStatus(fiber.StatusNotFound) // invalid parameter returns 404 } return c.SendString("Hello, " + users[id] + "!") // custom hello message to user with the id }) // Start server log.Fatal(app.Listen(":3000")) } ``` In this example: - The `:id?` parameter in the route is optional. - If no valid `id` is provided, a `404 Not Found` is returned. - Valid `id` values (0-3) map to users Alice, Bob, Charlie, and David. ### References - [Fiber Documentation](https://docs.gofiber.io) --- ## Parsley ## Fiber with Dependency Injection (via Parsley) [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/parsley) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/parsley) This example demonstrates integrating the [Parsley dependency injection framework](https://github.com/matzefriedrich/parsley) into a GoFiber web application. The goal is to showcase how dependency injection can create a clean, maintainable, and modular structure in your GoFiber projects. ### Prerequisites * Go 1.25+ ### Overview In this example, we use [Parsley](https://github.com/matzefriedrich/parsley) to: * **Bootstrap the application:** Set up and configure the Fiber app using Parsley’s DI container. * **Register dependencies:** Define and register services and route handlers with the DI container. * **Resolve dependencies:** Automatically resolve and inject them where needed. #### Key features * **Modular configuration:** Services are registered in modules, allowing for a clean separation of concerns. * **Automatic dependency injection:** Constructor-based dependency injection wires services together. * **Simplified route management:** Route handlers are registered and managed via the DI container, making it easy to extend and maintain. ### How it works * The `main` function bootstraps the application using Parsley’s `RunParsleyApplication` function. * Modules define how services (such as the Fiber app and route handlers) are registered and configured. * Route handlers are implemented as services that receive their dependencies (like the `Greeter` service) via constructor injection. The `Greeter` service is a simple example of how services can be injected and used within route handlers to handle requests. ### The recipe - step by step This guide demonstrates integrating the Parsley dependency injection framework with the GoFiber web framework. You can either clone the GoFiber recipes repository and navigate to the **parsley** example, or replicate each module while following the article: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/parsley ``` The main entry point of the application is in the `cmd/main.go`. ```go package main import ( "context" "github.com/gofiber/recipes/parsley-app/internal" "github.com/gofiber/recipes/parsley-app/internal/modules" "github.com/matzefriedrich/parsley/pkg/bootstrap" ) func main() { ctx := context.Background() // Runs a Fiber instance as a Parsley-enabled app bootstrap.RunParsleyApplication(ctx, internal.NewApp, modules.ConfigureFiber, modules.ConfigureGreeter) } ``` In this file, the `RunParsleyApplication` function bootstraps the application. It initializes the Parsley application context and configures the GoFiber server with the necessary services and route handlers. Parsley's `bootstrap` package is generic and could also be used with other web application frameworks; the glue is the `NewApp` method, representing a constructor function that must return a `bootstrap.Application` instance. The last parameter of the `RunParsleyApplication` function is an ellipsis parameter accepting `ModuleFunc` values representing service registration functions, which are invoked before calling the constructor function for `bootstrap.Application`. Here, the `ConfigureFiber` and `ConfigureGreeter` functions are specified; those are defined by the `modules` package. #### Configure and register the Fiber instance The `ConfigureFiber` function sets up the Fiber application and registers it as a singleton service within the Parsley framework: ```go package modules import ( "github.com/gofiber/fiber/v3" "github.com/matzefriedrich/parsley/pkg/registration" "github.com/matzefriedrich/parsley/pkg/types" ) var _ types.ModuleFunc = ConfigureFiber func ConfigureFiber(registry types.ServiceRegistry) error { registration.RegisterInstance(registry, fiber.Config{ AppName: "parsley-app-recipe", Immutable: true, }) registry.Register(newFiber, types.LifetimeSingleton) registry.RegisterModule(RegisterRouteHandlers) return nil } func newFiber(config fiber.Config) *fiber.App { return fiber.New(config) } ``` This configuration ensures that the Fiber instance is initialized and available for dependency injection. #### Define and register the application service(s) The `Greeter` service generates greeting messages based on input parameters. In the recipe example application, this service is a dependency required by the handler of the `say-hello` route. ```go package services import "fmt" type Greeter interface { SayHello(name string, polite bool) string } type greeter struct{} func (g *greeter) SayHello(name string, polite bool) string { if polite { return fmt.Sprintf("Good day, %s!\n", name) } return fmt.Sprintf("Hi, %s\n", name) } func NewGreeter() Greeter { return &greeter{} } ``` The `Greeter` service is registered by the `ConfigureGreeter` service registration module: ```go package modules import ( "github.com/gofiber/recipes/parsley-app/internal/services" "github.com/matzefriedrich/parsley/pkg/types" ) func ConfigureGreeter(registry types.ServiceRegistry) error { registry.Register(services.NewGreeterFactory, types.LifetimeTransient) return nil } ``` This setup allows the `Greeter` service to be injected wherever needed within the application. #### Implement and register route handlers Route handlers in this example are services that implement the `RouteHandler` interface, allowing them to register routes with the Fiber application. ```go package route_handlers import ( "strconv" "github.com/gofiber/recipes/parsley-app/internal/services" "github.com/gofiber/fiber/v3" ) type greeterRouteHandler struct { greeter services.Greeter } const defaultPoliteFlag = "true" func (h *greeterRouteHandler) Register(app *fiber.App) { app.Get("/say-hello", h.HandleSayHelloRequest) } func (h *greeterRouteHandler) HandleSayHelloRequest(ctx fiber.Ctx) error { name := ctx.Query("name") politeFlag := ctx.Query("polite", defaultPoliteFlag) polite, _ := strconv.ParseBool(politeFlag) msg := h.greeter.SayHello(name, polite) return ctx.Status(fiber.StatusOK).Send([]byte(msg)) } var _ RouteHandler = &greeterRouteHandler{} func NewGreeterRouteHandler(greeter services.Greeter) RouteHandler { return &greeterRouteHandler{ greeter: greeter, } } ``` This handler responds to GET requests at `/say-hello` with a greeting message, utilizing the `Greeter` service injected via the constructor function. ### Run the application To start the application, execute: ```sh go run ./cmd/main.go ``` Once running, you can test the `say-hello` endpoint via the browser, or from the terminal using `curl`. For this recipe, the default listening port is `5502`: ```sh curl http://localhost:5502/say-hello?name=YourName&polite=true ``` --- ## PostgreSQL ## PostgreSQL Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/postgresql) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/postgresql) This project demonstrates how to connect to a PostgreSQL database in a Go application using the Fiber framework. ### Prerequisites Ensure you have the following installed: - Golang - [Fiber](https://github.com/gofiber/fiber) package - PostgreSQL ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/postgresql ``` 2. Install dependencies: ```sh go get ``` 3. Set up your PostgreSQL database and update the connection string in the code. ### Running the Application 1. Start the application: ```sh go run main.go ``` 2. Access the application at `http://localhost:3000`. ### Example Here is an example of how to connect to a PostgreSQL database in a Fiber application: ```go package main import ( "database/sql" "log" "github.com/gofiber/fiber/v3" _ "github.com/jackc/pgx/v5/stdlib" ) func main() { // Database connection connStr := "user=username dbname=mydb sslmode=disable" db, err := sql.Open("postgres", connStr) if err != nil { log.Fatal(err) } defer db.Close() // Fiber instance app := fiber.New() // Routes app.Get("/", func(c fiber.Ctx) error { var greeting string err := db.QueryRow("SELECT 'Hello, World!'").Scan(&greeting) if err != nil { return err } return c.SendString(greeting) }) // Start server log.Fatal(app.Listen(":3000")) } ``` ### References - [Fiber Documentation](https://docs.gofiber.io) - [PostgreSQL Documentation](https://www.postgresql.org/docs/) - [pgx Driver Documentation](https://pkg.go.dev/github.com/jackc/pgx/v5) --- ## Prefork ## Prefork Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/prefork) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/prefork) This project demonstrates how to use the `Prefork` feature in a Go application using the Fiber framework. Preforking can improve performance by utilizing multiple CPU cores. ### Prerequisites Ensure you have the following installed: - Golang - [Fiber](https://github.com/gofiber/fiber) package ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/prefork ``` 2. Install dependencies: ```sh go get ``` ### Running the Application 1. Start the application: ```sh go run main.go ``` ### Example Here is an example of how to set up the `Prefork` feature in a Fiber application: ```go package main import ( "fmt" "log" "os" "github.com/gofiber/fiber/v3" ) func main() { // Print current process if fiber.IsChild() { fmt.Printf("[%d] Child\n", os.Getppid()) } else { fmt.Printf("[%d] Master\n", os.Getppid()) } // Fiber instance app := fiber.New() // Routes app.Get("/", hello) // Start server with prefork enabled log.Fatal(app.Listen(":3000", fiber.ListenConfig{EnablePrefork: true})) } // Handler func hello(c fiber.Ctx) error { return c.SendString("Hello, World!") } ``` ### References - [Fiber Documentation](https://docs.gofiber.io) - [Fiber Prefork Documentation](https://docs.gofiber.io/api/fiber#prefork) --- ## RabbitMQ ## Fiber and RabbitMQ example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/rabbitmq) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/rabbitmq) ### Description This example demonstrates how to integrate [RabbitMQ](https://www.rabbitmq.com/) with a [Fiber](https://github.com/gofiber/fiber) HTTP server. The API exposes a `/send` endpoint that publishes messages to a RabbitMQ queue. A separate worker process consumes messages from the queue and prints them to the console. ### How it works ![Architecture diagram](https://user-images.githubusercontent.com/11155743/112727736-f8ca2200-8f34-11eb-8d40-12d9f381bd05.png) - The Fiber API server connects to RabbitMQ and exposes `GET /send?msg=`. - Each request publishes the `msg` query parameter as a message to the `TestQueue` queue. - The worker process subscribes to `TestQueue` and logs received messages. ### Prerequisites - [Go](https://golang.org/) 1.21+ - [Docker](https://www.docker.com/) and [Docker Compose](https://docs.docker.com/compose/) ### Environment variables | Variable | Default | Description | |----------------|------------------------------------------|--------------------------| | `RABBITMQ_URL` | `amqp://user:password@localhost:5672/` | RabbitMQ connection URL | ### Setup #### Option A: Docker Compose (recommended) Start all services (RabbitMQ, worker, API) with a single command: ```bash docker compose up --build ``` #### Option B: Manual setup 1. Start RabbitMQ: ```bash make docker.network make docker.rabbitmq ``` 2. Wait ~30 seconds for RabbitMQ to be ready. 3. Start the worker (in a separate terminal): ```bash make docker.worker ``` 4. Run the API server: ```bash make run # or RABBITMQ_URL=amqp://user:password@localhost:5672/ go run main.go ``` ### Endpoints #### `GET /send` Publishes a message to the `TestQueue` RabbitMQ queue. | Parameter | Type | Required | Description | |-----------|--------|----------|----------------------| | `msg` | string | yes | Message to publish | **Success response** `200 OK`: ```json {"status": "message sent"} ``` **Error response** `400 Bad Request`: ```json {"error": "msg parameter required"} ``` ### curl examples Send a message: ```bash curl "http://127.0.0.1:3000/send?msg=Hello%20World" ``` Missing parameter (returns 400): ```bash curl "http://127.0.0.1:3000/send" ``` ### Worker output When a message is received, the worker prints: ```console 2021/03/27 16:32:35 Successfully connected to RabbitMQ instance 2021/03/27 16:32:35 [*] - Waiting for messages 2021/03/27 16:32:35 [*] - Run Fiber API server and go to http://127.0.0.1:3000/send?msg= 2021/03/27 16:33:24 Received message: Hello World ``` ### RabbitMQ management dashboard The RabbitMQ management UI is available at [http://localhost:15672](http://localhost:15672) (default credentials: `user` / `password`). ![RabbitMQ dashboard](https://user-images.githubusercontent.com/11155743/112728092-8fe3a980-8f36-11eb-9d79-be8eab26358b.png) --- ## React ## React Fiber [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/react-router) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/react-router) A sample application to showcase serving React (with Router) with an almost bare Fiber. Hopefully, this application can be of use (as a reference or others) for those who wants to serve their client-side SPA with Fiber. ### Technologies - Go with Fiber - React with TypeScript and React Router - Docker ### Application - This application has three routes: `/`, `/react`, and a catch-all, 404 route. `/` will show the Fiber logo, `/react` will show the React logo, and the 404 route will show both logos. - As this application serves the frontend while backed by a server, the client-side routing will work well and will not cause any issue (unlike if you are running without a file server). You can type the URL route manually in the browser and it will still work and will render the accurate page, so no worries. - This is a simplified form of Create React App with TypeScript. With that being said, that's why there is no `manifest.json`, `logo512.png`, and other extra things like that. - I restructured the project structure to be a bit more modular by categorizing files to `assets`, `components`, and `styles`. I also made it so all of the CSS is loaded in `index.tsx` for easier seeing. - I also moved several dependencies to their appropriate places, such as `@types` and `test` in development dependencies instead of dependencies. ### Installation It is recommended that you use Docker to instantly run this application. After running the Docker application, please open `localhost:8080` in your browser. Make sure you are in the `react-router` folder before running these commands. ```bash docker build . -t react-router:latest docker run -d -p 8080:8080 react-router:latest ``` If you prefer doing things manually, then the installation steps are as follows: - Clone the repository by using `git clone git@github.com:gofiber/recipes.git`. - Switch to the application by using `cd recipes/react-router`. - Install npm dependencies by using `cd web && npm ci`. - Build frontend by using `npm run build`. - Run the Fiber application by using `go run cmd/react-router/main.go`. Don't forget to return to the main repository by using `cd ..` (assuming you are in `web` folder). - Open `localhost:8080` in your browser. --- ## Recover Middleware ## Recover Middleware Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/recover) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/recover) This project demonstrates how to implement a recovery mechanism in a Go application using the Fiber framework's `Recover` middleware. ### Prerequisites Ensure you have the following installed: - Golang - [Fiber](https://github.com/gofiber/fiber) package ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/recover ``` 2. Install dependencies: ```sh go get ``` ### Running the Application 1. Start the application: ```sh go run main.go ``` ### Example Here is an example of how to set up the `Recover` middleware in a Fiber application: ```go package main import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/recover" ) func main() { app := fiber.New() // Use the Recover middleware app.Use(recover.New()) app.Get("/", func(c fiber.Ctx) error { // This will cause a panic panic("something went wrong") }) app.Listen(":3000") } ``` ### References - [Fiber Documentation](https://docs.gofiber.io) - [Fiber Recover Middleware Documentation](https://docs.gofiber.io/api/middleware/recover) --- ## RSS Feed [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/rss-feed) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/rss-feed) This project demonstrates how to create an RSS feed in a Go application using the Fiber framework. ### Prerequisites Ensure you have the following installed: - Golang - [Fiber](https://github.com/gofiber/fiber) package ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/rss-feed ``` 2. Install dependencies: ```sh go get ``` ### Running the Application 1. Start the application: ```sh go run main.go ``` ### Example This recipe uses [Mustache templates](https://github.com/cbroglie/mustache) (via the `gofiber/template/mustache` engine) to render an RSS XML response. The template lives in `./xmls/example.xml`. **`xmls/example.xml`:** ```xml {{{Lang}}} {{{Title}}} {{{Greetings}}} ``` **`main.go`:** ```go package main import ( "log" "github.com/gofiber/fiber/v3" "github.com/gofiber/template/mustache/v3" ) func main() { engineXML := mustache.New("./xmls", ".xml") if err := engineXML.Load(); err != nil { log.Fatal(err) } app := fiber.New() app.Get("/rss", func(c fiber.Ctx) error { // Set Content-Type to application/rss+xml c.Type("rss") // Render Mustache template with data return engineXML.Render(c, "example", fiber.Map{ "Lang": "en", "Title": "hello-rss", "Greetings": "Hello World", }) }) log.Fatal(app.Listen(":3000")) } ``` #### Testing with curl ```sh curl http://localhost:3000/rss ``` ### References - [Fiber Documentation](https://docs.gofiber.io) - [Fiber Template - Mustache](https://github.com/gofiber/template/tree/master/mustache) - [cbroglie/mustache](https://pkg.go.dev/github.com/cbroglie/mustache) --- ## Seenode ## Seenode Deployment Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/seenode) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/seenode) This project demonstrates how to deploy a Go application using the Fiber framework on Seenode. ### Prerequisites Ensure you have the following installed: - Golang - [Fiber](https://github.com/gofiber/fiber) package - [Seenode account](https://cloud.seenode.com) ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/seenode ``` 2. Install dependencies: ```sh go mod tidy ``` 3. Create a Seenode account and connect your repository: - Go to [Seenode Dashboard](https://cloud.seenode.com) - Create a new Web Service - Connect your Git repository 4. Configure deployment: - **Build Command**: `go build -o app main.go` - **Start Command**: `./app` 5. Deploy the application: ```sh git add . git commit -m "Deploy to Seenode" git push ``` ### Running the Application 1. Open the application in your browser using the provided Seenode URL. ### Example See `./main.go` for the full application code. It exposes: - `GET /` — welcome message - `GET /health` — health check, returns `{"status":"ok"}` ### References - [Fiber Documentation](https://docs.gofiber.io) - [Seenode Documentation](https://seenode.com/docs/frameworks/go/fiber/) - [Seenode](https://seenode.com) --- ## Server Timing [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/server-timing) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/server-timing) This project demonstrates how to implement Server-Timing headers in a Go application using the Fiber framework. ### Prerequisites Ensure you have the following installed: - Golang - [Fiber](https://github.com/gofiber/fiber) package ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/server-timing ``` 2. Install dependencies: ```sh go get ``` ### Running the Application 1. Start the application: ```sh go run main.go ``` ### Example Here is an example of how to set up Server-Timing headers in a Fiber application: ```go package main import ( "fmt" "log" "time" "github.com/gofiber/fiber/v3" ) func main() { app := fiber.New() app.Use(func(c fiber.Ctx) error { start := time.Now() err := c.Next() // dur value must be in milliseconds per W3C spec c.Append("Server-Timing", fmt.Sprintf("app;dur=%.2f", float64(time.Since(start).Microseconds())/1000.0)) return err }) app.Get("/", func(c fiber.Ctx) error { return c.SendString("Hello, World!") }) log.Fatal(app.Listen(":3000")) } ``` #### Testing with curl ```sh curl -i http://localhost:3000/ ``` Example response header: ``` Server-Timing: app;dur=2001.23 ``` ### References - [Fiber Documentation](https://docs.gofiber.io) - [Server-Timing Header Documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server-Timing) --- ## Sessions + SQLite3 ## Sessions - SQLite3 [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/sessions-sqlite3) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/sessions-sqlite3) This example uses the SQLite3 storage package to persist user sessions. While the storage package can automatically create the sessions table at initialization, we create it manually to add an additional "u" column. This custom column serves several purposes: - Enables efficient querying of sessions by user identifier - Allows tracking of multiple sessions per user - Facilitates session cleanup for specific users The default table schema only stores session data and expiry, making it difficult to associate sessions with specific users. The "u" column solves this limitation. ### Prerequisites - Go 1.25 or higher - Go modules ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/sessions-sqlite3 ``` 2. Install dependencies: ```sh go mod tidy ``` 3. Create the sessions table in SQLite3: ```sql CREATE TABLE sessions ( k TEXT PRIMARY KEY, v BLOB, e INTEGER, u TEXT ); ``` ### Running the Application 1. Run the application: ```sh go run main.go ``` 2. The server will start on `http://localhost:3000`. ### Explanation This example uses the SQLite3 storage package to persist user sessions. The storage package can create the sessions table for you at initialization, but for the purpose of this example, the table is created manually with an additional "u" column to better query all user-related sessions. ### Security Notes - The UID passed from the front-end is used directly to identify the user. In production, **always validate the UID** server-side (e.g., verify credentials against your database) instead of trusting client-supplied values. - Session type assertions (`s.Get("uid").(string)`) are guarded with an `ok` check to prevent panics on malformed or missing session data. --- ## Socketio ## WebSocket Chat Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/socketio) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/socketio) This example demonstrates how to create a simple chatroom using WebSockets. The chatroom supports multiple users and allows them to send messages to each other. ### Prerequisites - Go 1.25 or higher - Go modules ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/socketio ``` 2. Install dependencies: ```sh go mod tidy ``` ### Running the Application 1. Run the application: ```sh go run main.go ``` 2. The server will start on `http://localhost:3000`. ### Connecting to the WebSocket To connect to the WebSocket, use the following URL: ``` ws://localhost:3000/ws/ ``` ### Message Object Example Here is an example of a message object that can be sent between users: ```json { "from": "", "to": "", "data": "hello" } ``` --- ## Single Page Application (SPA) [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/spa) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/spa) This project demonstrates how to set up a Single Page Application (SPA) using React for the frontend and Go with the Fiber framework for the backend. ### Prerequisites Ensure you have the following installed: - Golang - Node.js - npm ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/spa ``` 2. Install frontend dependencies: ```sh cd frontend npm install ``` 3. Install backend dependencies: ```sh cd ../backend go get ``` ### Usage #### Building Frontend Assets 1. Build the frontend assets: ```sh cd frontend npm run build ``` 2. Watch frontend assets for changes: ```sh npm run dev ``` #### Running the Application 1. Start the Fiber backend application: ```sh cd backend go run main.go ``` ### Example Here is an example of how to serve a SPA with Fiber v3 using the `static` middleware: ```go package main import ( "log" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/static" ) func main() { app := fiber.New() // Serve Single Page Application on "/web". // The NotFoundHandler falls back to index.html so client-side routing works. app.Get("/web*", static.New("dist", static.Config{ NotFoundHandler: func(c fiber.Ctx) error { return c.SendFile("./dist/index.html") }, })) // Start server log.Fatal(app.Listen(":3000")) } ``` ### References - [Fiber Documentation](https://docs.gofiber.io) - [React Documentation](https://reactjs.org/docs/getting-started.html) - [Tailwind CSS Documentation](https://tailwindcss.com/docs) - [Parcel Documentation](https://parceljs.org/docs) --- ## Sqlboiler ## Fiber with sqlboiler [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/sqlboiler) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/sqlboiler) > #### 🎯 [Fiber](https://github.com/gofiber/fiber) + [Sqlboiler](https://github.com/aarondl/sqlboiler) Example ### 👀 Usage ##### 1. Run Postgres ```bash $ docker compose build ``` ```bash $ docker compose up ``` ##### 2. Wait 1-2 minutes ```console [+] Running 2/0 ✔ Network sqlboiler_default Created 0.0s ✔ Container postgres Created 0.0s Attaching to postgres postgres | postgres | PostgreSQL Database directory appears to contain a database; Skipping initialization postgres | postgres | 2023-09-22 01:09:46.453 UTC [1] LOG: starting PostgreSQL 16.0 (Debian 16.0-1.pgdg120+1) on aarch64-unknown-linux-gnu, compiled by gcc (Debian 12.2.0-14) 12.2.0, 64-bit postgres | 2023-09-22 01:09:46.453 UTC [1] LOG: listening on IPv4 address "0.0.0.0", port 5432 postgres | 2023-09-22 01:09:46.453 UTC [1] LOG: listening on IPv6 address "::", port 5432 postgres | 2023-09-22 01:09:46.454 UTC [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432" postgres | 2023-09-22 01:09:46.461 UTC [30] LOG: database system was shut down at 2023-09-22 01:09:44 UTC postgres | 2023-09-22 01:09:46.468 UTC [1] LOG: database system is ready to accept connections ``` ##### 3. You have to migrate the database. > ###### 🎯 It is a "database-first" ORM as opposed to "code-first" (like gorm/gorp). That means you must first create your database schema. > ###### 🎯 I used [golang-migrate](https://github.com/golang-migrate/migrate) to proceed with the migrate. ###### 1. Make Migration files ```bash $ migrate create -ext sql -dir ./migrations -seq create_initial_table ``` ```console sqlboiler/migrations/000001_create_initial_table.up.sql sqlboiler/migrations/000001_create_initial_table.up.sql ``` ###### 2. Migrate ```bash $ migrate -path migrations -database "postgresql://user:password@localhost:5432/fiber_demo?sslmode=disable" -verbose up ``` ```console 2023/09/22 20:00:00 Start buffering 1/u create_initial_table 2023/09/22 20:00:00 Read and execute 1/u create_initial_table 2023/09/22 20:00:00 Finished 1/u create_initial_table (read 24.693541ms, ran 68.30925ms) 2023/09/22 20:00:00 Finished after 100.661625ms 2023/09/22 20:00:00 Closing source and database ``` ###### 3. Rollback Migrate ```bash $ migrate -path migrations -database "postgresql://user:password@localhost:5432/fiber_demo?sslmode=disable" -verbose down ``` ```console 2023/09/22 20:00:00 Are you sure you want to apply all down migrations? [y/N] y 2023/09/22 20:00:00 Applying all down migrations 2023/09/22 20:00:00 Start buffering 1/d create_initial_table 2023/09/22 20:00:00 Read and execute 1/d create_initial_table 2023/09/22 20:00:00 Finished 1/d create_initial_table (read 39.681125ms, ran 66.220125ms) 2023/09/22 20:00:00 Finished after 1.83152475s ``` ##### 4. Use sqlboiler ###### 1. Install ```bash # Go 1.25 and above: $ go install github.com/aarondl/sqlboiler/v4@latest $ go install github.com/aarondl/sqlboiler/v4/drivers/sqlboiler-psql@latest ``` ###### 2. Create a configuration file > ###### 🎯 The configuration file should be named sqlboiler.toml ###### Example ```toml output = "models" wipe = true no-tests = true add-enum-types = true [psql] dbname = "fiber_demo" host = "localhost" port = 5432 user = "user" pass = "password" schema = "schema" blacklist = ["migrations", "other"] ``` ###### 3. Create models > ###### 🎯 After creating a configuration file that points at the database we want to generate models for, we can invoke the sqlboiler command line utility. ```bash $ sqlboiler psql ``` ```text models/ ├── author.go ├── boil_queries.go ├── boil_table_names.go ├── boil_types.go ├── boil_view_names.go ├── post.go ├── schema_migrations.go ``` --- ## Sqlc ## Fiber with sqlc [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/sqlc) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/sqlc) > #### 🎯 [fiber](https://github.com/gofiber/fiber) + [sqlc](https://github.com/sqlc-dev/sqlc) Example ### Usage ##### 1. Run Postgres ```bash $ docker compose build ``` ```bash $ docker compose up ``` ##### 2. Wait 1-2 minutes ```console [+] Running 2/0 ✔ Network sqlc_default Created 0.1s ✔ Container postgres Created 0.0s Attaching to postgres postgres | postgres | PostgreSQL Database directory appears to contain a database; Skipping initialization postgres | postgres | postgres | 2023-09-28 09:17:50.737 UTC [1] LOG: starting PostgreSQL 16.0 (Debian 16.0-1.pgdg120+1) on aarch64-unknown-linux-gnu, compiled by gcc (Debian 12.2.0-14) 12.2.0, 64-bit postgres | 2023-09-28 09:17:50.737 UTC [1] LOG: listening on IPv4 address "0.0.0.0", port 5432 postgres | 2023-09-28 09:17:50.737 UTC [1] LOG: listening on IPv6 address "::", port 5432 postgres | 2023-09-28 09:17:50.740 UTC [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432" postgres | 2023-09-28 09:17:50.751 UTC [30] LOG: database system was shut down at 2023-09-28 08:50:35 UTC postgres | 2023-09-28 09:17:50.770 UTC [1] LOG: database system is ready to accept connections ``` ##### 3. You have to migrate the database. > ##### 🎯 It is a "database-first" ORM as opposed to "code-first" (like gorm/gorp). That means you must first create your database schema. > ##### 🎯 I used [golang-migrate](https://github.com/golang-migrate/migrate) to proceed with the migrate. ###### 1. Make Migration files ```bash $ migrate create -ext sql -dir ./database/migrations -seq create_initial_table ``` ```console sqlc/database/migrations/000001_create_initial_table.up.sql sqlc/database/migrations/000001_create_initial_table.up.sql ``` ###### 2. Migrate ```bash $ migrate -path database/migrations -database "postgresql://user:password@localhost:5432/fiber_demo?sslmode=disable" -verbose up ``` ```console 2023/09/28 20:00:00 Start buffering 1/u create_initial_table 2023/09/28 20:00:00 Read and execute 1/u create_initial_table 2023/09/28 20:00:00 Finished 1/u create_initial_table (read 24.693541ms, ran 68.30925ms) 2023/09/28 20:00:00 Finished after 100.661625ms 2023/09/28 20:00:00 Closing source and database ``` ###### 3. Rollback Migrate ```bash $ migrate -path database/migrations -database "postgresql://user:password@localhost:5432/fiber_demo?sslmode=disable" -verbose down ``` ```console 2023/09/28 20:00:00 Are you sure you want to apply all down migrations? [y/N] y 2023/09/28 20:00:00 Applying all down migrations 2023/09/28 20:00:00 Start buffering 1/d create_initial_table 2023/09/28 20:00:00 Read and execute 1/d create_initial_table 2023/09/28 20:00:00 Finished 1/d create_initial_table (read 39.681125ms, ran 66.220125ms) 2023/09/28 20:00:00 Finished after 1.83152475s ``` ##### 4. Use sqlc ###### 1. Install ```bash # Go 1.25 and above: $ go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest # Legacy Go versions (<1.25): go get github.com/sqlc-dev/sqlc/cmd/sqlc ``` ###### 2. Create a configuration file ###### Example ###### sqlc.yaml ```yaml version: "2" sql: - engine: "postgresql" queries: "database/query" schema: "database/migrations" gen: go: package: "sqlc" out: "database/sqlc" ``` ###### author.sql ```sql -- name: GetAuthors :many SELECT * FROM author; -- name: GetAuthor :one SELECT * FROM author WHERE id = $1; -- name: NewAuthor :one INSERT INTO author (email, name) VALUES ($1, $2) RETURNING *; -- name: UpdateAuthor :one UPDATE author SET email = $1, name = $2 WHERE id = $3 RETURNING *; -- name: DeleteAuthor :exec DELETE FROM author WHERE id = $1; ``` ###### post.sql ```sql -- name: GetPosts :many SELECT * FROM post; -- name: GetPost :one SELECT * FROM post WHERE id = $1; -- name: NewPost :one INSERT INTO post (title, content, author) VALUES ($1, $2, $3) RETURNING *; -- name: UpdatePost :one UPDATE post SET title = $1, content = $2, author = $3 WHERE id = $4 RETURNING *; -- name: DeletePost :exec DELETE FROM post WHERE id = $1; ``` ###### 3. Generate ```bash $ sqlc generate ``` ```text sqlc/ ├── author.sql.go ├── db.go ├── models.go ├── post.sql.go ``` ##### 5. Reference [sqlc document](https://docs.sqlc.dev/en/stable/) ### API Endpoints #### Authors | Method | Path | Description | |--------|------|-------------| | GET | `/authors` | List all authors | | GET | `/authors/:id` | Get an author by ID | | POST | `/authors` | Create a new author | | PUT | `/authors/:id` | Update an author | | DELETE | `/authors/:id` | Delete an author | #### Posts | Method | Path | Description | |--------|------|-------------| | GET | `/posts` | List all posts | | GET | `/posts/:id` | Get a post by ID | | POST | `/posts` | Create a new post | | PUT | `/posts/:id` | Update a post | | DELETE | `/posts/:id` | Delete a post | --- ## Server-Sent Events ## Server-Sent Events with Fiber [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/sse) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/sse) This example demonstrates how to implement Server-Sent Events (SSE) in a Fiber application. ### Description Server-Sent Events (SSE) allow servers to push updates to the client over a single HTTP connection. This is useful for real-time applications where the server needs to continuously send data to the client, such as live feeds, notifications, or real-time charts. ### Prerequisites - Go 1.25 or higher - Go modules ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/sse ``` 2. Install dependencies: ```sh go mod tidy ``` ### Running the Application 1. Run the application: ```sh go run main.go ``` 2. The server will start on `http://localhost:3000`. ### Endpoints - **GET /**: Index page - **GET /sse**: SSE route - **PUT /publish**: Send messages via SSE ### Example Usage By default, the example will run on port `3000`; this can be changed by modifying the `appPort` constant in `main.go`. The index page connects to the SSE endpoint using a relative URL (`/sse`), so it works regardless of the host or port. 1. Open your browser and navigate to `http://localhost:3000`. 2. The client will automatically connect to the SSE endpoint and start receiving updates from the server. 3. The `/sse` endpoint will publish the current time to the client every two seconds #### Custom Messages To send a custom message, send a `PUT` request to the `/publish` endpoint in the following JSON format ```json { "message": "Hello, World!" } ``` Messages sent to the `/publish` endpoint will be added to a queue that is read from in FIFO order. You can test this by using curl in an iterator If you are using the Bash or Zsh shell: ```sh for i in {1..10}; do curl -X PUT -H 'Content-type: application/json' --data "{\"message\":\"SSE TEST $i\"}" http://localhost:3000/publish done ``` If you are using fish: ```sh for i in (seq 1 10) curl -X PUT -H 'Content-type: application/json' --data "{\"message\":\"SSE TEST $i\"}" http://localhost:3000/publish end ``` Once published, your added messages will begin appearing in the output at `http://localhost:3000`. Once the queue is empty and no user-published messages are left, `/sse` will return to it's standard behavior of displaying the current time. ### Code Overview #### `main.go` The main Go file sets up the Fiber application and handles the SSE connections. It includes the necessary configuration to send events to the client. ### Additional Information Server-Sent Events (SSE) is a standard allowing servers to push data to web clients over HTTP. Unlike WebSockets, which require a full-duplex connection, SSE uses a unidirectional connection from the server to the client. This makes SSE simpler to implement and more efficient for scenarios where only the server needs to send updates. For more information on SSE, you can refer to the following resources: - [Server-Sent Events on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) - [Server-Sent Events on Wikipedia](https://en.wikipedia.org/wiki/Server-sent_events) --- ## Stream Request Body [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/stream-request-body) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/stream-request-body) This project demonstrates how to handle streaming request bodies in a Go application using the Fiber framework. ### Prerequisites Ensure you have the following installed: - Golang - [Fiber](https://github.com/gofiber/fiber) package ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/stream-request-body ``` 2. Install dependencies: ```sh go get ``` ### Running the Application 1. Start the application: ```sh go run main.go ``` ### Example Here is an example of how to handle a streaming request body in Go using Fiber: ```go package main import ( "io" "log" "github.com/gofiber/fiber/v3" ) func main() { app := fiber.New() // Enable request body streaming. app.Server().StreamRequestBody = true // You can test the route by using cURL: // curl -X POST --data-binary @/path/to/large/file localhost:3000 app.Post("/", func(c fiber.Ctx) error { reader := c.RequestCtx().RequestBodyStream() if reader == nil { return c.SendStatus(fiber.StatusOK) } // Read 1MiB at a time buffer := make([]byte, 0, 1024*1024) for { length, err := io.ReadFull(reader, buffer[:cap(buffer)]) // Cap the buffer based on the actual length read buffer = buffer[:length] if length > 0 { // Process the chunk - e.g., write to file, parse data, etc. log.Printf("Read %d bytes", length) } if err != nil { // EOF or ErrUnexpectedEOF means all data has been read. // ErrUnexpectedEOF means the last chunk was smaller than the // buffer, which is normal for the final (or only) chunk. if err == io.EOF || err == io.ErrUnexpectedEOF { break } return err } } return c.SendStatus(fiber.StatusOK) }) log.Fatal(app.Listen(":3000")) } ``` #### curl Example ```sh curl -X POST --data-binary @/path/to/large/file http://localhost:3000 ``` ### References - [Fiber Documentation](https://docs.gofiber.io) - [Go io Package Documentation](https://pkg.go.dev/io) --- ## Svelte Netlify ## Svelte + Fiber on Netlify [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/svelte-netlify) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/svelte-netlify) [![Netlify Status](https://api.netlify.com/api/v1/badges/143c3c42-60f7-427a-b3fd-8ca3947a2d40/deploy-status)](https://app.netlify.com/sites/gofiber-svelte/deploys) A Go Fiber API deployed as a Netlify Function, with a Svelte frontend. The API provides IP geolocation via [ip-api.com](http://ip-api.com). **Demo:** https://gofiber-svelte.netlify.app/ ### Prerequisites - **Go** 1.21+ - **Node.js** 18+ (for the Svelte frontend) - **Netlify CLI** — `npm install -g netlify-cli` ### Local Development 1. Install frontend dependencies and build the Svelte app: ```bash npm install npm run build ``` 2. Build the Go function: ```bash ./build.sh ``` 3. Start the local dev server: ```bash netlify dev ``` The app will be available at `http://localhost:8888`. ### Deploy to Netlify #### Via Netlify CLI ```bash netlify deploy --prod ``` #### Via Git Connect your repository in the Netlify dashboard. The `netlify.toml` configures the build automatically. ### How It Works - `./build.sh` compiles the Go binary to `cmd/gateway/gateway` and places it in the `functions/` directory. - Netlify serves the binary as a [Netlify Function](https://functions.netlify.com/). - Static files under `public/` are served directly (entry point: `index.html`). - API calls to `/api/*` are redirected server-side to `/.netlify/functions/gateway/:splat` (status 200). ### Project Structure ``` . ├── cmd/gateway/ # Lambda entry point (main.go) ├── handler/ # Fiber route handlers ├── public/ # Compiled Svelte frontend ├── build.sh # Build script for the Go function └── netlify.toml # Netlify build configuration ``` ### Notes - Netlify Functions are limited to 125,000 requests/month on the free tier (~2.89 req/min). Response caching with a 10-minute TTL is applied to the geolocation endpoint to stay within limits. --- ## Sveltekit Embed ## Fiber Sveltekit Embed App [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/sveltekit-embed) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/sveltekit-embed) ![image](https://github.com/gofiber/recipes/assets/40540244/2aa084b8-9bbc-46f3-9759-930857429f05) This application is a full-stack project built using Sveltekit, Tailwind CSS, Fiber. It showcases the construction of a monolithic architecture for a full-stack application. ### Run the Project To run the project, follow these steps: 1. Execute the following command to run all the necessary commands for building and running the application: ```bash make all ``` 2. Once the build process is complete, you can start the application by running: ```bash ./app ``` ### Available Commands The following commands are available to manage the project: | Command | Description | | --- | --- | | `info` | Info command. Displays the available commands and the purpose of the application. | | `go-build` | Builds the Golang project and creates an `app` file. | | `svelte-build` | Builds the SvelteKit project. It first installs the dependencies and then performs the project build. | | `all` | Runs both `svelte-build` and `go-build` commands sequentially. | | `build` | Same as `all`. This is the target CI runs. | ### Usage To use this application, run the following command: ```bash make ``` API Routes ---------- The Go Fiber application provides the following API routes: | Route | Description | | --- | --- | | `/*` | Serves static files from the specified directory (`template.Dist()`). If a file is not found, it serves `index.html`. | Go Dependencies --------------- - **Go Modules:** Go's built-in package manager used to manage dependencies for Go projects. - **Fiber:** A fast and minimalist web framework for Golang. Npm Dependencies ---------------- - **SvelteKit:** A JavaScript framework used to build modern web applications. - **Tailwind CSS:** A fast and customizable CSS styling library. Can be used in SvelteKit projects. - **Skeleton UI:** This is a fully featured UI Toolkit for building reactive interfaces quickly using Svelte and Tailwind. ---------------- Author: [@ugurkorkmaz](https://github.com/ugurkorkmaz) --- ## create-svelte Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte). ### Creating a project If you're seeing this, you've probably already done this step. Congrats! ```bash # create a new project in the current directory npm create svelte@latest # create a new project in my-app npm create svelte@latest my-app ``` ### Developing Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: ```bash npm run dev # or start the server and open the app in a new browser tab npm run dev -- --open ``` ### Building To create a production version of your app: ```bash npm run build ``` You can preview the production build with `npm run preview`. > To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment. --- ## Swagger ## Swagger API Documentation [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/swagger) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/swagger) This project demonstrates how to integrate Swagger for API documentation in a [Fiber](https://github.com/gofiber/fiber) application using [`gofiber/contrib/swaggerui`](https://github.com/gofiber/contrib/tree/main/swaggerui) and [`swaggo/swag`](https://github.com/swaggo/swag). ### Prerequisites - Go 1.21+ - [Swag CLI](https://github.com/swaggo/swag) for generating Swagger docs - PostgreSQL (connection configured via environment variables) ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/swagger ``` 2. Install the Swag CLI: ```sh go install github.com/swaggo/swag/cmd/swag@latest ``` 3. Configure the database connection via environment variables: ```sh export DB_USER=postgres export DB_PASSWORD=secret export DB_HOST=localhost export DB_NAME=books export DB_PORT=5432 ``` ### Generating Swagger Docs Generate (or regenerate) the Swagger documentation from the annotations in your source code: ```sh swag init ``` This writes `docs/docs.go`, `docs/swagger.json`, and `docs/swagger.yaml`. ### Running the Application ```sh go run main.go ``` The application starts on port **3000**. ### Accessing the Swagger UI Open your browser and navigate to: ``` http://localhost:3000/docs/docs ``` The Swagger UI is served by [`gofiber/contrib/v3/swaggerui`](https://github.com/gofiber/contrib/tree/main/swaggerui) and reads the spec from `./docs/swagger.json`. ### API Endpoints All routes are prefixed with `/api/v1`. | Method | Path | Description | |--------|------|-------------| | GET | `/api/v1/books` | List all books | | GET | `/api/v1/books/:id` | Get a book by ID | | POST | `/api/v1/books` | Register a new book | | DELETE | `/api/v1/books/:id` | Delete a book by ID | ### Example Annotate your Fiber handler functions with Swag comments to generate docs: ```go // GetBookByID returns a book by ID // @Summary Get book by ID // @Description Get a single book by its ID // @Tags books // @Accept json // @Produce json // @Param id path int true "Book ID" // @Success 200 {object} ResponseHTTP{data=models.Book} // @Failure 404 {object} ResponseHTTP{} // @Router /v1/books/{id} [get] func GetBookByID(c fiber.Ctx) error { // Your code here } ``` ### curl Examples ```sh # List all books curl http://localhost:3000/api/v1/books # Get book by ID curl http://localhost:3000/api/v1/books/1 # Create a book curl -X POST http://localhost:3000/api/v1/books \ -H "Content-Type: application/json" \ -d '{"title":"The Go Programming Language","author":"Donovan & Kernighan"}' # Delete a book curl -X DELETE http://localhost:3000/api/v1/books/1 ``` ### References - [Fiber](https://github.com/gofiber/fiber) - [gofiber/contrib/swaggerui](https://github.com/gofiber/contrib/tree/main/swaggerui) - [Swag Documentation](https://github.com/swaggo/swag) --- ## Tableflip Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/tableflip) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/tableflip) This example demonstrates how to use [tableflip](https://github.com/cloudflare/tableflip) for graceful upgrades in a Go application. ### What is Tableflip? Tableflip is a library that allows you to update the running code and/or configuration of a network service without disrupting existing connections. It achieves this by starting a new process, transferring clients to it, and then exiting the old process. #### Goals of Tableflip - No old code keeps running after a successful upgrade. - The new process has a grace period for initialization. - Crashing during initialization is acceptable. - Only a single upgrade is ever run in parallel. - Tableflip works on Linux and macOS. ### Steps 1. **Build v0.0.1 Demo:** ```bash go build -o demo main.go ``` 2. **Run the Demo and Create a GET Request to `127.0.0.1:8080/version`:** ```bash [PID: 123] v0.0.1 ``` 3. **Prepare a New Version:** - Change the `main.go` to update the version to "v0.0.2". - Rebuild the demo: ```bash go build -o demo main.go ``` 4. **Kill the Old Process:** ```bash kill -s HUP 123 ``` 5. **Create the Request to the Version API Again:** ```bash [PID: 123] v0.0.2 ``` The client is completely immune to server upgrades and reboots, and our application updates gracefully! --- ## Template ## Template Project [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/template) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/template) This project demonstrates how to set up a Go application with template rendering using the Django template engine. ### Prerequisites Ensure you have the following installed: - Golang ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/template ``` 2. Install dependencies: ```sh go get ``` ### Running the Application 1. Start the Fiber application: ```sh go run main.go ``` ### Example Here is an example of how to set up a basic route with template rendering in Go: ```go package main import ( "log" "github.com/gofiber/fiber/v3" "github.com/gofiber/template/django/v4" ) func main() { // Initialize the template engine engine := django.New("./views", ".html") // Create a new Fiber instance with the template engine app := fiber.New(fiber.Config{ Views: engine, }) // Define a route app.Get("/", func(c fiber.Ctx) error { return c.Render("index", fiber.Map{ "Title": "Hello, World!", }) }) log.Fatal(app.Listen(":3000")) } ``` ### References - [Fiber Documentation](https://docs.gofiber.io) - [Fiber Template Documentation](https://github.com/gofiber/template) --- ## Template Asset Bundling [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/template-asset-bundling) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/template-asset-bundling) This example demonstrates how to integrate asset bundling into a Go web application using [Fiber](https://github.com/gofiber/fiber), [gofiber/template](https://github.com/gofiber/template) for HTML template rendering, [Tailwind CSS](https://tailwindcss.com) for utility-first styling, and [Parcel](https://parceljs.org) as a zero-configuration asset bundler. Parcel processes and hashes the CSS assets, which are then served as static files by Fiber. ### Prerequisites Ensure you have the following installed: - [Go](https://golang.org/dl/) 1.21+ - [Node.js](https://nodejs.org/) 18+ and npm ### Project Structure ``` template-asset-bundling/ ├── app.go # Fiber application entry point ├── handlers/ │ └── handlers.go # Route handlers (Home, About, NotFound) ├── views/ │ ├── layouts/ │ │ └── main.html # Base layout template │ ├── partials/ # Reusable template partials │ ├── index.html # Home page template │ ├── about.html # About page template │ └── 404.html # Not found template ├── assets/ │ └── app.css # Tailwind CSS source (input) ├── public/ │ └── assets/ # Compiled assets output (git-ignored) ├── package.json # Node dependencies and npm scripts └── .postcssrc # PostCSS configuration for Parcel ``` ### Setup 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/template-asset-bundling ``` 2. Install Node.js dependencies: ```sh npm install ``` 3. Install Go dependencies: ```sh go mod download ``` ### Running #### Development Run the asset watcher and the Go server in separate terminals: ```sh # Terminal 1 — watch and rebuild assets on change npm run dev # Terminal 2 — start the Fiber server (template hot-reload enabled) go run app.go ``` Open [http://localhost:3000](http://localhost:3000) in your browser. #### Production Build optimized assets first, then run the server with `APP_ENV=production` to disable template hot-reloading: ```sh npm run build APP_ENV=production go run app.go ``` ### How It Works - `npm run dev` runs Parcel in watch mode, compiling `assets/app.css` (Tailwind source) into hashed output files under `public/assets/`. - The `getCssAsset` template function walks `public/assets/` at render time to find the correct hashed filename and injects the `` tag automatically. - In development (`APP_ENV` is not `production`), `engine.Reload(true)` re-parses templates on every request so changes are reflected without restarting the server. --- ## Todo App + Auth + GORM ## Todo App with Auth using GORM [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/todo-app-with-auth-gorm) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/todo-app-with-auth-gorm) This project demonstrates a Todo application with authentication using GORM. ### Prerequisites Ensure you have the following installed and available in your `GOPATH`: - Golang - [Air](https://github.com/air-verse/air) for hot reloading - [Godotenv](https://github.com/joho/godotenv) for loading `.env` file ### Installation 1. Clone the repository: ```sh git clone https://github.com/gofiber/recipes.git cd recipes/todo-app-with-auth-gorm ``` 2. Install dependencies: ```sh go get ``` ### Running the Application 1. Start the application: ```sh air ``` ### Environment Variables Create a `.env` file in the root directory and add the following variables: ```shell # PORT returns the server listening port # default: 5000 PORT= # DB returns the name of the sqlite database # default: gotodo.db DB= # TOKENKEY returns the jwt token secret TOKENKEY= # TOKENEXP returns the jwt token expiration duration. # Should be time.ParseDuration string. Source: https://golang.org/pkg/time/#ParseDuration # default: 10h TOKENEXP= ``` --- ## Unit Testing ## Unit Testing Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/unit-test) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/unit-test) This example demonstrates how to write unit tests for a Go Fiber application using the `stretchr/testify` package. ### Description This project provides a basic setup for unit testing in a Go Fiber application. It includes examples of how to structure tests, write test cases, and use the `stretchr/testify` package for assertions. ### Requirements - [Go](https://golang.org/dl/) 1.18 or higher - [Git](https://git-scm.com/downloads) ### Project Structure - `main.go`: The main application entry point. - `main_test.go`: The test file containing unit tests. - `go.mod`: The Go module file. ### Setup 1. Clone the repository: ```bash git clone https://github.com/gofiber/recipes.git cd recipes/unit-test ``` 2. Install the dependencies: ```bash go mod download ``` ### Running the Tests To run the tests, use the following command: ```bash go test ./... ``` ### Example Usage The `main.go` file sets up a simple Fiber application with a single route. The `main_test.go` file contains unit tests for this application. #### `main.go` This file sets up a basic Fiber application with a single route that returns "OK". #### `main_test.go` This file contains unit tests for the Fiber application. It uses the `stretchr/testify` package for assertions. ```go package main import ( "io" "net/http" "testing" "github.com/gofiber/fiber/v3" "github.com/stretchr/testify/assert" ) func TestIndexRoute(t *testing.T) { tests := []struct { description string route string expectedError bool expectedCode int expectedBody string }{ { description: "index route", route: "/", expectedError: false, expectedCode: 200, expectedBody: "OK", }, { description: "non existing route", route: "/i-dont-exist", expectedError: false, expectedCode: 404, expectedBody: "Cannot GET /i-dont-exist", }, } app := Setup() for _, test := range tests { req, err := http.NewRequest("GET", test.route, nil) assert.Nilf(t, err, test.description) res, err := app.Test(req, fiber.TestConfig{Timeout: 0, FailOnTimeout: false}) assert.Equalf(t, test.expectedError, err != nil, test.description) if test.expectedError { continue } defer res.Body.Close() assert.Equalf(t, test.expectedCode, res.StatusCode, test.description) body, err := io.ReadAll(res.Body) assert.Nilf(t, err, test.description) assert.Equalf(t, test.expectedBody, string(body), test.description) } } ``` ### Unit Testing in General Unit testing is a software testing method where individual units or components of a software are tested in isolation from the rest of the application. The purpose of unit testing is to validate that each unit of the software performs as expected. Unit tests are typically automated and written by developers as part of the development process. #### Benefits of Unit Testing - **Early Bug Detection**: Unit tests help in identifying bugs early in the development cycle. - **Documentation**: Unit tests can serve as documentation for the code. - **Refactoring Support**: Unit tests provide a safety net when refactoring code. - **Design**: Writing unit tests can lead to better software design. ### Unit Testing in Fiber Fiber is an Express-inspired web framework written in Go. Unit testing in Fiber involves testing the individual routes and handlers to ensure they behave as expected. The `stretchr/testify` package is commonly used for writing assertions in Go tests. #### Writing Unit Tests in Fiber 1. **Setup the Application**: Create a function to setup the Fiber application. This function can be reused in the tests. 2. **Define Test Cases**: Create a structure to define the input and expected output for each test case. 3. **Perform Requests**: Use the `app.Test` method to perform HTTP requests and capture the response. 4. **Assertions**: Use the `stretchr/testify` package to write assertions and verify the response. #### The `app.Test` Method The `app.Test` method in Fiber is used to simulate HTTP requests to the Fiber application and test the responses. This is particularly useful for unit tests as it allows testing the routes and handlers of the application without starting a real server. ##### Usage of the `app.Test` Method The `app.Test` method takes two parameters: 1. **req**: An `*http.Request` object representing the HTTP request to be tested. 2. **config**: A `fiber.TestConfig` struct for configuring the test (e.g., `Timeout` and `FailOnTimeout`). The method returns an `*http.Response` and an `error`. The `*http.Response` contains the application's response to the simulated request, and the `error` indicates if any error occurred during the request processing. ##### Example Here is an example of how the `app.Test` method is used in a unit test: ```go package main import ( "io" "net/http" "testing" "github.com/gofiber/fiber/v3" "github.com/stretchr/testify/assert" ) func TestIndexRoute(t *testing.T) { // Setup the app as it is done in the main function app := Setup() // Create a new HTTP request req, err := http.NewRequest("GET", "/", nil) assert.Nil(t, err) // Perform the request using app.Test res, err := app.Test(req, fiber.TestConfig{Timeout: 0, FailOnTimeout: false}) defer res.Body.Close() // Verify that no error occurred assert.Nil(t, err) // Verify the status code assert.Equal(t, 200, res.StatusCode) // Read the response body body, err := io.ReadAll(res.Body) assert.Nil(t, err) // Verify the response body assert.Equal(t, "OK", string(body)) } ``` In this example, a GET request is sent to the root route (`"/"`) of the application. The response is verified to ensure that the status code is `200` and the response text is `"OK"`. ### Conclusion This example provides a basic setup for unit testing in a Go Fiber application. It can be extended and customized further to fit the needs of more complex applications. ### References - [Fiber Documentation](https://docs.gofiber.io) - [Testify Documentation](https://github.com/stretchr/testify) - [Go Testing](https://golang.org/pkg/testing/) --- ## File Upload ## File Upload Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/upload-file) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/upload-file) This example demonstrates how to handle file uploads using Go Fiber. ### Description This project provides a basic setup for handling file uploads in a Go Fiber application. It includes examples for uploading single and multiple files, as well as saving files to different directories. ### Requirements - [Go](https://golang.org/dl/) 1.18 or higher - [Git](https://git-scm.com/downloads) ### Project Structure - `single/main.go`: Example for uploading a single file to the root directory. - `single_relative_path/main.go`: Example for uploading a single file to a relative path. - `multiple/main.go`: Example for uploading multiple files. - `go.mod`: The Go module file. ### Setup 1. Clone the repository: ```bash git clone https://github.com/gofiber/recipes.git cd recipes/upload-file ``` 2. Install the dependencies: ```bash go mod download ``` ### Running the Examples #### Single File Upload 1. Navigate to the `single` directory: ```bash cd single ``` 2. Run the application: ```bash go run main.go ``` 3. Use a tool like `curl` or Postman to upload a file: ```bash curl -F "document=@/path/to/your/file" http://localhost:3000/ ``` #### Single File Upload with Relative Path 1. Navigate to the `single_relative_path` directory: ```bash cd single_relative_path ``` 2. Run the application: ```bash go run main.go ``` 3. Use a tool like `curl` or Postman to upload a file: ```bash curl -F "document=@/path/to/your/file" http://localhost:3000/ ``` #### Multiple File Upload 1. Navigate to the `multiple` directory: ```bash cd multiple ``` 2. Run the application: ```bash go run main.go ``` 3. Use a tool like `curl` or Postman to upload multiple files: ```bash curl -F "documents=@/path/to/your/file1" -F "documents=@/path/to/your/file2" http://localhost:3000/ ``` ### Security - **Filename sanitization**: All handlers use `filepath.Base(file.Filename)` to strip any directory components from uploaded filenames, preventing path traversal attacks (e.g., `../../etc/passwd`). - **Body size limit**: The app is configured with a 10 MB body limit (`BodyLimit: 10 * 1024 * 1024`) to prevent denial-of-service via large uploads. ### Code Overview #### `single/main.go` Handles uploading a single file to the root directory. #### `single_relative_path/main.go` Handles uploading a single file to a relative path (`./uploads/`) or a temp uploads directory (`./uploads_relative/`). #### `multiple/main.go` Handles uploading multiple files. ### Conclusion This example provides a basic setup for handling file uploads in a Go Fiber application. It can be extended and customized further to fit the needs of more complex applications. ### References - [Fiber Documentation](https://docs.gofiber.io) --- ## URL Shortener ## URL Shortener API [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/url-shortener-api) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/url-shortener-api) This project provides a URL shortening service built with [Fiber](https://gofiber.io) and Redis. ### Tech Stack - Go + [Fiber v3](https://gofiber.io) - Redis (via [go-redis/v8](https://github.com/go-redis/redis)) ### Environment Variables Copy `api/.env.example` to `api/.env` and adjust the values: | Variable | Default | Description | |-------------|------------------|------------------------------------------------| | `DB_ADDR` | `localhost:6379` | Redis address (`host:port`) | | `DB_PASS` | _(empty)_ | Redis password (leave empty if none) | | `APP_PORT` | `:3000` | Port the API listens on (include leading colon)| | `DOMAIN` | `localhost:3000` | Public domain used to build the short URL | | `API_QUOTA` | `10` | Max API calls per IP per 30-minute window | ### Quick Start #### Redis only (local dev) Start Redis with Docker Compose: ```sh docker compose up db -d ``` Copy and edit the env file, then run the API: ```sh cp api/.env.example api/.env cd api && go run . ``` #### Full stack (API + Redis) ```sh docker compose up -d ``` ### API Documentation **Endpoint:** `POST http://localhost:3000/api/v1` #### Request body | Field | Type | Required | Description | |---------|--------|----------|------------------------------------------| | `url` | string | yes | The original URL to shorten | | `short` | string | no | Custom alias (auto-generated if omitted) | | `expiry`| int | no | Expiry in hours (default: 24) | #### Response body | Field | Type | Description | |--------------------|--------|------------------------------------------| | `url` | string | Original URL | | `short` | string | Full short URL including domain | | `expiry` | int | Expiry in hours | | `rate_limit` | int | Remaining API calls in current window | | `rate_limit_reset` | int | Minutes until rate limit window resets | > Rate limit: 10 calls per IP every 30 minutes (configurable via `API_QUOTA` in `.env`). #### curl Examples **Shorten a URL (auto-generated alias):** ```sh curl -X POST http://localhost:3000/api/v1 \ -H "Content-Type: application/json" \ -d '{"url": "https://gofiber.io", "expiry": 24}' ``` **Shorten a URL with a custom alias:** ```sh curl -X POST http://localhost:3000/api/v1 \ -H "Content-Type: application/json" \ -d '{"url": "https://gofiber.io", "short": "fiber", "expiry": 48}' ``` **Resolve a short URL (browser or curl):** ```sh curl -L http://localhost:3000/fiber ``` ### Setup --- ## Validation ## Validation with [Fiber](https://gofiber.io) [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/validation) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/validation) This example demonstrates how to use [go-playground/validator](https://github.com/go-playground/validator) for input validation in a Go Fiber application. ### Description This project provides a basic setup for validating request data in a Go Fiber application using the `go-playground/validator` package. It includes the necessary configuration and code to perform validation on incoming requests. ### Requirements - [Go](https://golang.org/dl/) 1.18 or higher - [Git](https://git-scm.com/downloads) ### Project Structure - `main.go`: The main application entry point. - `config/env.go`: Configuration file for environment variables. - `go.mod`: The Go module file. - `.env`: Environment variables file. ### Setup 1. Clone the repository: ```bash git clone https://github.com/gofiber/recipes.git cd recipes/validation ``` 2. Install the dependencies: ```bash go mod download ``` 3. Create a `.env` file in the root directory with the following content: ```dotenv PORT=":8080" ``` 4. Run the application: ```bash go run main.go ``` The application should now be running on `http://localhost:8080`. ### Example Usage 1. Send a POST request to `http://localhost:8080/validate` with a JSON payload: ```json { "name": "John Doe", "email": "john.doe@example.com", "age": 30 } ``` 2. The server will validate the request data and respond with a success message if the data is valid, or an error message if the data is invalid. ### Code Overview #### `main.go` The main Go file sets up the Fiber application, handles HTTP requests, and performs validation using the `go-playground/validator` package. #### `config/env.go` The configuration file for loading environment variables. ```go package config import "os" // Config func to get env value func Config(key string) string { return os.Getenv(key) } ``` ### Conclusion This example provides a basic setup for validating request data in a Go Fiber application using the `go-playground/validator` package. It can be extended and customized further to fit the needs of more complex applications. ### References - [Fiber Documentation](https://docs.gofiber.io) - [Validator Documentation](https://github.com/go-playground/validator) --- ## Vercel ## Vercel Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/vercel) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/vercel) This example demonstrates how to deploy a Go Fiber application to Vercel. ### Description This project provides a starting point for deploying a Go Fiber application to Vercel. It includes the necessary configuration files and code to run a serverless application on Vercel. ### Requirements - [Go](https://golang.org/dl/) 1.18 or higher - [Git](https://git-scm.com/downloads) - [Vercel CLI](https://vercel.com/download) - A folder named `public` with one or more static files to be served > [!CAUTION] > If you don't have a `public` folder, Vercel will serve all files from your project root, which can expose sensitive files like your source code. ### Project Structure - `api/index.go`: The main entry point for the serverless function. - `vercel.json`: Configuration file for Vercel. - `go.mod`: The Go module file. ### Setup 1. Clone the repository: ```bash git clone https://github.com/gofiber/recipes.git cd recipes/vercel ``` 2. Install the dependencies: ```bash go mod download ``` ### Configuration Ensure the `vercel.json` file is present in the root directory to handle routing properly. This file rewrites all requests to the `api/index.go` handler. ```json { "rewrites": [ { "source": "(.*)", "destination": "api/index.go" } ] } ``` ### Deploy 1. Install the Vercel CLI: ```bash npm install -g vercel ``` 2. Log in to Vercel: ```bash vercel login ``` 3. Deploy the application: ```bash vercel ``` Follow the prompts to complete the deployment. Your application will be deployed to Vercel and a URL will be provided. ### Example Usage 1. Open your browser and navigate to the provided Vercel URL. 2. You should see the JSON response with the URI and path. ### Code Overview #### `api/index.go` The main Go file sets up the Fiber application, handles HTTP requests, and manages the routing. ```go package handler import ( "github.com/gofiber/fiber/v3/middleware/adaptor" "github.com/gofiber/fiber/v3" "net/http" ) // Handler is the main entry point of the application. Think of it like the main() method func Handler(w http.ResponseWriter, r *http.Request) { // This is needed to set the proper request path in `fiber.Ctx` r.RequestURI = r.URL.String() handler().ServeHTTP(w, r) } // building the fiber application func handler() http.HandlerFunc { app := fiber.New() app.Get("/v1", func(ctx fiber.Ctx) error { return ctx.JSON(fiber.Map{ "version": "v1", }) }) app.Get("/v2", func(ctx fiber.Ctx) error { return ctx.JSON(fiber.Map{ "version": "v2", }) }) app.Get("/", func(ctx fiber.Ctx) error { return ctx.JSON(fiber.Map{ "uri": ctx.Request().URI().String(), "path": ctx.Path(), }) }) return adaptor.FiberApp(app) } ``` ### Conclusion This example provides a basic setup for deploying a Go Fiber application to Vercel. It can be extended and customized further to fit the needs of more complex applications. ### References - [Vercel Documentation](https://vercel.com/docs) - [Fiber Documentation](https://docs.gofiber.io) --- ## WebSocket (Websocket) ## WebSocket Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/websocket) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/websocket) This example demonstrates a simple WebSocket application using Go Fiber. ### Description This project provides a basic setup for a WebSocket server using Go Fiber. It includes the necessary configuration and code to run a real-time WebSocket server. ### Requirements - [Go](https://golang.org/dl/) 1.18 or higher - [Git](https://git-scm.com/downloads) ### Project Structure - `main.go`: The main application entry point. - `go.mod`: The Go module file. ### Setup 1. Clone the repository: ```bash git clone https://github.com/gofiber/recipes.git cd recipes/websocket ``` 2. Install the dependencies: ```bash go mod download ``` 3. Run the application: ```bash go run main.go ``` The application should now be running on `http://localhost:3000`. ### WebSocket Endpoint - **GET /ws**: WebSocket endpoint for the application. ### Example Usage 1. Connect to the WebSocket server at `ws://localhost:3000/ws`. 2. Send a message to the server. 3. The server will echo the message back to the client. ### Code Overview #### `main.go` The main Go file sets up the Fiber application, handles WebSocket connections, and manages the WebSocket communication. ```go package main import ( "fmt" "log" "github.com/gofiber/fiber/v3" "github.com/gofiber/contrib/v3/websocket" ) func main() { app := fiber.New() // Optional middleware app.Use("/ws", func(c fiber.Ctx) error { if c.Get("host") == "localhost:3000" { c.Locals("Host", "Localhost:3000") return c.Next() } return c.Status(fiber.StatusForbidden).SendString("Request origin not allowed") }) // Upgraded websocket request app.Get("/ws", websocket.New(func(c *websocket.Conn) { fmt.Println(c.Locals("Host")) // "Localhost:3000" for { mt, msg, err := c.ReadMessage() if err != nil { log.Println("read:", err) break } log.Printf("recv: %s", msg) err = c.WriteMessage(mt, msg) if err != nil { log.Println("write:", err) break } } })) // ws://localhost:3000/ws log.Fatal(app.Listen(":3000")) } ``` ### Conclusion This example provides a basic setup for a WebSocket server using Go Fiber. It can be extended and customized further to fit the needs of more complex applications. ### References - [Fiber Documentation](https://docs.gofiber.io) - [WebSocket Documentation](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) --- ## WebSocket Chat ## WebSocket Chat Example [![Github](https://img.shields.io/static/v1?label=&message=Github&color=2ea44f&style=for-the-badge&logo=github)](https://github.com/gofiber/recipes/tree/master/websocket-chat) [![StackBlitz](https://img.shields.io/static/v1?label=&message=StackBlitz&color=2ea44f&style=for-the-badge&logo=StackBlitz)](https://stackblitz.com/github/gofiber/recipes/tree/master/websocket-chat) This example demonstrates a simple chat application using Go Fiber and WebSockets. ### Description This project provides a basic setup for a WebSocket-based chat application using Go Fiber. It includes the necessary configuration and code to run a real-time chat server. ### Requirements - [Go](https://golang.org/dl/) 1.18 or higher - [Git](https://git-scm.com/downloads) ### Project Structure - `main.go`: The main application entry point. - `home.html`: The HTML file for the chat client. - `go.mod`: The Go module file. ### Setup 1. Clone the repository: ```bash git clone https://github.com/gofiber/recipes.git cd recipes/websocket-chat ``` 2. Install the dependencies: ```bash go mod download ``` 3. Run the application: ```bash go run main.go ``` The application should now be running on `http://localhost:8080`. ### WebSocket Endpoints - **GET /ws**: WebSocket endpoint for the chat application. ### Example Usage 1. Open your browser and navigate to `http://localhost:8080`. 2. Enter a message in the input field and click "Send". 3. The message should appear in the chat log. ### Code Overview #### `main.go` The main Go file sets up the Fiber application, handles WebSocket connections, and manages the chat hub. - Defines a `client` struct with a mutex and a closing flag to guard concurrent writes per connection. - Declares global channels (`register`, `broadcast`, `unregister`) and a shared `clients` map. - `runHub()` runs in a goroutine and serialises map mutations: it adds connections on `register`, fans out messages to all clients in parallel goroutines on `broadcast`, and removes connections on `unregister`. - `main()` parses the `--addr` flag, serves `home.html` as a static file, upgrades `/ws` requests to WebSocket, and starts the hub before listening. #### `home.html` The HTML file provides a simple user interface for the chat application, including a message log and input field. ### Conclusion This example provides a basic setup for a WebSocket-based chat application using Go Fiber. It can be extended and customized further to fit the needs of more complex applications. ### References - [Fiber Documentation](https://docs.gofiber.io) - [WebSocket Documentation](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) --- ## 👋 Welcome (Storage) ![Fiber](https://raw.githubusercontent.com/gofiber/storage/master/.github/logo-dark.svg#gh-dark-mode-only) ![Fiber](https://raw.githubusercontent.com/gofiber/storage/master/.github/logo.svg#gh-light-mode-only) ![](https://img.shields.io/badge/%F0%9F%93%9A%20godoc-pkg-00ACD7.svg?color=00ACD7&style=flat) ![](https://img.shields.io/badge/%F0%9F%93%9D%20goreport-A%2B-75C46B) ![](https://img.shields.io/badge/%F0%9F%93%8A%20benchmarks-charts-00ACD7?style=flat) ![](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7) Premade storage drivers that implement the [`Storage`](https://github.com/gofiber/storage/blob/main/storage.go) interface, designed to be used with various [Fiber middlewares](https://github.com/gofiber/fiber/tree/master/middleware). **Note:** All storages are tested with the latest two [Go version](https://go.dev/doc/devel/release#policy). Older Go versions may also work, but are not guaranteed to be supported. ```go // Storage interface for communicating with different database/key-value // providers. Visit https://github.com/gofiber/storage for more info. type Storage interface { // GetWithContext gets the value for the given key with a context. // `nil, nil` is returned when the key does not exist GetWithContext(ctx context.Context, key string) ([]byte, error) // Get gets the value for the given key. // `nil, nil` is returned when the key does not exist Get(key string) ([]byte, error) // SetWithContext stores the given value for the given key // with an expiration value, 0 means no expiration. SetWithContext(ctx context.Context, key string, val []byte, exp time.Duration) error // Set stores the given value for the given key along // with an expiration value, 0 means no expiration. // Empty key or value will be ignored without an error. Set(key string, val []byte, exp time.Duration) error // DeleteWithContext deletes the value for the given key with a context. // It returns no error if the storage does not contain the key, DeleteWithContext(ctx context.Context, key string) error // Delete deletes the value for the given key. // It returns no error if the storage does not contain the key, Delete(key string) error // ResetWithContext resets the storage and deletes all keys with a context. ResetWithContext(ctx context.Context) error // Reset resets the storage and delete all keys. Reset() error // Close closes the storage and will stop any running garbage // collectors and open connections. Close() error } ``` ### 📑 Storage Implementations - [Aerospike](./aerospike/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-aerospike.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [ArangoDB](./arangodb/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-arangodb.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [AzureBlob](./azureblob/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-azureblob.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [Badger](./badger/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-badger.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [Bbolt](./bbolt) ![](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-bbolt.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [Cassandra](./cassandra/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-cassandra.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [ClickHouse](./clickhouse/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-clickhouse.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [CloudflareKV](./cloudflarekv/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-cloudflarekv.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [Coherence](./coherence/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-coherence.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [Couchbase](./couchbase/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-couchbase.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [DynamoDB](./dynamodb/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-dynamodb.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [Etcd](./etcd/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-etcd.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [Firestore](./firestore/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-firestore.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [LevelDB](./leveldb/README.md) ![LevelDB Tests Status](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-leveldb.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [Memcache](./memcache/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-memcache.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [Memory](./memory/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-memory.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [Minio](./minio/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-minio.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [MockStorage](./mockstorage/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-mockstorage.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [MongoDB](./mongodb/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-mongodb.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [MSSQL](./mssql/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-mssql.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [MySQL](./mysql/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-mysql.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [NATS](./nats/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-nats.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [Neo4j](./neo4j/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-neo4j.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [Pebble](./pebble/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-pebble.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [Postgres](./postgres/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-postgres.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [Redis](./redis/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-redis.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [Ristetto](./ristretto/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-ristretto.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [Rueidis](./rueidis/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-rueidis.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [S3](./s3/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-s3.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [ScyllaDB](./scylladb/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-scylladb.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [SQLite3](./sqlite3/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-sqlite3.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [SurrealDB](./surrealdb/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-surrealdb.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [Valkey](./valkey/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-valkey.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) See the benchmarks under https://gofiber.github.io/storage/benchmarks --- ## Aerospike ![Release](https://img.shields.io/github/v/tag/gofiber/storage?filter=aerospike*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-aerospike.yml?label=Tests) An Aerospike client driver using `aerospike/aerospike-client-go` and [aerospike/aerospike-client-go](https://github.com/aerospike/aerospike-client-go). #### Table of Contents - [Signatures](#signatures) - [Installation](#installation) - [Examples](#examples) - [Config](#config) - [Default Config](#default-config) #### Signatures ```go func New(config ...Config) Storage func NewFromConnection(client *aerospike.Client, config ...Config) *Storage func (s *Storage) Get(key string) ([]byte, error) func (s *Storage) GetWithContext(ctx context.Context, key string) ([]byte, error) func (s *Storage) Set(key string, val []byte, exp time.Duration) error func (s *Storage) SetWithContext(ctx context.Context, key string, val []byte, exp time.Duration) error func (s *Storage) Delete(key string) error func (s *Storage) DeleteWithContext(ctx context.Context, key string) error func (s *Storage) Reset() error func (s *Storage) ResetWithContext(ctx context.Context) error func (s *Storage) Close() error func (s *Storage) Conn() driver.Client func (s *Storage) GetSchemaInfo() *SchemaInfo ``` **Note:** Aerospike has no native context support, so the context methods run the operation to completion. They do honour a context that is already cancelled or past its deadline, returning the context error without touching the storage. **Note:** `Config.Expiration` is deprecated and no longer applied. The storage interface documents an expiration of zero as no expiration, and substituting a default here meant `Set(key, value, 0)` quietly stored an entry that expired. Pass the expiration you want to `Set`. **Note:** `Reset` deletes user data only. This driver keeps its schema bookkeeping in a separate Aerospike set, derived from `SetName` as `SetName + "_fiber_schema"`, so it is untouched by the scan `Reset` runs and no key name is reserved for it. A `SetName` long enough that the derived name would exceed Aerospike's 63-byte limit is truncated and given a digest of the full name, so any `SetName` the server accepts still yields a bookkeeping set it accepts. It reports any scan or delete that failed rather than logging and carrying on, so `New` with `Reset: true` fails loudly when the store could not actually be wiped instead of starting against stale keys. #### Installation Aerospike is tested on the 2 last [Go versions](https://golang.org/dl/) with support for modules. So make sure to initialize one first if you didn't do that yet: ```bash go mod init github.com// ``` And then install the aerospike implementation: ```bash go get github.com/gofiber/storage/aerospike ``` #### Examples Import the storage package. ```go import "github.com/gofiber/storage/aerospike" ``` You can use the following possibilities to create a storage: ```go // Initialize default config store := aerospike.New() // Initialize custom config store := aerospike.New(aerospike.Config{ Hosts: []*aerospike.Host{aerospike.NewHost("localhost", 3000)}, Namespace: "test", // Default namespace SetName: "fiber", Reset: false, Expiration: 1 * time.Hour, SchemaVersion: 1, SchemaDescription: "Default Fiber storage schema", ForceSchemaUpdate: false, }) ``` #### Config ```go type Config struct { // Hosts is a list of Aerospike server hosts Hosts []*aerospike.Host // Namespace is the Aerospike namespace Namespace string // SetName is the Aerospike Set name SetName string // Reset clears any existing keys in existing Set Reset bool // Expiration was the default expiration time of entries. // // Deprecated: no longer applied. Zero means no expiration, and defaulting // here made Set(key, value, 0) quietly store an expiring entry. Pass the // expiration to Set instead. Expiration time.Duration // SchemaVersion indicates the schema version to use SchemaVersion int // SchemaDescription provides additional info about the schema SchemaDescription string // ForceSchemaUpdate forces schema update even if version matches ForceSchemaUpdate bool // Initial host connection timeout duration. The timeout when opening a connection // to the server host for the first time. InitialConnectionTimeout time.Duration } ``` #### Default Config Used only for optional fields ```go var ConfigDefault = Config{ Hosts: []*aerospike.Host{aerospike.NewHost("localhost", 3000)}, Namespace: "test", // Default namespace SetName: "fiber", Reset: false, Expiration: 1 * time.Hour, SchemaVersion: 1, SchemaDescription: "Default Fiber storage schema", ForceSchemaUpdate: false, } ``` #### Using an Existing Aerospike Connection If your application already holds an `*aerospike.Client`, you can build the storage on it instead of connecting a second time. Only the `Namespace`, `SetName`, `Reset` and schema options are read; the connection settings come from the client. The client stays yours to close: `Close` on a storage built this way leaves it connected, so the rest of your application keeps working. The storage itself is closed: any operation on it afterwards returns `ErrClosed`. ```go import ( as "github.com/aerospike/aerospike-client-go/v8" "github.com/gofiber/storage/aerospike" ) func main() { client, err := as.NewClient("127.0.0.1", 3000) if err != nil { panic(err) } defer client.Close() store := aerospike.NewFromConnection(client, aerospike.Config{ Namespace: "test", SetName: "fiber_storage", }) defer store.Close() } ``` --- ## ArangoDB ![Release](https://img.shields.io/github/v/tag/gofiber/storage?filter=arangodb*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-arangodb.yml?label=Tests) A ArangoDB storage driver using `arangodb/go-driver` and [arangodb/go-driver](https://github.com/arangodb/go-driver). #### Table of Contents - [Signatures](#signatures) - [Installation](#installation) - [Examples](#examples) - [Config](#config) - [Default Config](#default-config) #### Signatures ```go func New(config ...Config) Storage func NewWithContext(ctx context.Context, config ...Config) *Storage func NewFromConnection(client driver.Client, config ...Config) *Storage func NewFromConnectionWithContext(ctx context.Context, client driver.Client, config ...Config) *Storage func (s *Storage) GetWithContext(ctx context.Context, key string) ([]byte, error) func (s *Storage) Get(key string) ([]byte, error) func (s *Storage) SetWithContext(ctx context.Context, key string, val []byte, exp time.Duration) error func (s *Storage) Set(key string, val []byte, exp time.Duration) error func (s *Storage) DeleteWithContext(ctx context.Context, key string) error func (s *Storage) Delete(key string) error func (s *Storage) ResetWithContext(ctx context.Context) error func (s *Storage) Reset() error func (s *Storage) Close() error func (s *Storage) Conn() driver.Client ``` **Note:** Expirations are stored with a one-second granularity and rounded up, so an entry is never dropped before its expiration but may outlive it by up to a second. #### Installation ArangoDB is tested on the 2 last (1.14/1.15) [Go versions](https://golang.org/dl/) with support for modules. So make sure to initialize one first if you didn't do that yet: ```bash go mod init github.com// ``` And then install the arangodb implementation: ```bash go get github.com/gofiber/storage/arangodb/v2 ``` #### Examples Import the storage package. ```go import "github.com/gofiber/storage/arangodb/v2" ``` You can use the following possibilities to create a storage: ```go // Initialize default config store := arangodb.New() // Initialize custom config store := arangodb.New(arangodb.Config{ Host: "http://127.0.0.1", Port: 8529, Database: "fiber", Collection: "fiber_storage", Reset: false, GCInterval: 10 * time.Second, }) ``` #### Config ```go type Config struct { // Host name where the DB is hosted // // Optional. Default is "http://127.0.0.1" Host string // Port where the DB is listening on // // Optional. Default is 8529 Port int // Server username // // Optional. Default is "" Username string // Server password // // Optional. Default is "" Password string // Database name // // Optional. Default is "fiber" Database string // Collection name // // Optional. Default is "fiber_storage" Collection string // Reset clears any existing keys in existing collection // // Optional. Default is false Reset bool // Time before deleting expired keys // // Optional. Default is 10 * time.Second GCInterval time.Duration } ``` #### Default Config Used only for optional fields ```go var ConfigDefault = Config{ Host: "http://127.0.0.1", Port: 8529, Database: "fiber", Collection: "fiber_storage", Reset: false, GCInterval: 10 * time.Second, } ``` #### Using an Existing ArangoDB Connection If your application already holds a `driver.Client`, you can build the storage on it instead of creating a second one. Only the `Database`, `Collection`, `Reset` and `GCInterval` options are read; the endpoint and credentials come from the client, and connection fields left over in the config (`Host`, `Port`, `Username`, `Password`) are ignored. `Close` on a storage built this way stops the garbage collector; ArangoDB's driver has no connection to close, so the client keeps working. ```go import ( "github.com/arangodb/go-driver" "github.com/arangodb/go-driver/http" "github.com/gofiber/storage/arangodb/v2" ) func main() { conn, err := http.NewConnection(http.ConnectionConfig{ Endpoints: []string{"http://127.0.0.1:8529"}, }) if err != nil { panic(err) } client, err := driver.NewClient(driver.ClientConfig{ Connection: conn, Authentication: driver.BasicAuthentication("root", "password"), }) if err != nil { panic(err) } store := arangodb.NewFromConnection(client, arangodb.Config{ Database: "fiber", Collection: "fiber_storage", }) defer store.Close() } ``` --- ## Azure Blob ![Release](https://img.shields.io/github/v/tag/gofiber/storage?filter=azureblob*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-azureblob.yml?label=Tests) [Azure Blob storage](https://azure.microsoft.com/en-us/products/storage/blobs/#overview) is Microsoft's object storage solution for the cloud. #### Table of Contents - [Signatures](#signatures) - [Installation](#installation) - [Examples](#examples) - [Config](#config) - [Default Config](#default-config) #### Signatures ```go func New(config ...Config) Storage func NewWithContext(ctx context.Context, config ...Config) *Storage func NewFromConnection(client *azblob.Client, config ...Config) *Storage func NewFromConnectionWithContext(ctx context.Context, client *azblob.Client, config ...Config) *Storage func (s *Storage) Get(key string) ([]byte, error) func (s *Storage) GetWithContext(ctx context.Context, key string) ([]byte, error) func (s *Storage) Set(key string, val []byte, exp time.Duration) error func (s *Storage) SetWithContext(ctx context.Context, key string, val []byte, exp time.Duration) error func (s *Storage) Delete(key string) error func (s *Storage) DeleteWithContext(ctx context.Context, key string) error func (s *Storage) Reset() error func (s *Storage) ResetWithContext(ctx context.Context) error func (s *Storage) Close() error func (s *Storage) Conn() *azblob.Client ``` #### Installation Azure blob storage driver is tested on the 2 last [Go versions](https://golang.org/dl/) with support for modules. So make sure to initialize one first if you didn't do that yet: ```bash go mod init github.com// ``` And then install the azure blob implementation: ```bash go get github.com/gofiber/storage/azureblob/v2 ``` #### Examples Import the storage package. ```go import "github.com/gofiber/storage/azureblob/v2" ``` You can use the following possibilities to create a storage: ```go // Initialize default config store := azureblob.New() // Initialize custom config store := azureblob.New(azureblob.Config{ Account: "test", Container: "test", Credentials: Credentials{ Account: "test", Key: "YXp1cml0ZWtleQo=", }, }) ``` #### Config ```go type Config struct { // Storage account name. Account string // Container name. Container string // Storage endpoint. // Optional. Default: "https://STORAGEACCOUNTNAME.blob.core.windows.net" Endpoint string // Request timeout. // Optional. Default is 0 (no timeout) RequestTimeout time.Duration // Reset clears any existing keys in existing container. // Optional. Default is false Reset bool // Credentials overrides AWS access key and AWS secret access key. Not recommended. // Optional. Default is Credentials{} Credentials Credentials // The maximum number of times requests that encounter retryable failures should be attempted. // Optional. Default is 3 MaxAttempts int } ``` #### Default Config ```go var ConfigDefault = Config{ Account: "", Container: "", Endpoint: "", RequestTimeout: 0, Reset: false, MaxAttempts: 3, } ``` #### Using an Existing Azure Blob Client If your application already holds an `*azblob.Client`, you can build the storage on it instead of creating a second one. Only the `Container`, `RequestTimeout` and `Reset` options are read; the endpoint and credentials come from the client. The client stays yours to manage: `Close` on a storage built this way is a no-op, so the rest of your application keeps working. ```go import ( "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" "github.com/gofiber/storage/azureblob/v2" ) func main() { cred, err := azblob.NewSharedKeyCredential("account", "key") if err != nil { panic(err) } client, err := azblob.NewClientWithSharedKeyCredential("https://account.blob.core.windows.net/", cred, nil) if err != nil { panic(err) } store := azureblob.NewFromConnection(client, azureblob.Config{ Container: "fiber", }) defer store.Close() } ``` --- ## Badger ![Release](https://img.shields.io/github/v/tag/gofiber/storage?filter=badger*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-badger.yml?label=Tests) A fast key-value DB using [dgraph-io/badger](https://github.com/dgraph-io/badger) #### Table of Contents - [Signatures](#signatures) - [Installation](#installation) - [Examples](#examples) - [Config](#config) - [Default Config](#default-config) #### Signatures ```go func New(config ...Config) Storage func NewFromConnection(db *badger.DB, config ...Config) *Storage func (s *Storage) Get(key string) ([]byte, error) func (s *Storage) GetWithContext(ctx context.Context, key string) ([]byte, error) func (s *Storage) Set(key string, val []byte, exp time.Duration) error func (s *Storage) SetWithContext(ctx context.Context, key string, val []byte, exp time.Duration) error func (s *Storage) Delete(key string) error func (s *Storage) DeleteWithContext(ctx context.Context, key string) error func (s *Storage) Reset() error func (s *Storage) ResetWithContext(ctx context.Context) error func (s *Storage) Close() error func (s *Storage) Conn() *badger.DB ``` **Note:** Badger has no native context support, so the context methods run the operation to completion. They do honour a context that is already cancelled or past its deadline, returning the context error without touching the storage. #### Installation Badger is tested on the 2 last [Go versions](https://golang.org/dl/) with support for modules. So make sure to initialize one first if you didn't do that yet: ```bash go mod init github.com// ``` And then install the badger implementation: ```bash go get github.com/gofiber/storage/badger/v2 ``` #### Examples Import the storage package. ```go import "github.com/gofiber/storage/badger/v2" ``` You can use the following possibilities to create a storage: ```go // Initialize default config store := badger.New() // Initialize custom config store := badger.New(badger.Config{ Database: "./fiber.badger", Reset: false, GCInterval: 10 * time.Second, }) ``` #### Config ```go type Config struct { // Database name // // Optional. Default is "./fiber.badger" Database string // Reset clears any existing keys in existing Table // // Optional. Default is false Reset bool // Time before deleting expired keys // // Optional. Default is 10 * time.Second GCInterval time.Duration // BadgerOptions is a way to set options in badger // // Optional. Default is badger.DefaultOptions("./fiber.badger") BadgerOptions badger.Options // Logger is the default logger used by badger // // Optional. Default is nil Logger badger.Logger // UseLogger define if any logger will be used // // Optional. Default is false UseLogger bool } ``` #### Default Config ```go var ConfigDefault = Config{ Database: "./fiber.badger", Reset: false, GCInterval: 10 * time.Second, BadgerOptions: badger.DefaultOptions("./fiber.badger").WithLogger(nil), Logger: nil, UseLogger: false, } ``` #### Using an Existing Badger Database Badger allows a single writer per directory, so an application that already keeps a `*badger.DB` open cannot have the storage open the same directory again. Pass the open database instead. Only the `Reset` and `GCInterval` options are read. The database stays yours to close: `Close` on a storage built this way stops the value-log collector but leaves the database open, so the rest of your application keeps working. The storage itself is closed, and any operation on it afterwards returns `ErrClosed`. > **Warning:** keys are not namespaced. `Reset` — the config option and the method — runs `DropAll` on the shared database, deleting every key your application stored in it, not just this storage's entries. ```go import ( "github.com/dgraph-io/badger/v3" badgerstorage "github.com/gofiber/storage/badger/v2" ) func main() { db, err := badger.Open(badger.DefaultOptions("./fiber.badger")) if err != nil { panic(err) } defer db.Close() store := badgerstorage.NewFromConnection(db) defer store.Close() } ``` --- ## Bbolt ![Release](https://img.shields.io/github/v/tag/gofiber/storage?filter=bbolt*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-bbolt.yml?label=Tests) A Bbolt storage driver using [etcd-io/bbolt](https://github.com/etcd-io/bbolt). Bolt is a pure Go key/value store inspired by [Howard Chu's](https://twitter.com/hyc_symas) [LMDB project](https://www.symas.com/symas-embedded-database-lmdb). The goal of the project is to provide a simple, fast, and reliable database for projects that don't require a full database server such as Postgres or MySQL. #### Table of Contents - [Signatures](#signatures) - [Installation](#installation) - [Examples](#examples) - [Config](#config) - [Default Config](#default-config) #### Signatures ```go func New(config ...Config) Storage func NewFromConnection(conn *bbolt.DB, config ...Config) *Storage func (s *Storage) Get(key string) ([]byte, error) func (s *Storage) GetWithContext(ctx context.Context, key string) ([]byte, error) func (s *Storage) Set(key string, val []byte, exp time.Duration) error func (s *Storage) SetWithContext(ctx context.Context, key string, val []byte, exp time.Duration) error func (s *Storage) Delete(key string) error func (s *Storage) DeleteWithContext(ctx context.Context, key string) error func (s *Storage) Reset() error func (s *Storage) ResetWithContext(ctx context.Context) error func (s *Storage) Close() error func (s *Storage) Conn() *bbolt.DB ``` **Note:** With `ReadOnly` set, `Set`, `Delete` and `Reset` return `ErrReadOnly`, which callers can match with `errors.Is`. **Note:** `ReadOnly` opens the database without creating the bucket, so the bucket must already exist; `New` panics if it does not. `Reset` writes, so combining it with `ReadOnly` is rejected rather than silently ignored. **Note:** If the configured bucket is dropped outside this driver, every method returns `ErrBucketNotFound`, which callers can match with `errors.Is`. **Note:** Bbolt has no notion of key expiration, so the `exp` argument of `Set` is ignored and stored entries live until they are deleted or the storage is reset. **Note:** Bbolt has no native context support, so the context methods run the operation to completion. They do honour a context that is already cancelled or past its deadline, returning the context error without touching the storage. #### Installation Bbolt is tested on the 2 last [Go versions](https://golang.org/dl/) with support for modules. So make sure to initialize one first if you didn't do that yet: ```bash go mod init github.com// ``` And then install the s3 implementation: ```bash go get github.com/gofiber/storage/bbolt/v2 ``` #### Examples Import the storage package. ```go import "github.com/gofiber/storage/bbolt/v2" ``` You can use the following possibilities to create a storage: ```go // Initialize default config store := bbolt.New() // Initialize custom config store := bbolt.New(bbolt.Config{ Database: "my_database.db", Bucket: "my-bucket", Reset: false, }) ``` #### Config ```go // Config defines the config for storage. type Config struct { // Database path // // Optional. Default is "fiber.db" Database string // Bbolt bucket name // // Optional. Default is "fiber_storage" Bucket string // Timeout is the amount of time to wait to obtain a file lock. // Only available on Darwin and Linux. // // Optional. Default is 60 * time.Second. Timeout time.Duration // Open database in read-only mode. // // Optional. Default is false ReadOnly bool // Reset clears any existing keys in existing Bucket // // Optional. Default is false Reset bool } ``` #### Default Config ```go // ConfigDefault is the default config var ConfigDefault = Config{ Database: "fiber.db", Bucket: "fiber_storage", Timeout: 60 * time.Second, ReadOnly: false, Reset: false, } ``` #### Using an Existing Bbolt Database bbolt takes an exclusive lock on its file, so an application that already keeps a `*bbolt.DB` open cannot have the storage open the same file again. Pass the open database instead. Only the `Bucket`, `Reset` and `ReadOnly` options are read: a database opened with `bbolt.Options{ReadOnly: true}` makes the storage read-only on its own, so it needs no extra config, and setting `ReadOnly` yourself makes the storage read-only over a writable handle too. The database stays yours to close: `Close` on a storage built this way leaves it open, so the rest of your application keeps working. The storage itself is closed: any operation on it afterwards returns `ErrClosed`. ```go import ( bboltstorage "github.com/gofiber/storage/bbolt/v2" "go.etcd.io/bbolt" ) func main() { conn, err := bbolt.Open("fiber.db", 0o666, nil) if err != nil { panic(err) } defer conn.Close() store := bboltstorage.NewFromConnection(conn, bboltstorage.Config{ Bucket: "fiber_storage", }) defer store.Close() } ``` --- ## Cassandra A Cassandra storage driver using [gocql/gocql](https://github.com/gocql/gocql) ![Release](https://img.shields.io/github/v/tag/gofiber/storage?filter=cassandra*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-cassandra.yml?label=Tests) ### Table of Contents - [Signatures](#signatures) - [Installation](#installation) - [Examples](#examples) - [Config](#config) - [Default Config](#default-config) #### Signatures ```go func New(config ...Config) (*Storage, error) func NewFromConnection(session *gocql.Session, config Config) (*Storage, error) func (s *Storage) Get(key string) ([]byte, error) func (s *Storage) GetWithContext(ctx context.Context, key string) ([]byte, error) func (s *Storage) Set(key string, val []byte, exp time.Duration) error func (s *Storage) SetWithContext(ctx context.Context, key string, val []byte, exp time.Duration) error func (s *Storage) Delete(key string) error func (s *Storage) DeleteWithContext(ctx context.Context, key string) error func (s *Storage) Reset() error func (s *Storage) ResetWithContext(ctx context.Context) error func (s *Storage) Close() error func (s *Storage) Conn() *gocql.Session ``` **Note:** `Config.Expiration` is deprecated and no longer applied. The storage interface documents an expiration of zero as no expiration, and substituting a default here meant `Set(key, value, 0)` quietly stored an entry that expired. Pass the expiration you want to `Set`. #### Installation Cassandra is supported on the latest two versions of Go: Install the cassandra implementation: ```bash go get github.com/gofiber/storage/cassandra ``` #### Running the tests This module uses [Testcontainers for Go](https://github.com/testcontainers/testcontainers-go/) to run integration tests, which will start a local instance of Cassandra as a Docker container under the hood. To run the tests, you must have Docker (or another container runtime 100% compatible with the Docker APIs) installed on your machine. #### Local development Before running this implementation, you must ensure a Cassandra cluster is available. For local development, we recommend using the Cassandra Docker image; it contains everything necessary for the client to operate correctly. To start Cassandra using Docker, issue the following: ```bash docker run --name cassandra -p 9042:9042 -d cassandra:latest ``` After running this command, you're ready to start using the storage and connecting to the database. #### Examples You can use the following options to create a cassandra storage driver: ```go import "github.com/gofiber/storage/cassandra" // Initialize default config, to connect to localhost:9042 using the memory engine and with a clean table. store := New(Config{ Hosts: []string{"localhost:9042"}, Keyspace: "test_keyspace_creation", Table: "test_kv", Expiration : 10 * time.Minute, }) ``` #### Config ```go // Config defines the configuration options for the Cassandra storage type Config struct { // Optional. Default is localhost // Hosts is a list of Cassandra nodes to connect to. Hosts []string // Optional. Default is gofiber // Keyspace is the name of the Cassandra keyspace to use. Keyspace string // Optional. Default is kv_store // Table is the name of the Cassandra table to use. Table string // Optional. Default is Quorum // Consistency is the Cassandra consistency level. Consistency gocql.Consistency // Optional. PoolConfig.HostSelectionPolicy = gocql.TokenAwareHostPolicy(gocql.RoundRobinHostPolicy()) // PoolConfig is the Cassandra connection pool configuration. PoolConfig *gocql.PoolConfig // Optional. Default is false // SslOpts is the SSL options for the Cassandra cluster. SslOpts *gocql.SslOptions // Expiration was the time after which an entry was considered expired. // // Deprecated: no longer applied. Zero means no expiration, and defaulting // here made Set(key, value, 0) quietly store an expiring entry. Pass the // expiration to Set instead. Expiration time.Duration // Optional. Default is false // Reset is a flag to reset the database. Reset bool // Optional. Default is 3 // MaxRetries is the maximum number of retries for a query. MaxRetries int // Optional. Default is 5 seconds // ConnectTimeout is the timeout for connecting to the Cassandra cluster. ConnectTimeout time.Duration } ``` #### Default Config ```go var ConfigDefault = Config{ Hosts: []string{"localhost:9042"}, Keyspace: "gofiber", Table: "kv_store", Consistency: gocql.Quorum, Reset: false, Expiration: 10 * time.Minute, MaxRetries: 3, ConnectTimeout: 5 * time.Second, SslOpts: nil, PoolConfig: &gocql.PoolConfig{ HostSelectionPolicy: gocql.TokenAwareHostPolicy(gocql.RoundRobinHostPolicy()), }, } ``` #### Using an Existing Cassandra Session If your application already holds a `*gocql.Session`, you can build the storage on it instead of opening a second one. The keyspace has to exist already; only the table is created. Only the `Keyspace`, `Table` and `Reset` options are read, the rest come from the session you pass. In particular `Consistency`, `ConnectTimeout` and `MaxRetries` are fixed by the session and ignored here. The session stays yours to close: `Close` on a storage built this way leaves it open, so the rest of your application keeps working. The storage itself is closed: any operation on it afterwards returns `ErrClosed`. ```go import ( "github.com/gocql/gocql" "github.com/gofiber/storage/cassandra" ) func main() { cluster := gocql.NewCluster("localhost:9042") cluster.Keyspace = "gofiber" session, err := cluster.CreateSession() if err != nil { panic(err) } defer session.Close() store, err := cassandra.NewFromConnection(session, cassandra.Config{ Keyspace: "gofiber", Table: "kv_store", }) if err != nil { panic(err) } defer store.Close() } ``` --- ## Clickhouse A Clickhouse storage driver using [https://github.com/ClickHouse/clickhouse-go](https://github.com/ClickHouse/clickhouse-go). ![Release](https://img.shields.io/github/v/tag/gofiber/storage?filter=clickhouse*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-clickhouse.yml?label=Tests) #### Table of Contents - [Signatures](#signatures) - [Installation](#installation) - [Examples](#examples) - [Config](#config) - [Default Config](#default-config) #### Signatures ```go func New(config ...Config) (*Storage, error) func NewWithContext(ctx context.Context, configuration Config) (*Storage, error) func NewFromConnection(conn driver.Conn, configuration Config) (*Storage, error) func NewFromConnectionWithContext(ctx context.Context, conn driver.Conn, configuration Config) (*Storage, error) func (s *Storage) Get(key string) ([]byte, error) func (s *Storage) GetWithContext(ctx context.Context, key string) ([]byte, error) func (s *Storage) Set(key string, val []byte, exp time.Duration) error func (s *Storage) SetWithContext(ctx context.Context, key string, val []byte, exp time.Duration) error func (s *Storage) Delete(key string) error func (s *Storage) DeleteWithContext(ctx context.Context, key string) error func (s *Storage) Reset() error func (s *Storage) ResetWithContext(ctx context.Context) error func (s *Storage) Close() error func (s *Storage) Conn() *Session ``` **Note:** Expirations are stored with a one-second granularity and rounded up, so an entry is never dropped before its expiration but may outlive it by up to a second. #### Installation Clickhouse is supported on the last version of Go: Install the clickhouse implementation: ```bash go get github.com/gofiber/storage/clickhouse ``` #### Running the tests This module uses [Testcontainers for Go](https://github.com/testcontainers/testcontainers-go/) to run integration tests, which will start a local instance of Clickhouse as a Docker container under the hood. To run the tests, you must have Docker (or another container runtime 100% compatible with the Docker APIs) installed on your machine. #### Local development Before running this implementation, you must ensure a Clickhouse cluster is available. For local development, we recommend using the Clickhouse Docker image; it contains everything necessary for the client to operate correctly. To start Clickhouse using Docker, issue the following: ```bash docker run -d -p 9000:9000 --name some-clickhouse-server --ulimit nofile=262144:262144 clickhouse/clickhouse-server ``` After running this command you're ready to start using the storage and connecting to the database. #### Examples You can use the following options to create a clickhouse storage driver: ```go import "github.com/gofiber/storage/clickhouse" // Initialize default config, to connect to localhost:9000 using the memory engine and with a clean table. store, err := clickhouse.New(clickhouse.Config{ Host: "localhost", Port: 9000, Clean: true, }) // Initialize custom config to connect to a different host/port and use custom engine and with clean table. store, err := clickhouse.New(clickhouse.Config{ Host: "some-ip-address", Port: 9000, Engine: clickhouse.MergeTree, Clean: true, }) // Initialize to connect with TLS enabled with your own tls.Config and with clean table. tlsConfig := config := &tls.Config{...} store, err := clickhouse.New(clickhouse.Config{ Host: "some-ip-address", Port: 9000, Clean: true, TLSConfig: tlsConfig, }) ``` #### Config ```go // Config defines configuration options for Clickhouse connection. type Config struct { // The host of the database. Ex: 127.0.0.1 Host string // The port where the database is supposed to listen to. Ex: 9000 Port int // The database that the connection should authenticate from Database string // The username to be used in the authentication Username string // The password to be used in the authentication Password string // The name of the table that will store the data Table string // The engine that should be used in the table Engine string // Should start a clean table, default false Clean bool // TLS configuration, default nil TLSConfig *tls.Config // Should the connection be in debug mode, default false Debug bool // The function to use with the debug config, default print function. It only works when debug is true Debugf func(format string, v ...any) } ``` #### Default Config ```go var DefaultConfig = Config{ Host: "localhost", Port: 9000, Engine: "Memory", Clean: false, } ``` #### Using an Existing Clickhouse Connection If your application already holds a `clickhouse.Conn`, you can build the storage on it instead of opening a second connection. Only the `Table`, `Engine` and `Clean` options are read; the connection details are taken from the connection you pass. The connection stays yours to close: `Close` on a storage built this way leaves it open, so the rest of your application keeps working. The storage itself is closed: any operation on it afterwards returns `ErrClosed`. ```go import ( driver "github.com/ClickHouse/clickhouse-go/v2" "github.com/gofiber/storage/clickhouse" ) func main() { conn, err := driver.Open(&driver.Options{ Addr: []string{"127.0.0.1:9000"}, }) if err != nil { panic(err) } defer conn.Close() store, err := clickhouse.NewFromConnection(conn, clickhouse.Config{ Table: "fiber_storage", Engine: clickhouse.Memory, }) if err != nil { panic(err) } defer store.Close() } ``` --- ## Cloudflare KV ![Release](https://img.shields.io/github/v/tag/gofiber/storage?filter=cloudflarekv*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-cloudflarekv.yml?label=Tests) A Cloudflare KV storage driver using [cloudflare/cloudflare-go](https://github.com/cloudflare/cloudflare-go). #### Table of Contents - [Signatures](#signatures) - [Installation](#installation) - [Examples](#examples) - [Config](#config) - [Default Config](#default-config) #### Signatures ```go func New(config ...Config) Storage func NewFromConnection(api APIInterface, config ...Config) *Storage func (s *Storage) Get(key string) ([]byte, error) func (s *Storage) GetWithContext(ctx context.Context, key string) ([]byte, error) func (s *Storage) Set(key string, val []byte, exp time.Duration) error func (s *Storage) SetWithContext(ctx context.Context, key string, val []byte, exp time.Duration) error func (s *Storage) Delete(key string) error func (s *Storage) DeleteWithContext(ctx context.Context, key string) error func (s *Storage) Reset() error func (s *Storage) ResetWithContext(ctx context.Context) error func (s *Storage) Close() error func (s *Storage) Conn() *cloudflare.API ``` #### Installation ```bash go mod init github.com// ``` And then install the Cloudflare KV implementation: ```bash go get github.com/gofiber/storage/cloudflarekv ``` #### Examples Import the storage package. ```go import "github.com/gofiber/storage/cloudflarekv" ``` You can use the following methods to create storage. The Key must be an API Token generated with at least `Account.Workers KV Storage` permission. Check the [Create API Token](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/) documentation to generate one. ```go // Initialize default config store := cloudflarekv.New() store := cloudflarekv.New(cloudflarekv.Config{ Key: "", Email: "", AccountID: "fiber", NamespaceID: "fiber", Reset: false, }) ``` #### Config ```go type Config struct { // Cloudflare Auth Token // // Optional. Default is "" Key string // Cloudflare Email // // Optional. Default is "" Email string // Account id // // Optional. Default is "fiber" AccountID string // Namespace id // // Optional. Default is "fiber" NamespaceID string // Reset clears any existing keys in existing Table // // Optional. Default is false Reset bool } ``` #### Default Config ```go var ConfigDefault = Config{ Key: "", Email: "", AccountID: "fiber", NamespaceID: "fiber", Reset: false, } ``` #### Using an Existing Cloudflare API Client If your application already holds a Cloudflare API client, you can build the storage on it instead of creating a second one. Only the `Email`, `AccountID`, `NamespaceID` and `Reset` options are read; the credentials come from the client. The client stays yours to manage: `Close` on a storage built this way is a no-op, so the rest of your application keeps working. ```go import ( "github.com/cloudflare/cloudflare-go" "github.com/gofiber/storage/cloudflarekv" ) func main() { api, err := cloudflare.NewWithAPIToken("token") if err != nil { panic(err) } store := cloudflarekv.NewFromConnection(api, cloudflarekv.Config{ Email: "user@example.com", AccountID: "account-id", NamespaceID: "namespace-id", }) defer store.Close() } ``` --- ## Coherence A Coherence storage driver using [https://github.com/oracle/coherence-go-client](https://github.com/oracle/coherence-go-client). ![Release](https://img.shields.io/github/v/tag/gofiber/storage?filter=coherence*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-coherence.yml?label=Tests) #### Table of Contents - [Signatures](#signatures) - [Installation](#installation) - [Examples](#examples) - [Config](#config) - [Default Config](#default-config) #### Signatures ```go func New(config ...Config) (*Storage, error) func NewWithContext(ctx context.Context, config ...Config) (*Storage, error) func NewFromConnection(session *coherence.Session, config ...Config) (*Storage, error) func NewFromConnectionWithContext(ctx context.Context, session *coherence.Session, config ...Config) (*Storage, error) func (s *Storage) Get(key string) ([]byte, error) func (s *Storage) GetWithContext(ctx context.Context, key string) ([]byte, error) func (s *Storage) Set(key string, val []byte, exp time.Duration) error func (s *Storage) SetWithContext(ctx context.Context, key string, val []byte, exp time.Duration) error func (s *Storage) Delete(key string) error func (s *Storage) DeleteWithContext(ctx context.Context, key string) error func (s *Storage) Reset() error func (s *Storage) ResetWithContext(ctx context.Context) error func (s *Storage) Close() error func (s *Storage) Conn() *Session ``` #### Installation Coherence is supported on Go versions 1.23 and above: Install the coherence implementation: ```bash go get github.com/gofiber/storage/coherence ``` Before running or testing this implementation, you must ensure a Coherence cluster is available. For local development, we recommend using the Coherence CE Docker image; it contains everything necessary for the client to operate correctly. To start a Coherence cluster using Docker, issue the following: ```bash docker run -d -p 1408:1408 ghcr.io/oracle/coherence-ce:25.03 ``` See the documentation [here](https://pkg.go.dev/github.com/oracle/coherence-go-client/v2@v2.0.0/coherence#hdr-Obtaining_a_Session) on connection options when creating a Coherence session. #### Examples Import the storage package. ```go import "github.com/gofiber/storage/coherence" ``` You can use the following possibilities to create a storage: ```go // Initialize default config, to connect to localhost:1408 using plain text store, err := coherence.New() // Initialize custom config to connect to a different host/port and use plain text and expiry of 5 minutes. store, err := coherence.New(coherence.Config{ Address: "my-host:myport", Expiration: time.Duration(300) * time.Second, // 5 minutes }) // Initialize to connect with TLS enabled with your own tls.Config tlsConfig := config := &tls.Config{...} store, err := coherence.New(coherence.Config{ Address: "my-host:myport", TLSConfig: tlsConfig, }) ``` > Note: If you create two stores using `coherence.New()` they will effectivity be identical. > If you wish to have two separate stores, then you can use: > ```go > store1, err := coherence.New(Config{ScopeName: "scope1"}) > store2, err := coherence.New(Config{ScopeName: "scope2"}) > ``` **Near Caches** The latest version of the Coherence Go client introduces near cache support to cache frequently accessed data in the Go client to avoid sending requests across the network. This is particularly useful if you are using sticky sessions via a LBR as this will cache the session in the Go process and the `Get()` operations will be much quicker. When the session is expired on the server it will automatically be removed from the near cache. To enable this for you session, you can set the `NearCacheTimeout` to a duration less than the expiry. ```go // Initialize default config, to connect to localhost:1408 using plain text store, err := coherence.New() // Use plain text with default expiry of 5 minutes, and a near cache expiry of 2 minutes store, err := coherence.New(coherence.Config{ Address: "my-host:myport", Expiration: time.Duration(300) * time.Second, // 5 minutes NearCacheTimeout: time.Duration(120) * time.Second, // 2 minutes }) ``` > Note: You must ensure your near cache timeout is less that the session timeout. #### Config ```go // Config defines configuration options for Coherence connection. type Config struct { // Address to connect to, defaults to "localhost:1408" Address string // Timeout is the default session timeout to connect to Coherence, defaults to 30s Timeout time.Duration // ScopeName defines a scope allowing for multiple storage sessions ScopeName string // Reset indicates if the store should be reset after being created Reset bool // TLSConfig specifies tls.Config to use when connecting, if nil then plain text is used TLSConfig *tls.Config // NearCacheTimeout defines the timeout for a near cache. Is this is set, then a near cache // with the timeout is created. Note: this must be less than the session timeout or any timeout you specify // when using Set(). NearCacheTimeout time.Duration } ``` #### Default Config ```go var DefaultConfig = Config{ Address: "localhost:1408", Timeout: time.Duration(120) * time.Seconds, ScopeName: defaultScopeName, Reset: false, NearCacheTimeout: time.Duration(60) * time.Seconds, } ``` #### Using an Existing Coherence Session If your application already holds a `*coherence.Session`, you can build the storage on it instead of creating a second one. Only the `ScopeName`, `NearCacheTimeout` and `Reset` options are read; the connection settings come from the session. `NewFromConnectionWithContext` uses the given context for the optional reset. The session stays yours to close: `Close` on a storage built this way leaves it open, so the rest of your application keeps working. The storage itself is closed: any operation on it afterwards returns `ErrClosed`. ```go import ( "context" "github.com/gofiber/storage/coherence" coh "github.com/oracle/coherence-go-client/v2/coherence" ) func main() { session, err := coh.NewSession(context.Background(), coh.WithPlainText()) if err != nil { panic(err) } defer session.Close() store, err := coherence.NewFromConnection(session, coherence.Config{ ScopeName: "my-store", }) if err != nil { panic(err) } defer store.Close() } ``` --- ## Couchbase ![Release](https://img.shields.io/github/v/tag/gofiber/storage?filter=couchbase*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-couchbase.yml?label=Tests) A Couchbase storage driver using [couchbase/gocb](https://github.com/couchbase/gocb). #### Table of Contents - [Signatures](#signatures) - [Installation](#installation) - [Examples](#examples) - [Config](#config) - [Default Config](#default-config) #### Signatures ```go func New(config ...Config) Storage func NewFromConnection(cluster *gocb.Cluster, config ...Config) *Storage func (s *Storage) Get(key string) ([]byte, error) func (s *Storage) GetWithContext(ctx context.Context, key string) ([]byte, error) func (s *Storage) Set(key string, val []byte, exp time.Duration) error func (s *Storage) SetWithContext(ctx context.Context, key string, val []byte, exp time.Duration) error func (s *Storage) Delete(key string) error func (s *Storage) DeleteWithContext(ctx context.Context, key string) error func (s *Storage) Reset() error func (s *Storage) ResetWithContext(ctx context.Context) error func (s *Storage) Close() error func (s *Storage) Conn() *gocb.Cluster ``` #### Installation Couchbase is tested on the 2 last [Go versions](https://golang.org/dl/) with support for modules. So make sure to initialize one first if you didn't do that yet: ```bash go mod init github.com// ``` And then install the Couchbase implementation: ```bash go get github.com/gofiber/storage/couchbase/v2 ``` #### Examples Import the storage package. ```go import "github.com/gofiber/storage/couchbase/v2" ``` You can use the following possibilities to create a storage: ```go // Initialize default config store := couchbase.New() // Initialize Couchbase storage with custom config store := couchbase.New(couchbase.Config{ Host: "127.0.0.1:8091", Username: "", Password: "", Bucket: 0, ConnectionTimeout: 3* time.Second, KVTimeout: 1* time.Second, }) ``` #### Config ```go type Config struct { // The application username to Connect to the Couchbase cluster Username string // The application password to Connect to the Couchbase cluster Password string // The connection string for the Couchbase cluster Host string // The name of the bucket to Connect to Bucket string // The timeout for connecting to the Couchbase cluster ConnectionTimeout time.Duration // The timeout for performing operations on the Couchbase cluster KVTimeout time.Duration } ``` #### Default Config ```go // ConfigDefault is the default config var ConfigDefault = Config{ Host: "127.0.0.1:8091", Username: "admin", Password: "123456", Bucket: "fiber_storage", ConnectionTimeout: 3 * time.Second, KVTimeout: 1 * time.Second, } ``` #### Using an Existing Couchbase Connection If your application already holds a `*gocb.Cluster`, you can build the storage on it instead of connecting a second time. Only the `Bucket` option is read; the connection settings come from the cluster. Values are read and written with a legacy transcoder passed per operation, so a cluster left on gocb's default JSON transcoder still stores raw bytes. The cluster stays yours to close: `Close` on a storage built this way leaves it open, so the rest of your application keeps working. The storage itself is closed: any operation on it afterwards returns `ErrClosed`. ```go import ( "github.com/couchbase/gocb/v2" "github.com/gofiber/storage/couchbase/v2" ) func main() { cluster, err := gocb.Connect("127.0.0.1", gocb.ClusterOptions{ Authenticator: gocb.PasswordAuthenticator{ Username: "user", Password: "password", }, }) if err != nil { panic(err) } defer cluster.Close(nil) store := couchbase.NewFromConnection(cluster, couchbase.Config{ Bucket: "fiber_storage", }) defer store.Close() } ``` --- ## DynamoDB ![Release](https://img.shields.io/github/v/tag/gofiber/storage?filter=dynamodb*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-dynamodb.yml?label=Tests) A DynamoDB storage driver using [aws/aws-sdk-go-v2](https://github.com/aws/aws-sdk-go-v2). **Note:** If config fields of credentials not given, credentials are using from the environment variables, ~/.aws/credentials, or EC2 instance role. If config fields of credentials given, credentials are using from config. Look at: [specifying credentials](https://aws.github.io/aws-sdk-go-v2/docs/configuring-sdk/#specifying-credentials) #### Table of Contents - [Signatures](#signatures) - [Installation](#installation) - [Examples](#examples) - [Config](#config) - [Default Config](#default-config) #### Signatures ```go func New(config Config) *Storage func NewWithContext(ctx context.Context, config Config) *Storage func NewFromConnection(db *dynamodb.Client, config Config) *Storage func NewFromConnectionWithContext(ctx context.Context, db *dynamodb.Client, config Config) *Storage func (s *Storage) Get(key string) ([]byte, error) func (s *Storage) GetWithContext(ctx context.Context, key string) ([]byte, error) func (s *Storage) Set(key string, val []byte, exp time.Duration) error func (s *Storage) SetWithContext(ctx context.Context, key string, val []byte, exp time.Duration) error func (s *Storage) Delete(key string) error func (s *Storage) DeleteWithContext(ctx context.Context, key string) error func (s *Storage) Reset() error func (s *Storage) ResetWithContext(ctx context.Context) error func (s *Storage) Close() error func (s *Storage) Conn() *awsdynamodb.Client ``` #### Installation DynamoDB is tested on the 2 last [Go versions](https://golang.org/dl/) with support for modules. So make sure to initialize one first if you didn't do that yet: ```bash go mod init github.com// ``` And then install the dynamodb implementation: ```bash go get github.com/gofiber/storage/dynamodb/v2 ``` #### Examples Import the storage package. ```go import "github.com/gofiber/storage/dynamodb/v2" ``` You can use the following possibilities to create a storage: ```go // Initialize dynamodb store := dynamodb.New(dynamodb.Config{ }) ``` #### Config ```go type Config struct { // Region of the DynamoDB service you want to use. // Valid values: https://docs.aws.amazon.com/general/latest/gr/rande.html#ddb_region. // E.g. "us-west-2". // Optional (read from shared config file or environment variable if not set). // Environment variable: "AWS_REGION". Region string // Name of the DynamoDB table. // Optional ("fiber_storage" by default). Table string // CustomEndpoint allows you to set a custom DynamoDB service endpoint. // This is especially useful if you're running a "DynamoDB local" Docker container for local testing. // Typical value for the Docker container: "http://localhost:8000". // See https://hub.docker.com/r/amazon/dynamodb-local/. // Optional ("" by default) Endpoint string // Credentials overrides AWS access key and AWS secret access key. Not recommended. // // Optional. Default is Credentials{} Credentials Credentials // The maximum number of times requests that encounter retryable failures should be attempted. // // Optional. Default is 3 MaxAttempts int // Reset clears any existing keys in existing Bucket // // Optional. Default is false Reset bool // ReadCapacityUnits of the table. // Only required when the table doesn't exist yet and is created by gokv. // Optional (5 by default, which is the same default value as when creating a table in the web console) // 25 RCUs are included in the free tier (across all tables). // For example calculations, see https://github.com/awsdocs/amazon-dynamodb-developer-guide/blob/c420420a59040c5b3dd44a6e59f7c9e55fc922ef/doc_source/HowItWorks.ProvisionedThroughput. // For limits, see https://github.com/awsdocs/amazon-dynamodb-developer-guide/blob/c420420a59040c5b3dd44a6e59f7c9e55fc922ef/doc_source/Limits.md#capacity-units-and-provisioned-throughput.md#provisioned-throughput. ReadCapacityUnits int64 // ReadCapacityUnits of the table. // Only required when the table doesn't exist yet and is created by gokv. // Optional (5 by default, which is the same default value as when creating a table in the web console) // 25 RCUs are included in the free tier (across all tables). // For example calculations, see https://github.com/awsdocs/amazon-dynamodb-developer-guide/blob/c420420a59040c5b3dd44a6e59f7c9e55fc922ef/doc_source/HowItWorks.ProvisionedThroughput. // For limits, see https://github.com/awsdocs/amazon-dynamodb-developer-guide/blob/c420420a59040c5b3dd44a6e59f7c9e55fc922ef/doc_source/Limits.md#capacity-units-and-provisioned-throughput.md#provisioned-throughput. WriteCapacityUnits int64 // If the table doesn't exist yet, gokv creates it. // If WaitForTableCreation is true, gokv will block until the table is created, with a timeout of 15 seconds. // If the table still doesn't exist after 15 seconds, an error is returned. // If WaitForTableCreation is false, gokv returns the client immediately. // In the latter case you need to make sure that you don't read from or write to the table before it's created, // because otherwise you will get ResourceNotFoundException errors. // Optional (true by default). WaitForTableCreation *bool } type Credentials struct { AccessKey string SecretAccessKey string } ``` #### Default Config ```go var ConfigDefault = Config{ Table: "fiber_storage", Credentials: Credentials{}, MaxAttempts: 3, Reset: false, ReadCapacityUnits: 5, WriteCapacityUnits: 5, WaitForTableCreation: aws.Bool(true), } ``` #### Using an Existing DynamoDB Client If your application already holds a `*dynamodb.Client`, you can build the storage on it instead of creating a second one. Only the table options and `Reset` are read; the endpoint and credentials come from the client. `Reset` deletes every entry in the table and leaves the table itself — with its billing mode, indexes, streams and tags — in place. The client stays yours to manage: `Close` on a storage built this way is a no-op, so the rest of your application keeps working. ```go import ( "context" awsconfig "github.com/aws/aws-sdk-go-v2/config" awsdynamodb "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/gofiber/storage/dynamodb/v2" ) func main() { awscfg, err := awsconfig.LoadDefaultConfig(context.Background()) if err != nil { panic(err) } client := awsdynamodb.NewFromConfig(awscfg) store := dynamodb.NewFromConnection(client, dynamodb.Config{ Table: "fiber_storage", }) defer store.Close() } ``` --- ## Etcd ![Release](https://img.shields.io/github/v/tag/gofiber/storage?filter=etcd*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-etcd.yml?label=Tests) A Etcd storage driver using [`etcd-io/etcd`](https://github.com/etcd-io/etcd). #### Table of Contents - [Signatures](#signatures) - [Installation](#installation) - [Examples](#examples) - [Config](#config) - [Default Config](#default-config) #### Signatures ```go func New(config ...Config) *Storage func NewFromConnection(db *clientv3.Client) *Storage func (s *Storage) Get(key string) ([]byte, error) func (s *Storage) GetWithContext(ctx context.Context, key string) ([]byte, error) func (s *Storage) Set(key string, val []byte, exp time.Duration) error func (s *Storage) SetWithContext(ctx context.Context, key string, val []byte, exp time.Duration) error func (s *Storage) Delete(key string) error func (s *Storage) DeleteWithContext(ctx context.Context, key string) error func (s *Storage) Reset() error func (s *Storage) ResetWithContext(ctx context.Context) error func (s *Storage) Close() error func (s *Storage) Conn() *clientv3.Client ``` #### Installation Etcd requires Go 1.26 or newer — the version required by [`etcd-io/etcd`](https://github.com/etcd-io/etcd) v3.7 — and is tested on the latest [Go versions](https://golang.org/dl/) with support for modules. So make sure to initialize one first if you didn't do that yet: ```bash go mod init github.com// ``` And then install the etcd implementation: ```bash go get github.com/gofiber/storage/etcd/v2 ``` #### Examples Import the storage package. ```go import "github.com/gofiber/storage/etcd/v2" ``` You can use the following possibilities to create a storage: ```go // Initialize default config store := etcd.New() // Initialize custom config store := etcd.New(Config{ Endpoints: []string{"localhost:2379"}, }) ``` #### Config ```go type Config struct { // Endpoints is a list of URLs. Endpoints []string // DialTimeout is the timeout for failing to establish a connection. DialTimeout time.Duration // Username is a username for authentication. Username string // Password is a password for authentication. Password string // TLS holds the client secure credentials, if any. TLS *tls.Config } ``` #### Default Config ```go var ConfigDefault = Config{ Endpoints: []string{"localhost:2379"}, DialTimeout: 2 * time.Second, Username: "", Password: "", TLS: nil, } ``` #### Using an Existing Etcd Connection If your application already holds a `*clientv3.Client`, you can build the storage on it instead of dialing a second time. The client stays yours to close: `Close` on a storage built this way leaves it open, so the rest of your application keeps working. The storage itself is closed: any operation on it afterwards returns `ErrClosed`. > **Warning:** the storage does not namespace its keys. `Reset` deletes **every key** reachable through the client — on a cluster shared with service discovery or configuration, that is all of it. ```go import ( "github.com/gofiber/storage/etcd/v2" clientv3 "go.etcd.io/etcd/client/v3" ) func main() { client, err := clientv3.New(clientv3.Config{ Endpoints: []string{"localhost:2379"}, }) if err != nil { panic(err) } defer client.Close() store := etcd.NewFromConnection(client) defer store.Close() } ``` --- ## Firestore ![Release](https://img.shields.io/github/v/tag/gofiber/storage?filter=firestore*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-firestore.yml?label=Tests) A Firestore storage driver using [cloud.google.com/go/firestore](https://pkg.go.dev/cloud.google.com/go/firestore). **Note:** If no credentials are provided, the driver uses Application Default Credentials (ADC) or the `GOOGLE_APPLICATION_CREDENTIALS` environment variable. If credentials are provided via config (`Credentials` or `CredentialsPath`), those take precedence. See: [Google Cloud Authentication Guide](https://cloud.google.com/docs/authentication) #### Table of Contents - [Signatures](#signatures) - [Installation](#installation) - [Examples](#examples) - [Config](#config) - [Default Config](#default-config) #### Signatures ```go func New(config ...Config) *Storage func NewWithContext(ctx context.Context, config ...Config) *Storage func NewFromConnection(client *firestore.Client, collection string) *Storage func (s *Storage) Get(key string) ([]byte, error) func (s *Storage) GetWithContext(ctx context.Context, key string) ([]byte, error) func (s *Storage) Set(key string, val []byte, exp time.Duration) error func (s *Storage) SetWithContext(ctx context.Context, key string, val []byte, exp time.Duration) error func (s *Storage) Delete(key string) error func (s *Storage) DeleteWithContext(ctx context.Context, key string) error func (s *Storage) Reset() error func (s *Storage) ResetWithContext(ctx context.Context) error func (s *Storage) Close() error func (s *Storage) Conn() *firestore.Client ``` #### Installation Firestore is tested on the last [Go versions](https://golang.org/dl/) with support for modules. So make sure to initialize one first if you didn't do that yet: ```bash go mod init github.com// ``` And then install the firestore implementation: ```bash go get github.com/gofiber/storage/firestore ``` #### Examples Import the storage package. ```go import firestorage "github.com/gofiber/storage/firestore" ``` You can use the following possibilities to create a storage: ```go // Initialize with Application Default Credentials store := firestorage.New(firestorage.Config{ ProjectID: "my-gcp-project", }) // Initialize with service account JSON file store := firestorage.New(firestorage.Config{ ProjectID: "my-gcp-project", CredentialsPath: "/path/to/service-account-key.json", Collection: "sessions", }) // Initialize with embedded credentials JSON store := firestorage.New(firestorage.Config{ ProjectID: "my-gcp-project", Credentials: `{"type": "service_account", ...}`, }) // Initialize with custom timeout store := firestorage.New(firestorage.Config{ ProjectID: "my-gcp-project", Collection: "fiber_storage", RequestTimeout: 10 * time.Second, Reset: false, }) ``` ##### Using an Existing Firestore Client If you already have a Firestore client configured in your application, you can create a Storage instance directly from that client. The client stays yours to close: `Close` on a storage built this way leaves it open, so the rest of your application keeps working. The storage itself is closed: any operation on it afterwards returns `ErrClosed`. > **Behavior change:** `Close` used to close the client passed to `NewFromConnection`. It no longer does. If your application relied on `store.Close()` to release a client it created itself, close that client directly (`defer client.Close()`). ```go import ( "cloud.google.com/go/firestore" "context" firestorage "github.com/gofiber/storage/firestore" ) ctx := context.Background() client, err := firestore.NewClient(ctx, "my-gcp-project") if err != nil { panic(err) } // The client is yours: closing the storage no longer closes it. defer client.Close() store := firestorage.NewFromConnection(client, "my_collection") defer store.Close() ``` #### Config ```go type Config struct { // ProjectID is the Google Cloud project ID // Required. Will panic if empty ProjectID string // Collection name where data will be stored // // Optional. Default is "fiber_storage" Collection string // CredentialsPath is the path to the service account JSON key file // If not provided, Application Default Credentials (ADC) will be used // // Optional. Default is "" CredentialsPath string // Credentials is a JSON string with service account credentials // Takes precedence over CredentialsPath if both are provided // // Optional. Default is "" Credentials string // RequestTimeout is the timeout for Firestore requests // // Optional. Default is 10 seconds RequestTimeout time.Duration // Reset clears all documents in the collection on initialization // // Optional. Default is false Reset bool } ``` #### Default Config ```go var ConfigDefault = Config{ Collection: "fiber_storage", RequestTimeout: 10 * time.Second, Reset: false, } ``` #### Additional Resources - [Firestore Go SDK Documentation](https://pkg.go.dev/cloud.google.com/go/firestore) - [Firebase Console](https://console.firebase.google.com) - [Google Cloud Firestore Documentation](https://cloud.google.com/firestore/docs) - [Google Cloud Authentication Guide](https://cloud.google.com/docs/authentication) - [Firestore Quotas and Limits](https://cloud.google.com/firestore/quotas) --- ## LevelDB ![Release](https://img.shields.io/github/v/tag/gofiber/storage?filter=leveldb*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-leveldb.yml?label=Tests) A fast key-value DB using [syndtr/goleveldb](https://github.com/syndtr/goleveldb) #### Table of Contents - [Signatures](#signatures) - [Installation](#installation) - [Examples](#examples) - [Config](#config) - [Default Config](#default-config) #### Signatures ```go func New(config ...Config) Storage func NewFromConnection(db *leveldb.DB, config ...Config) *Storage func (s *Storage) Get(key string) ([]byte, error) func (s *Storage) GetWithContext(ctx context.Context, key string) ([]byte, error) func (s *Storage) Set(key string, val []byte, exp time.Duration) error func (s *Storage) SetWithContext(ctx context.Context, key string, val []byte, exp time.Duration) error func (s *Storage) Delete(key string) error func (s *Storage) DeleteWithContext(ctx context.Context, key string) error func (s *Storage) Reset() error func (s *Storage) ResetWithContext(ctx context.Context) error func (s *Storage) Close() error func (s *Storage) Conn() *leveldb.DB ``` **Note:** The tuning fields of `Config` are applied when the database is opened. They used to be ignored: only `Path` and `GCInterval` had any effect. **Note:** With `ReadOnly` set, `Set`, `Delete` and `Reset` return `ErrReadOnly`, which callers can match with `errors.Is`. **Note:** Every entry is stored as a JSON envelope carrying the value and its expiration, so a value read directly through `Conn()` is the envelope rather than the raw payload. Entries written by earlier versions of this driver are still read back correctly, both the envelopes they wrote for keys with an expiration and the bare payloads they wrote for keys without one. Those earlier envelopes carry no marker, so a bare payload that is a JSON object with exactly a `value` and an `expire_at` field is read as one; this only affects databases written before this version. The same is true in principle of a payload that reproduces the current envelope — a JSON object carrying `_fiber_storage_v`, `value` and `expire_at` — but that field name is namespaced to this driver precisely so a caller's own data does not resemble one by accident. **Note:** An entry this driver cannot read is reported rather than silently dropped: `ErrUnknownEnvelope` for one written by a newer version of the driver, `ErrCorruptEnvelope` for one whose value is missing. Both are matchable with `errors.Is`. **Note:** LevelDB has no native context support, so the context methods run the operation to completion. They do honour a context that is already cancelled or past its deadline, returning the context error without touching the storage. #### Installation LevelDB is tested on the 2 last [Go versions](https://golang.org/dl/) with support for modules. So make sure to initialize one first if you didn't do that yet: ```bash go mod init github.com// ``` And then install the leveldb implementation: ```bash go get github.com/gofiber/storage/leveldb ``` #### Examples Import the storage package. ```go import "github.com/gofiber/storage/leveldb" ``` You can use the following possibilities to create a storage: ```go // Initialize default config store := leveldb.New() // Initialize custom config store := leveldb.New(leveldb.Config{ Path: "./testdb", GCInterval: 10 * time.Second, }) ``` #### Config ```go type Config struct { // Path is the filesystem path for the database // // Optional. Default is "./fiber.leveldb" Path string // CacheSize is the size of LevelDB's cache (in MB) // // Optional. Default is 8MB CacheSize int // BlockSize is the size of data blocks (in KB) // // Optional. Default is 4KB BlockSize int // WriteBuffer is the size of write buffer (in MB) // // Optional. Default is 4MB WriteBuffer int // CompactionL0Trigger is the number of level-0 tables that triggers compaction // // Optional. Default is 4 CompactionL0Trigger int // WriteL0PauseTrigger is the number of level-0 tables that triggers write pause // // Optional. Default is 12 WriteL0PauseTrigger int // WriteL0SlowdownTrigger is the number of level-0 tables that triggers write slowdown // // Optional. Default is 8 WriteL0SlowdownTrigger int // MaxOpenFiles is the maximum number of open files that can be held // // Optional. Default is 200 on MacOS, 500 on others MaxOpenFiles int // CompactionTableSize is the size of compaction table (in MB) // // Optional. Default is 2MB CompactionTableSize int // BloomFilterBits is the number of bits used in bloom filter // // Optional. Default is 10 bits/key BloomFilterBits int // NoSync completely disables fsync // // Optional. Default is false NoSync bool // ReadOnly opens the database in read-only mode // // Optional. Default is false ReadOnly bool // ErrorIfMissing returns error if database doesn't exist // // Optional. Default is false ErrorIfMissing bool // ErrorIfExist returns error if database exists // // Optional. Default is false ErrorIfExist bool // GCInterval is the garbage collection interval // // Optional. Default is 10 minutes GCInterval time.Duration } ``` #### Default Config ```go var ConfigDefault = Config{ Path: "./fiber.leveldb", CacheSize: 8, // 8 MB BlockSize: 4, // 4 KB WriteBuffer: 4, // 4 MB CompactionL0Trigger: 4, WriteL0PauseTrigger: 12, WriteL0SlowdownTrigger: 8, MaxOpenFiles: func() int { if runtime.GOOS == "darwin" { return 200 // MacOS } return 500 // Unix/Linux }(), CompactionTableSize: 2, // 2 MB BloomFilterBits: 10, // 10 bits per key NoSync: false, ReadOnly: false, ErrorIfMissing: false, ErrorIfExist: false, GCInterval: 10 * time.Minute, } ``` #### Using an Existing LevelDB Database LevelDB lets a single process hold a directory, so an application that already keeps a `*leveldb.DB` open cannot have the storage open the same path again. Pass the open database instead. Only the `GCInterval` and `ReadOnly` options are read. The database stays yours to close: `Close` on a storage built this way stops the garbage collector but leaves the database open, so the rest of your application keeps working. The storage itself is closed: any operation on it afterwards returns `ErrClosed`. Storages built on the same database share one write-order lock and one collector cursor, so each one's collector coordinates with the others' writes and a `Reset` by any of them rewinds them all. Closing the storage that opened the database closes the database itself: the others then report `ErrClosed` rather than reaching a closed handle. > **Warning:** the storage treats the whole keyspace as its own — `Reset` deletes every key in the database, and the background collector reclaims any value that looks like an expired entry. Keep application data out of a database backing this storage. ```go import ( leveldbstorage "github.com/gofiber/storage/leveldb" "github.com/syndtr/goleveldb/leveldb" ) func main() { db, err := leveldb.OpenFile("./fiber.leveldb", nil) if err != nil { panic(err) } defer db.Close() store := leveldbstorage.NewFromConnection(db) defer store.Close() } ``` --- ## Memcache ![Release](https://img.shields.io/github/v/tag/gofiber/storage?filter=memcache*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-memcache.yml?label=Tests) A Memcache storage driver using [`bradfitz/gomemcache`](https://github.com/bradfitz/gomemcache). #### Table of Contents - [Signatures](#signatures) - [Installation](#installation) - [Examples](#examples) - [Config](#config) - [Default Config](#default-config) #### Signatures ```go func New(config ...Config) Storage func NewFromConnection(db *memcache.Client, config ...Config) *Storage func (s *Storage) Get(key string) ([]byte, error) func (s *Storage) GetWithContext(ctx context.Context, key string) ([]byte, error) func (s *Storage) Set(key string, val []byte, exp time.Duration) error func (s *Storage) SetWithContext(ctx context.Context, key string, val []byte, exp time.Duration) error func (s *Storage) Delete(key string) error func (s *Storage) DeleteWithContext(ctx context.Context, key string) error func (s *Storage) Reset() error func (s *Storage) ResetWithContext(ctx context.Context) error func (s *Storage) Close() error func (s *Storage) Conn() *mc.Client ``` **Note:** Memcache has no native context support, so the context methods run the operation to completion. They do honour a context that is already cancelled or past its deadline, returning the context error without touching the storage. #### Installation Memory is tested on the 2 last [Go versions](https://golang.org/dl/) with support for modules. So make sure to initialize one first if you didn't do that yet: ```bash go mod init github.com// ``` And then install the memory implementation: ```bash go get github.com/gofiber/storage/memory/v2 ``` #### Examples Import the storage package. ```go import "github.com/gofiber/storage/memcache" ``` You can use the following possibilities to create a storage: ```go // Initialize default config store := memcache.New() // Initialize custom config store := memcache.New(memcache.Config{ Servers: "localhost:11211", }) ``` #### Config ```go type Config struct { // Server list divided by , // i.e. server1:11211, server2:11212 // // Optional. Default is "127.0.0.1:11211" Servers string // Reset clears any existing keys in existing Table // // Optional. Default is false Reset bool } ``` #### Default Config ```go var ConfigDefault = Config{ Servers: "127.0.0.1:11211", } ``` #### Using an Existing Memcache Connection If your application already holds a `*memcache.Client`, you can build the storage on it instead of creating a second one. Only `Reset` is read from the config; connection settings stay with your client. The client stays yours to manage: `Close` on a storage built this way is a no-op, so the rest of your application keeps working. > **Warning:** `Reset` runs `DeleteAll` against the memcached instance, deleting every item on it, not just this storage's entries. ```go import ( mc "github.com/bradfitz/gomemcache/memcache" "github.com/gofiber/storage/memcache/v2" ) func main() { client := mc.New("127.0.0.1:11211") store := memcache.NewFromConnection(client) defer store.Close() } ``` --- ## Memory ![Release](https://img.shields.io/github/v/tag/gofiber/storage?filter=memory*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-memory.yml?label=Tests) An in-memory storage driver. #### Table of Contents - [Signatures](#signatures) - [Installation](#installation) - [Examples](#examples) - [Config](#config) - [Default Config](#default-config) #### Signatures ```go func New(config ...Config) Storage func (s *Storage) Get(key string) ([]byte, error) func (s *Storage) GetWithContext(ctx context.Context, key string) ([]byte, error) func (s *Storage) Set(key string, val []byte, exp time.Duration) error func (s *Storage) SetWithContext(ctx context.Context, key string, val []byte, exp time.Duration) error func (s *Storage) Delete(key string) error func (s *Storage) DeleteWithContext(ctx context.Context, key string) error func (s *Storage) Reset() error func (s *Storage) ResetWithContext(ctx context.Context) error func (s *Storage) Close() error func (s *Storage) Conn() map[string]entry func (s *Storage) Keys() ([][]byte, error) ``` **Note:** `Conn` returns a snapshot copy of the stored entries rather than the live map, so reading it cannot race the garbage collector. Writing to the returned map does not affect the storage. **Note:** Expirations are stored with nanosecond precision, so an entry stays readable for exactly the duration given, no matter how short. Expired entries are reclaimed by a garbage collector that runs on `GCInterval`, but a `Get` never returns one that is past its expiration. **Note:** memory storage has no native context support, so the context methods run the operation to completion. They do honour a context that is already cancelled or past its deadline, returning the context error without touching the storage. #### Installation Memory is tested on the 2 last [Go versions](https://golang.org/dl/) with support for modules. So make sure to initialize one first if you didn't do that yet: ```bash go mod init github.com// ``` And then install the memory implementation: ```bash go get github.com/gofiber/storage/memory/v2 ``` #### Examples Import the storage package. ```go import "github.com/gofiber/storage/memory/v2" ``` You can use the following possibilities to create a storage: ```go // Initialize default config store := memory.New() // Initialize custom config store := memory.New(memory.Config{ GCInterval: 10 * time.Second, }) ``` #### Config ```go type Config struct { // Time before deleting expired keys // // Default is 10 * time.Second GCInterval time.Duration } ``` #### Default Config ```go var ConfigDefault = Config{ GCInterval: 10 * time.Second, } ``` --- ## Minio (Minio) ![Release](https://img.shields.io/github/v/tag/gofiber/storage?filter=minio*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-minio.yml?label=Tests) A Minio storage driver using [minio/minio-go](https://github.com/minio/minio-go). #### Table of Contents - [Signatures](#signatures) - [Installation](#installation) - [Examples](#examples) - [Config](#config) - [Default Config](#default-config) #### Signatures ```go func New(config ...Config) *Storage func NewWithContext(ctx context.Context, config ...Config) *Storage func NewFromConnection(client *minio.Client, config ...Config) *Storage func NewFromConnectionWithContext(ctx context.Context, client *minio.Client, config ...Config) *Storage func (s *Storage) Get(key string) ([]byte, error) func (s *Storage) GetWithContext(ctx context.Context, key string) ([]byte, error) func (s *Storage) Set(key string, val []byte, exp time.Duration) error func (s *Storage) SetWithContext(ctx context.Context, key string, val []byte, exp time.Duration) error func (s *Storage) Delete(key string) error func (s *Storage) DeleteWithContext(ctx context.Context, key string) error func (s *Storage) Reset() error func (s *Storage) ResetWithContext(ctx context.Context) error func (s *Storage) Close() error func (s *Storage) CheckBucket() error func (s *Storage) CheckBucketWithContext(ctx context.Context) error func (s *Storage) CreateBucket() error func (s *Storage) CreateBucketWithContext(ctx context.Context) error func (s *Storage) RemoveBucket() error func (s *Storage) Conn() *minio.Client ``` #### Installation Install the Minio implementation: ```bash go get github.com/gofiber/storage/minio ``` And then run minio on Docker ```bash docker run -d --restart always -p 9000:9000 -p 9001:9001 --name storage-minio --volume=minio:/var/lib/minio -e MINIO_ROOT_USER='minio-user' -e MINIO_ROOT_PASSWORD='minio-password' minio/minio server --console-address ":9001" /var/lib/minio ``` #### Examples Import the storage package. ```go import "github.com/gofiber/storage/minio" ``` You can use the following possibilities to create a storage: ```go // Initialize default config store := minio.New() // Initialize custom config store := minio.New(minio.Config{ Bucket: "fiber-bucket", Endpoint: "localhost:9000", Credentials: Credentials{ AccessKeyID: "minio-user", SecretAccessKey: "minio-password", }, }) ``` #### Config ```go // Config defines the config for storage. type Config struct { // Bucket // Default fiber-bucket Bucket string // Endpoint is a host name or an IP address Endpoint string // Region Set this value to override region cache // Optional Region string // Token Set this value to provide x-amz-security-token (AWS S3 specific) // Optional, Default is false Token string // Secure If set to true, https is used instead of http. // Default is false Secure bool // Reset clears any existing keys in existing Bucket // Optional. Default is false Reset bool // The maximum number of times requests that encounter retryable failures should be attempted. // Optional. Default is 10, same as the MinIO client. MaxRetry int // Credentials Minio access key and Minio secret key. // Need to be defined Credentials Credentials // GetObjectOptions Options for GET requests specifying additional options like encryption, If-Match GetObjectOptions minio.GetObjectOptions // PutObjectOptions // Allows user to set optional custom metadata, content headers, encryption keys and number of threads for multipart upload operation. PutObjectOptions minio.PutObjectOptions // ListObjectsOptions Options per to list objects ListObjectsOptions minio.ListObjectsOptions // RemoveObjectOptions Allows user to set options RemoveObjectOptions minio.RemoveObjectOptions } ``` #### Default Config The default configuration lacks Bucket, Region, and Endpoint which are all required and must be overwritten: ```go // ConfigDefault is the default config var ConfigDefault = Config{ Bucket: "fiber-bucket", Endpoint: "", Region: "", Token: "", Secure: false, Reset: false, Credentials: Credentials{}, GetObjectOptions: minio.GetObjectOptions{}, PutObjectOptions: minio.PutObjectOptions{}, ListObjectsOptions: minio.ListObjectsOptions{}, RemoveObjectOptions: minio.RemoveObjectOptions{}, } type Credentials struct { AccessKeyID string SecretAccessKey string } ``` #### Using an Existing Minio Connection If your application already holds a `*minio.Client`, you can build the storage on it instead of creating a second one. Only the `Bucket`, `Region`, `Reset` and object options are read; the endpoint and credentials come from the client. The client stays yours to manage: `Close` on a storage built this way is a no-op, so the rest of your application keeps working. ```go import ( storage "github.com/gofiber/storage/minio" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" ) func main() { client, err := minio.New("localhost:9000", &minio.Options{ Creds: credentials.NewStaticV4("minio-user", "minio-password", ""), }) if err != nil { panic(err) } store := storage.NewFromConnection(client, storage.Config{ Bucket: "fiber-bucket", }) defer store.Close() } ``` --- ## MockStorage ![Release](https://img.shields.io/github/v/tag/gofiber/storage?filter=mockstorage*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-mockstorage.yml?label=Tests) A mock storage implementation for Fiber. This storage is not persistent and is only used for testing purposes. ### Table of Contents - [Signatures](#signatures) - [Installation](#installation) - [Examples](#examples) - [Config](#config) - [Default Config](#default-config) ### Signatures #### Structs ```go type Storage struct { // contains filtered or unexported fields } type Entry struct { Value []byte Exp time.Time } type Config struct { CustomFuncs *CustomFuncs } type CustomFuncs struct { GetFunc func(key string) ([]byte, error) SetFunc func(key string, val []byte, exp time.Duration) error DeleteFunc func(key string) error ResetFunc func() error CloseFunc func() error ConnFunc func() map[string]Entry KeysFunc func() ([][]byte, error) } ``` #### Functions ```go // New creates a new Storage instance. You can optionally pass a Config. func New(config ...Config) *Storage // Get retrieves the value associated with the given key. func (s *Storage) Get(key string) ([]byte, error) // Set sets the value for the given key, with an optional expiration duration. func (s *Storage) Set(key string, val []byte, exp time.Duration) error // Delete removes the value associated with the given key. func (s *Storage) Delete(key string) error // Reset clears all values from the storage. func (s *Storage) Reset() error // Close performs any necessary cleanup when the storage is no longer needed. func (s *Storage) Close() error // Conn returns a copy of the current state of the storage. func (s *Storage) Conn() map[string]Entry // Keys returns a list of all keys in the storage. func (s *Storage) Keys() ([][]byte, error) // SetCustomFuncs allows you to set custom functions for the storage operations. func (s *Storage) SetCustomFuncs(custom *CustomFuncs) ``` ### Installation MockStorage is tested on the 2 last [Go versions](https://golang.org/dl/) with support for modules. So make sure to initialize one first if you didn't do that yet: ```bash go mod init github.com// ``` And then install the mockstorage implementation: ```bash go get github.com/gofiber/storage/mockstorage ``` ### Examples Import the storage package. ```go import "github.com/gofiber/storage/mockstorage" ``` You can use the following possibilities to create a storage: ```go // Initialize default config store := mockstorage.New() // Set a value in the storage. err := store.Set("key1", []byte("value1"), 0) if err != nil { // handle error } // Get a value from the storage. val, err := store.Get("key1") if err != nil { // handle error } fmt.Println(string(val)) // prints "value1" // Delete a value from the storage. err = store.Delete("key1") if err != nil { // handle error } // Mocking storage operations in tests: func TestMyFunction(t *testing.T) { // Create a new instance of MockStorage store := mockstorage.New() // Mock the Set function store.SetCustomFuncs(&mockstorage.CustomFuncs{ Set: func(key string, val []byte, exp time.Duration) error { if key == "expectedKey" && string(val) == "expectedValue" { return nil } return errors.New("unexpected key or value") }, }) // Call the function you want to test, which should call store.Set err := MyFunction(store) // Check that the function behaved as expected if err != nil { t.Errorf("MyFunction returned an error: %v", err) } } ``` > **Note:** In the `mockstorage` package, expiration of data is not handled automatically in the background. The data is only marked as expired and removed when you attempt to `Get()` it after its expiration time. If you're using a custom `Get()` function or accessing the data directly using the `Conn()` function, expired data will not be removed. Keep this in mind when writing your tests. ### Config ```go type Config struct { CustomFuncs *CustomFuncs } ``` ### Default Config ```go var ConfigDefault = Config{ CustomFuncs: &CustomFuncs{ GetFunc: nil, SetFunc: nil, DeleteFunc: nil, ResetFunc: nil, CloseFunc: nil, ConnFunc: nil, KeysFunc: nil, }, } ``` --- ## MongoDB (Mongodb) ![Release](https://img.shields.io/github/v/tag/gofiber/storage?filter=mongodb*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-mongodb.yml?label=Tests) A MongoDB storage driver using [mongodb/mongo-go-driver](https://github.com/mongodb/mongo-go-driver). #### Table of Contents - [Signatures](#signatures) - [Installation](#installation) - [Examples](#examples) - [Config](#config) - [Default Config](#default-config) #### Signatures ```go func New(config ...Config) Storage func NewWithContext(ctx context.Context, config ...Config) *Storage func NewFromConnection(client *mongo.Client, config ...Config) *Storage func NewFromConnectionWithContext(ctx context.Context, client *mongo.Client, config ...Config) *Storage func (s *Storage) Get(key string) ([]byte, error) func (s *Storage) GetWithContext(ctx context.Context, key string) ([]byte, error) func (s *Storage) Set(key string, val []byte, exp time.Duration) error func (s *Storage) SetWithContext(ctx context.Context, key string, val []byte, exp time.Duration) error func (s *Storage) Delete(key string) error func (s *Storage) DeleteWithContext(ctx context.Context, key string) error func (s *Storage) Reset() error func (s *Storage) ResetWithContext(ctx context.Context) error func (s *Storage) Close() error func (s *Storage) Conn() *mongo.Database ``` #### Installation MongoDB is tested on the 2 last [Go versions](https://golang.org/dl/) with support for modules. So make sure to initialize one first if you didn't do that yet: ```bash go mod init github.com// ``` And then install the mongodb implementation: ```bash go get github.com/gofiber/storage/mongodb/v3 ``` #### Examples Import the storage package. ```go import "github.com/gofiber/storage/mongodb/v3" ``` You can use the following possibilities to create a storage: ```go // Initialize default config store := mongodb.New() // Initialize custom config store := mongodb.New(mongodb.Config{ Host: "127.0.0.1", Port: 27017, Database: "fiber", Collection: "fiber_storage", Reset: false, }) // Initialize custom config using connection string store := mongodb.New(mongodb.Config{ ConnectionURI: "mongodb://user:password@127.0.0.1:27017", Database: "fiber", Collection: "fiber_storage", Reset: false, }) ``` #### Config ```go type Config struct { // Connection string to use for DB. Will override all other authentication values if used // // Optional. Default is "" ConnectionURI string // Host name where the DB is hosted // // Optional. Default is "127.0.0.1" Host string // Port where the DB is listening on // // Optional. Default is 27017 Port int // Server username // // Optional. Default is "" Username string // Server password // // Optional. Default is "" Password string // Database name // // Optional. Default is "fiber" Database string // Collection name // // Optional. Default is "fiber_storage" Collection string // Reset clears any existing keys in existing Table // // Optional. Default is false Reset bool } ``` #### Default Config ```go var ConfigDefault = Config{ ConnectionURI: "", Host: "127.0.0.1", Port: 27017, Database: "fiber", Collection: "fiber_storage", Reset: false, } ``` #### Using an Existing MongoDB Connection If your application already holds a `*mongo.Client`, you can build the storage on it instead of connecting a second time. Only the `Database`, `Collection` and `Reset` options are read; the connection settings come from the client. The client stays yours to disconnect: `Close` on a storage built this way leaves it connected, so the rest of your application keeps working. The storage itself is closed, and any operation on it afterwards returns `ErrClosed`. ```go import ( "context" "github.com/gofiber/storage/mongodb/v3" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" ) func main() { client, err := mongo.Connect(options.Client().ApplyURI("mongodb://localhost:27017")) if err != nil { panic(err) } defer client.Disconnect(context.Background()) store := mongodb.NewFromConnection(client, mongodb.Config{ Database: "fiber", Collection: "fiber_storage", }) defer store.Close() } ``` --- ## MSSQL ![Release](https://img.shields.io/github/v/tag/gofiber/storage?filter=mssql*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-mssql.yml?label=Tests) A MSSQL storage driver using [microsoft/go-mssqldb](https://github.com/microsoft/go-mssqldb). #### Table of Contents - [Signatures](#signatures) - [Installation](#installation) - [Examples](#examples) - [Config](#config) - [Default Config](#default-config) #### Signatures ```go func New(config ...Config) Storage func NewFromConnection(db *sql.DB, config ...Config) *Storage func (s *Storage) Get(key string) ([]byte, error) func (s *Storage) GetWithContext(ctx context.Context, key string) ([]byte, error) func (s *Storage) Set(key string, val []byte, exp time.Duration) error func (s *Storage) SetWithContext(ctx context.Context, key string, val []byte, exp time.Duration) error func (s *Storage) Delete(key string) error func (s *Storage) DeleteWithContext(ctx context.Context, key string) error func (s *Storage) Reset() error func (s *Storage) ResetWithContext(ctx context.Context) error func (s *Storage) Close() error func (s *Storage) Conn() *sql.DB ``` **Note:** Expirations are stored with a one-second granularity and rounded up, so an entry is never dropped before its expiration but may outlive it by up to a second. #### Installation MSSQL is tested on the 2 last [Go versions](https://golang.org/dl/) with support for modules. So make sure to initialize one first if you didn't do that yet: ```bash go mod init github.com// ``` And then install the mssql implementation: ```bash go get github.com/gofiber/storage/mssql/v2 ``` #### Examples Import the storage package. ```go import "github.com/gofiber/storage/mssql/v2" ``` You can use the following possibilities to create a storage: ```go // Initialize default config store := mssql.New() // Initialize custom config store := mssql.New(mssql.Config{ Host: "127.0.0.1", Port: 1433, Database: "fiber", Table: "fiber_storage", Reset: false, GCInterval: 10 * time.Second, SslMode: "disable", }) // Initialize custom config using connection string store := mssql.New(mssql.Config{ ConnectionURI: "sqlserver://user:password@localhost:1433?database=fiber" Reset: false, GCInterval: 10 * time.Second, }) ``` #### Config ```go // Config defines the config for storage. type Config struct { // Connection string to use for DB. Will override all other authentication values if used // // Optional. Default is "" ConnectionURI string // Host name where the DB is hosted // // Optional. Default is "127.0.0.1" Host string // Port where the DB is listening on // // Optional. Default is 1433 Port int // Server username // // Optional. Default is "" Username string // Server password // // Optional. Default is "" Password string // Instance name // // Optional. Default is "" Instance string // Database name // // Optional. Default is "fiber" Database string // Table name // // Optional. Default is "fiber_storage" Table string // Reset clears any existing keys in existing Table // // Optional. Default is false Reset bool // Time before deleting expired keys // // Optional. Default is 10 * time.Second GCInterval time.Duration // The SSL mode for the connection // // Optional. Default is "disable" SslMode string } ``` #### Default Config ```go var ConfigDefault = Config{ ConnectionURI: "", Host: "127.0.0.1", Port: 1433, Database: "fiber", Table: "fiber_storage", Reset: false, GCInterval: 10 * time.Second, SslMode: "disable", } ``` #### Using an Existing MSSQL Connection If your application already holds a `*sql.DB`, you can build the storage on it instead of opening a second pool. The remaining config options (table name, GC interval, reset) still apply. The handle stays yours to close: `Close` on a storage built this way stops the garbage collector but leaves the pool open, so the rest of your application keeps working. The storage itself is closed: any operation on it afterwards returns `ErrClosed`. ```go import ( "database/sql" "github.com/gofiber/storage/mssql/v2" _ "github.com/microsoft/go-mssqldb" ) func main() { db, err := sql.Open("sqlserver", "sqlserver://user:password@localhost:1433?database=fiber") if err != nil { panic(err) } defer db.Close() store := mssql.NewFromConnection(db, mssql.Config{ Table: "fiber_storage", }) defer store.Close() } ``` --- ## MySQL (Mysql) ![Release](https://img.shields.io/github/v/tag/gofiber/storage?filter=mysql*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-mysql.yml?label=Tests) A MySQL storage driver using `database/sql` and [go-sql-driver/mysql](https://github.com/go-sql-driver/mysql). #### Table of Contents - [Signatures](#signatures) - [Installation](#installation) - [Examples](#examples) - [Config](#config) - [Default Config](#default-config) #### Signatures ```go func New(config ...Config) Storage func NewFromConnection(db *sql.DB, config ...Config) *Storage func (s *Storage) Get(key string) ([]byte, error) func (s *Storage) GetWithContext(ctx context.Context, key string) ([]byte, error) func (s *Storage) Set(key string, val []byte, exp time.Duration) error func (s *Storage) SetWithContext(ctx context.Context, key string, val []byte, exp time.Duration) error func (s *Storage) Delete(key string) error func (s *Storage) DeleteWithContext(ctx context.Context, key string) error func (s *Storage) Reset() error func (s *Storage) ResetWithContext(ctx context.Context) error func (s *Storage) Close() error func (s *Storage) Conn() *sql.DB ``` **Note:** Expirations are stored with a one-second granularity and rounded up, so an entry is never dropped before its expiration but may outlive it by up to a second. #### Installation MySQL is tested on the 2 last [Go versions](https://golang.org/dl/) with support for modules. So make sure to initialize one first if you didn't do that yet: ```bash go mod init github.com// ``` And then install the mysql implementation: ```bash go get github.com/gofiber/storage/mysql/v2 ``` #### Examples Import the storage package. ```go import "github.com/gofiber/storage/mysql/v2" ``` You can use the following possibilities to create a storage: ```go // Initialize default config store := mysql.New() // Initialize custom config store := mysql.New(mysql.Config{ Host: "127.0.0.1", Port: 3306, Database: "fiber", Table: "fiber_storage", Reset: false, GCInterval: 10 * time.Second, }) // Initialize custom config using connection string store := mysql.New(mysql.Config{ ConnectionURI: ":@tcp(:)/" Reset: false, GCInterval: 10 * time.Second, }) // Initialize custom config using sql db connection db, _ := sql.Open("mysql", ":@tcp(:)/") store := mysql.New(mysql.Config{ Db: db, Reset: false, GCInterval: 10 * time.Second, }) ``` #### Config ```go type Config struct { // DB Will override ConnectionURI and all other authentication values if used // // Optional. Default is nil Db *sql.DB // Connection string to use for DB. Will override all other authentication values if used // // Optional. Default is "" ConnectionURI string // Host name where the DB is hosted // // Optional. Default is "127.0.0.1" Host string // Port where the DB is listening on // // Optional. Default is 3306 Port int // Server username // // Optional. Default is "" Username string // Server password // // Optional. Default is "" Password string // Database name // // Optional. Default is "fiber" Database string // Table name // // Optional. Default is "fiber_storage" Table string // Reset clears any existing keys in existing Table // // Optional. Default is false Reset bool // Time before deleting expired keys // // Optional. Default is 10 * time.Second GCInterval time.Duration } ``` #### Default Config ```go var ConfigDefault = Config{ ConnectionURI: "", Host: "127.0.0.1", Port: 3306, Database: "fiber", Table: "fiber_storage", Reset: false, GCInterval: 10 * time.Second, } ``` #### Using an Existing MySQL Connection If your application already holds a `*sql.DB`, you can build the storage on it instead of opening a second pool. This is the same as setting `Config.Db`. The handle stays yours to close: `Close` on a storage built this way stops the garbage collector but leaves the pool open, so the rest of your application keeps working. The storage itself is closed: any operation on it afterwards returns `ErrClosed`. ```go import ( "database/sql" _ "github.com/go-sql-driver/mysql" "github.com/gofiber/storage/mysql/v2" ) func main() { db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/fiber") if err != nil { panic(err) } defer db.Close() store := mysql.NewFromConnection(db, mysql.Config{ Table: "fiber_storage", }) defer store.Close() } ``` --- ## Nats ![Release](https://img.shields.io/github/v/tag/gofiber/storage?filter=nats*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-nats.yml?label=Tests) A NATS Key/Value storage driver. #### Table of Contents - [Signatures](#signatures) - [Installation](#installation) - [Examples](#examples) - [Config](#config) - [Default Config](#default-config) #### Signatures ```go func New(config ...Config) Storage func NewWithContext(ctx context.Context, config ...Config) *Storage func NewFromConnection(nc *nats.Conn, config ...Config) *Storage func NewFromConnectionWithContext(ctx context.Context, nc *nats.Conn, config ...Config) *Storage func (s *Storage) Get(key string) ([]byte, error) func (s *Storage) GetWithContext(ctx context.Context, key string) ([]byte, error) func (s *Storage) Set(key string, val []byte, exp time.Duration) error func (s *Storage) SetWithContext(ctx context.Context, key string, val []byte, exp time.Duration) error func (s *Storage) Delete(key string) error func (s *Storage) DeleteWithContext(ctx context.Context, key string) error func (s *Storage) Reset() error func (s *Storage) ResetWithContext(ctx context.Context) error func (s *Storage) Close() error func (s *Storage) Conn() (*nats.Conn, jetstream.KeyValue) func (s *Storage) Keys() ([]string, error) ``` **Note:** Expirations are stored with a one-second granularity and rounded up, so an entry is never dropped before its expiration but may outlive it by up to a second. #### Installation [NATS Key/Value Store](https://docs.nats.io/nats-concepts/jetstream/key-value-store) driver is tested on the 2 last [Go versions](https://golang.org/dl/) with support for modules. So make sure to initialize one first if you didn't do that yet: ```bash go mod init github.com// ``` And then install the nats implementation: ```bash go get github.com/gofiber/storage/nats ``` #### Examples Import the storage package. ```go import "github.com/gofiber/storage/nats" ``` You can use the following options to create a storage driver: ```go // Initialize default config store := nats.New() // Initialize custom config store := nats.New(Config{ URLs: "nats://127.0.0.1:4443", NatsOptions: []nats.Option{ nats.MaxReconnects(2), // Enable TLS by specifying RootCAs nats.RootCAs("./testdata/certs/ca.pem"), }, KeyValueConfig: jetstream.KeyValueConfig{ Bucket: "test", Storage: jetstream.MemoryStorage, }, }) ``` #### Config ```go type Config struct { // Nats URLs, default "nats://127.0.0.1:4222". Can be comma separated list for multiple servers URLs string // Nats connection options. See nats_test.go for an example of how to use this. NatsOptions []nats.Option // Nats connection name ClientName string // Nats context Context context.Context // Nats key value config KeyValueConfig jetstream.KeyValueConfig // Wait for connection to be established, default: 250ms WaitForConnection time.Duration // Reset clears any existing keys in existing bucket default: false Reset bool } ``` #### Default Config ```go var ConfigDefault = Config{ URLs: nats.DefaultURL, Context: context.Background(), ClientName: "fiber_storage", KeyValueConfig: jetstream.KeyValueConfig{ Bucket: "fiber_storage", }, WaitForConnection: 100 * time.Millisecond, } ``` #### Using an Existing NATS Connection If your application already holds a `*nats.Conn`, you can build the storage on it instead of connecting a second time. Only the `KeyValueConfig` and `Reset` options are read; the connection settings come from the connection. Your own connect and reconnect handlers are left alone. The connection stays yours to close: `Close` on a storage built this way leaves it open, so the rest of your application keeps working. Because no handlers are installed, the bucket is resolved lazily: an operation retries the setup when the bucket is still missing, and re-resolves it if its backing stream disappears. A bucket this driver created is recreated should it vanish; a bucket that already existed is only looked up again, so one your application configured — with its own history, TTL and replica settings — is never silently replaced by this driver's defaults. ```go import ( "github.com/gofiber/storage/nats" natsgo "github.com/nats-io/nats.go" "github.com/nats-io/nats.go/jetstream" ) func main() { nc, err := natsgo.Connect("nats://127.0.0.1:4222") if err != nil { panic(err) } defer nc.Close() store := nats.NewFromConnection(nc, nats.Config{ KeyValueConfig: jetstream.KeyValueConfig{ Bucket: "fiber_storage", }, }) defer store.Close() } ``` --- ## Neo4j (Neo4j) ![Release](https://img.shields.io/github/v/tag/gofiber/storage?filter=neo4j*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-neo4j.yml?label=Tests) A Neo4j storage driver using [neo4j/neo4j-go-driver](https://github.com/neo4j/neo4j-go-driver). > **Note: Requires latest two releases of Golang** #### Table of Contents - [Signatures](#signatures) - [Installation](#installation) - [Examples](#examples) - [Config](#config) - [Default Config](#default-config) #### Signatures ```go func New(config ...Config) *Storage func NewWithContext(ctx context.Context, config ...Config) *Storage func NewFromConnection(db neo4j.DriverWithContext, config ...Config) *Storage func NewFromConnectionWithContext(ctx context.Context, db neo4j.DriverWithContext, config ...Config) *Storage func (s *Storage) Get(key string) ([]byte, error) func (s *Storage) GetWithContext(ctx context.Context, key string) ([]byte, error) func (s *Storage) Set(key string, val []byte, exp time.Duration) error func (s *Storage) SetWithContext(ctx context.Context, key string, val []byte, exp time.Duration) error func (s *Storage) Delete(key string) error func (s *Storage) DeleteWithContext(ctx context.Context, key string) error func (s *Storage) Reset() error func (s *Storage) ResetWithContext(ctx context.Context) error func (s *Storage) Close() error func (s *Storage) Conn() neo4j.DriverWithContext ``` **Note:** Expirations are stored with a one-second granularity and rounded up, so an entry is never dropped before its expiration but may outlive it by up to a second. #### Installation Neo4j is tested on the 2 last [Go versions](https://golang.org/dl/) with support for modules. So make sure to initialize one first if you didn't do that yet: ```bash go mod init github.com// ``` And then install the neo4j implementation: ```bash go get github.com/gofiber/storage/neo4j ``` #### Examples Import the storage package. ```go import "github.com/gofiber/storage/neo4j" ``` You can use the following possibilities to create a storage: > The `neo4j` package name used in this example is the package name (and default import name) for this storage driver. Feel free import it with a custom name to avoid confusing it with the neo4j-go-driver package which also uses `neo4j` as package name (and default import name). ```go // Initialize default config store := neo4j.New() // Initialize custom config store := neo4j.New(neo4j.Config{ DB: driver, Node: "fiber_storage", Reset: false, GCInterval: 10 * time.Second, }) ``` #### Config > The `neo4j`, `auth`, and `config` package names used here belong to the neo4j-go-driver package. ```go // Config defines the config for storage. type Config struct { // Connection pool // // DB neo4j.DriverWithContext object will override connection URI and other connection fields. // // Optional. Default is nil. DB neo4j.DriverWithContext // Target Server // // Optional. Default is "neo4j://localhost" URI string // Connection authentication // // Auth auth.TokenManager will override Username and Password fields // // Optional. Default is nil. Auth auth.TokenManager // Connection configurations // // Optional. Default is nil Configurations []func(*config.Config) // Server username // // Optional. Default is "" Username string // Server password // // Optional. Default is "" Password string // Node name // // Optional. Default is "fiber_storage" Node string // Reset clears any existing keys (Nodes) // // Optional. Default is false Reset bool // Time before deleting expired keys (Nodes) // // Optional. Default is 10 * time.Second GCInterval time.Duration } ``` ##### A note on Authentication If auth is enabled on your server, then authentication must be provided in one of the three ways (the previous overrides the next): - Via the connection pool, `neo4j.DriverWithContext`, provided on the `DB` field. - Via the `Auth` field: it must be an `auth.TokenManager` whose value is any one but `neo4j.NoAuth()`. - By setting both `Username` and `Password` fields: This will cause this storage driver to use Basic Auth. Otherwise, your neo4j driver will panic with authorization error. In contrast, if auth is disabled on your server, there's no need to provide any authentication parameter. #### Default Config Used only for optional fields ```go var ConfigDefault = Config{ URI: "neo4j://localhost", Node: "fiber_storage", Reset: false, GCInterval: 10 * time.Second, } ``` #### Using an Existing Neo4j Connection If your application already holds a `neo4j.DriverWithContext`, you can build the storage on it instead of creating a second one. This is the same as setting `Config.DB`. The driver stays yours to close: `Close` on a storage built this way stops the garbage collector but leaves the driver open, so the rest of your application keeps working. The storage itself is closed: any operation on it afterwards returns `ErrClosed`. ```go import ( "context" storage "github.com/gofiber/storage/neo4j" "github.com/neo4j/neo4j-go-driver/v5/neo4j" ) func main() { driver, err := neo4j.NewDriverWithContext("neo4j://localhost:7687", neo4j.BasicAuth("neo4j", "password", "")) if err != nil { panic(err) } defer driver.Close(context.Background()) store := storage.NewFromConnection(driver, storage.Config{ Node: "fiber_storage", }) defer store.Close() } ``` --- ## Pebble ![Release](https://img.shields.io/github/v/tag/gofiber/storage?filter=pebble*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-pebble.yml?label=Tests) A fast key-value DB using [cockroachdb/pebble](https://github.com/cockroachdb/pebble) #### Table of Contents - [Signatures](#signatures) - [Installation](#installation) - [Examples](#examples) - [Config](#config) - [Default Config](#default-config) #### Signatures ```go func New(config ...Config) Storage func NewFromConnection(db *pebble.DB, config ...Config) *Storage func (s *Storage) Get(key string) ([]byte, error) func (s *Storage) GetWithContext(ctx context.Context, key string) ([]byte, error) func (s *Storage) Set(key string, val []byte, exp time.Duration) error func (s *Storage) SetWithContext(ctx context.Context, key string, val []byte, exp time.Duration) error func (s *Storage) Delete(key string) error func (s *Storage) DeleteWithContext(ctx context.Context, key string) error func (s *Storage) Reset() error func (s *Storage) ResetWithContext(ctx context.Context) error func (s *Storage) Close() error func (s *Storage) Conn() *pebble.DB ``` **Note:** Every method returns `ErrClosed` once `Close` has been called, rather than reaching into a closed database, which Pebble panics on. **Note:** `Reset` deletes in bounded chunks so that resetting a large database does not have to fit in memory. A reset that spans more than one chunk is therefore not atomic: a concurrent reader can observe the database part way through it. **Note:** Expiration is tracked with a one-second granularity, so an `exp` shorter than a second is rounded up to one second. Expired entries are reported as a miss immediately and reclaimed in the background on `GCInterval`; `Get` does not delete them, since Pebble has no compare-and-delete and doing so could drop a value a concurrent `Set` had just written. **Note:** `WriteOptions` defaults to `nil`, which Pebble reads as a synchronous write, so every `Set` and `Delete` is flushed to disk before it returns. Pass `&pebble.WriteOptions{}` to let Pebble buffer writes instead, which is far faster but loses the most recent writes if the process dies. **Note:** Pebble has no native context support, so the context methods run the operation to completion. They do honour a context that is already cancelled or past its deadline, returning the context error without touching the storage. #### Installation Pebble is tested on the 2 last [Go versions](https://golang.org/dl/) with support for modules. So make sure to initialize one first if you didn't do that yet: ```bash go mod init github.com// ``` Note: This step is only required if you don't have an existing module. And then install the Pebble implementation: ```bash go get github.com/gofiber/storage/pebble/v2 ``` #### Examples Import the storage package. ```go import "github.com/gofiber/storage/pebble/v2" ``` You can use the following possibilities to create a storage: ```go // Initialize default config store := pebble.New() // Initialize custom config store := pebble.New(pebble.Config{ Path: "db", WriteOptions: &pebble.WriteOptions{}, }) ``` #### Config ```go type Config struct { // Path is the directory the database is stored in. // // Optional. Default is "db" Path string // WriteOptions are the options every write is issued with. Pebble reads nil // as synchronous, so each Set and Delete is flushed before returning. Pass // &pebble.WriteOptions{} to buffer instead, losing recent writes on a crash. // // Optional. Default is nil. WriteOptions *pebble.WriteOptions // GCInterval is how often expired entries are reclaimed in the background. // // Optional. Default is 10 * time.Second GCInterval time.Duration } ``` #### Default Config ```go var ConfigDefault = Config{ Path: "db", WriteOptions: nil, GCInterval: 10 * time.Second, } ``` #### Using an Existing Pebble Database Pebble takes a directory lock, so an application that already keeps a `*pebble.DB` open cannot have the storage open the same path again. Pass the open database instead. Only the `WriteOptions` and `GCInterval` options are read. The database stays yours to close: `Close` on a storage built this way stops the garbage collector but leaves the database open, so the rest of your application keeps working. The storage itself is closed, and any operation on it afterwards returns `ErrClosed`. Storages built on the same database share one write-order lock and one collector cursor, so each one's collector coordinates with the others' writes and a `Reset` by any of them rewinds them all. Closing the storage that opened the database closes the database itself: the others then report `ErrClosed`, which matters here because Pebble answers use of a closed database with a panic. > **Warning:** the storage treats the whole keyspace as its own — `Reset` deletes every key in the database, and the background collector reclaims any value that looks like an expired entry. Keep application data out of a database backing this storage. ```go import ( "github.com/cockroachdb/pebble" pebblestorage "github.com/gofiber/storage/pebble/v2" ) func main() { db, err := pebble.Open("./fiber.pebble", &pebble.Options{}) if err != nil { panic(err) } defer db.Close() store := pebblestorage.NewFromConnection(db) defer store.Close() } ``` --- ## Postgres ![Release](https://img.shields.io/github/v/tag/gofiber/storage?filter=postgres*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-postgres.yml?label=Tests) A Postgres storage driver using [jackc/pgx](https://github.com/jackc/pgx). > **CockroachDB** is also supported. Since CockroachDB is wire-compatible with PostgreSQL, this driver works with CockroachDB without any changes. Simply point the connection string to your CockroachDB instance. #### Table of Contents - [Signatures](#signatures) - [Installation](#installation) - [Examples](#examples) - [Config](#config) - [Default Config](#default-config) #### Signatures ```go func New(config ...Config) Storage func NewWithContext(ctx context.Context, config ...Config) *Storage func NewFromConnection(db *pgxpool.Pool, config ...Config) *Storage func NewFromConnectionWithContext(ctx context.Context, db *pgxpool.Pool, config ...Config) *Storage func (s *Storage) Get(key string) ([]byte, error) func (s *Storage) GetWithContext(ctx context.Context, key string) ([]byte, error) func (s *Storage) Set(key string, val []byte, exp time.Duration) error func (s *Storage) SetWithContext(ctx context.Context, key string, val []byte, exp time.Duration) error func (s *Storage) Delete(key string) error func (s *Storage) DeleteWithContext(ctx context.Context, key string) error func (s *Storage) Reset() error func (s *Storage) ResetWithContext(ctx context.Context) error func (s *Storage) Close() error func (s *Storage) Conn() *pgxpool.Pool ``` **Note:** Expirations are stored with a one-second granularity and rounded up, so an entry is never dropped before its expiration but may outlive it by up to a second. #### Installation Postgres is tested on the 2 last [Go versions](https://golang.org/dl/) with support for modules. So make sure to initialize one first if you didn't do that yet: ```bash go mod init github.com// ``` And then install the postgres implementation: ```bash go get github.com/gofiber/storage/postgres/v3 ``` #### Examples Import the storage package. ```go import "github.com/gofiber/storage/postgres/v3" ``` You can use the following possibilities to create a storage: ```go // Initialize default config store := postgres.New() // Initialize custom config store := postgres.New(postgres.Config{ DB: dbPool, Table: "fiber_storage", Reset: false, Unlogged: false, GCInterval: 10 * time.Second, }) ``` #### Config ```go // Config defines the config for storage. type Config struct { // DB pgxpool.Pool object will override connection uri and other connection fields // // Optional. Default is nil DB *pgxpool.Pool // Connection string to use for DB. Will override all other authentication values if used // // Optional. Default is "" ConnectionURI string // Host name where the DB is hosted // // Optional. Default is "127.0.0.1" Host string // Port where the DB is listening on // // Optional. Default is 5432 Port int // Server username // // Optional. Default is "" Username string // Server password // // Optional. Default is "" Password string // Database name // // Optional. Default is "fiber" Database string // Table name // // Optional. Default is "fiber_storage" Table string // The SSL mode for the connection // // Optional. Default is "disable" SSLMode string // Reset clears any existing keys in existing Table // // Optional. Default is false Reset bool // Unlogged creates the table as UNLOGGED. // Applies only when the table is created. // // Optional. Default is false // Warning: UNLOGGED tables are not crash-safe and are not replicated. Unlogged bool // Time before deleting expired keys // // Optional. Default is 10 * time.Second GCInterval time.Duration } ``` #### Default Config ```go // ConfigDefault is the default config var ConfigDefault = Config{ ConnectionURI: "", Host: "127.0.0.1", Port: 5432, Database: "fiber", Table: "fiber_storage", SSLMode: "disable", Reset: false, Unlogged: false, GCInterval: 10 * time.Second, } ``` #### Using an Existing Postgres Connection If your application already holds a `*pgxpool.Pool`, you can build the storage on it instead of opening a second pool. This is the same as setting `Config.DB`. The pool stays yours to close: `Close` on a storage built this way stops the garbage collector but leaves the pool open, so the rest of your application keeps working. The storage itself is closed: any operation on it afterwards returns `ErrClosed`. ```go import ( "context" "github.com/gofiber/storage/postgres/v3" "github.com/jackc/pgx/v5/pgxpool" ) func main() { pool, err := pgxpool.New(context.Background(), "postgres://user:password@localhost:5432/fiber") if err != nil { panic(err) } defer pool.Close() store := postgres.NewFromConnection(pool, postgres.Config{ Table: "fiber_storage", }) defer store.Close() } ``` --- ## Redis ![Release](https://img.shields.io/github/v/tag/gofiber/storage?filter=redis*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-redis.yml?label=Tests) A Redis storage driver using [go-redis/redis](https://github.com/go-redis/redis). #### Table of Contents - [Signatures](#signatures) - [Installation](#installation) - [Examples](#examples) - [Config](#config) - [Default Config](#default-config) #### Signatures ```go func New(config ...Config) Storage func NewWithContext(ctx context.Context, config ...Config) *Storage func NewFromConnection(conn redis.UniversalClient, config ...Config) *Storage func NewFromConnectionWithContext(ctx context.Context, conn redis.UniversalClient, config ...Config) *Storage func (s *Storage) Get(key string) ([]byte, error) func (s *Storage) GetWithContext(ctx context.Context, key string) ([]byte, error) func (s *Storage) Set(key string, val []byte, exp time.Duration) error func (s *Storage) SetWithContext(ctx context.Context, key string, val []byte, exp time.Duration) error func (s *Storage) Delete(key string) error func (s *Storage) DeleteWithContext(ctx context.Context, key string) error func (s *Storage) Reset() error func (s *Storage) ResetWithContext(ctx context.Context) error func (s *Storage) Close() error func (s *Storage) Conn() redis.UniversalClient func (s *Storage) Keys() ([][]byte, error) ``` **Note:** An expiration at or below zero means no expiration, and clears any expiration the key already had. In particular a negative one is not passed through to go-redis, which would read it as its `KeepTTL` sentinel. #### Installation Redis is tested on the 2 last [Go versions](https://golang.org/dl/) with support for modules. So make sure to initialize one first if you didn't do that yet: > **Note:** You can also use [DragonflyDB](https://dragonflydb.io/) as a Redis replacement. > Since DragonflyDB is fully compatible with the Redis API, you can use it exactly like Redis **without any code changes**. > [Example](#example-using-dragonflydb) ```bash go mod init github.com// ``` And then install the redis implementation: ```bash go get github.com/gofiber/storage/redis/v3 ``` #### Examples Import the storage package. ```go import "github.com/gofiber/storage/redis/v3" ``` You can use the one of the following options to create a Redis Storage: ```go // Initialize default config store := redis.New() // Initialize custom config store := redis.New(redis.Config{ Host: "127.0.0.1", Port: 6379, Username: "", Password: "", Database: 0, Reset: false, TLSConfig: nil, PoolSize: 10 * runtime.GOMAXPROCS(0), }) // Initialize Redis Failover Client store := redis.New(redis.Config{ MasterName: "master-name", Addrs: []string{":6379"}, }) // Initialize Redis Cluster Client store := redis.New(redis.Config{ Addrs: []string{":6379", ":6380"}, }) // Initialize AWS ElastiCache Redis Cluster with Configuration Endpoint store := redis.New(redis.Config{ Addrs: []string{"cluster.xxxxx.cache.amazonaws.com:6379"}, IsClusterMode: true, }) // Create a client with support for TLS cer, err := tls.LoadX509KeyPair("./client.crt", "./client.key") if err != nil { log.Println(err) return } tlsCfg := &tls.Config{ MinVersion: tls.VersionTLS12, InsecureSkipVerify: true, Certificates: []tls.Certificate{cer}, } store = redis.New(redis.Config{ URL: "redis://:@127.0.0.1:6379/", TLSConfig: tlsCfg, Reset: false, }) // Create a client with a Redis URL with all information. store = redis.New(redis.Config{ URL: "redis://:@127.0.0.1:6379/", Reset: false, }) ``` #### Config ```go type Config struct { // Host name where the DB is hosted // // Optional. Default is "127.0.0.1" Host string // Port where the DB is listening on // // Optional. Default is 6379 Port int // Server username // // Optional. Default is "" Username string // Server password // // Optional. Default is "" Password string // Database to be selected after connecting to the server. // // Optional. Default is 0 Database int // URL standard format Redis URL. If this is set all other config options, Host, Port, Username, Password, Database have no effect. // // Example: redis://:@localhost:6379/ // Optional. Default is "" URL string // Either a single address or a seed list of host:port addresses, this enables FailoverClient and ClusterClient // // Optional. Default is []string{} Addrs []string // MasterName is the sentinel master's name // // Optional. Default is "" MasterName string // ClientName will execute the `CLIENT SETNAME ClientName` command for each conn. // // Optional. Default is "" ClientName string // SentinelUsername // // Optional. Default is "" SentinelUsername string // SentinelPassword // // Optional. Default is "" SentinelPassword string // Reset clears any existing keys in existing Collection // // Optional. Default is false Reset bool // TLS Config to use. When set TLS will be negotiated. // // Optional. Default is nil TLSConfig *tls.Config // Maximum number of socket connections. // // Optional. Default is 10 connections per every available CPU as reported by runtime.GOMAXPROCS. PoolSize int // IsClusterMode forces cluster mode even with single address. // Useful for AWS ElastiCache Configuration Endpoints. // // Optional. Default is false IsClusterMode bool // SkipConnectionCheck disables the PING New sends, so New makes no network // call and never panics on an unreachable server; errors surface on first // use. Reset is skipped too, being a network call. // // Optional. Default is false SkipConnectionCheck bool } ``` #### Default Config ```go var ConfigDefault = Config{ Host: "127.0.0.1", Port: 6379, Username: "", Password: "", URL: "", Database: 0, Reset: false, TLSConfig: nil, PoolSize: 10 * runtime.GOMAXPROCS(0), Addrs: []string{}, MasterName: "", ClientName: "", SentinelUsername: "", SentinelPassword: "", IsClusterMode: false, SkipConnectionCheck: false, } ``` #### Using an Existing Redis Connection If you already have a Redis client configured in your application, you can create a Storage instance directly from that client. This is useful when you want to share an existing connection throughout your application instead of creating a new one. Only `Reset` is read from the optional config; the connection settings come from the client. `NewFromConnectionWithContext` uses the given context for the optional reset. The client stays yours to close: `Close` on a storage built this way leaves the client open, so the rest of your application keeps working. The storage itself is closed, and any operation on it afterwards returns `ErrClosed`. > **Warning:** `Reset` runs `FLUSHDB` on every node the client reaches, deleting every key in those databases, not just this storage's entries. ```go import ( "github.com/gofiber/storage/redis" redigo "github.com/redis/go-redis/v9" "fmt" "context" ) func main() { // Create or reuse a Redis universal client (e.g., redis.NewClient, redis.NewClusterClient, etc.) client := redigo.NewUniversalClient(&redigo.UniversalOptions{ Addrs: []string{"127.0.0.1:6379"}, }) // Create a new Storage instance from the existing Redis client store := redis.NewFromConnection(client) // Set a value if err := store.Set("john", []byte("doe"), 0); err != nil { panic(err) } // Get the value val, err := store.Get("john") if err != nil { panic(err) } fmt.Println("Stored value:", string(val)) // Clean up store.Close() } ``` #### Example: Using DragonflyDB > **Note:** You can use [DragonflyDB](https://dragonflydb.io/) in the same way as Redis. > Simply start a DragonflyDB server and configure it just like Redis. Then, call `New()` and use it exactly as you would with Redis. --- ## Ristretto ![Release](https://img.shields.io/github/v/tag/gofiber/storage?filter=ristretto*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-ristretto.yml?label=Tests) A Memory-bound storage driver using [`dgraph-io/ristretto`](https://github.com/dgraph-io/ristretto). #### Table of Contents - [Signatures](#signatures) - [Installation](#installation) - [Examples](#examples) - [Config](#config) - [Default Config](#default-config) #### Signatures ```go func New(config ...Config) Storage func NewFromConnection(cache *ristretto.Cache, config ...Config) *Storage func (s *Storage) Get(key string) ([]byte, error) func (s *Storage) GetWithContext(ctx context.Context, key string) ([]byte, error) func (s *Storage) Set(key string, val []byte, exp time.Duration) error func (s *Storage) SetWithContext(ctx context.Context, key string, val []byte, exp time.Duration) error func (s *Storage) Delete(key string) error func (s *Storage) DeleteWithContext(ctx context.Context, key string) error func (s *Storage) Reset() error func (s *Storage) ResetWithContext(ctx context.Context) error func (s *Storage) Close() error func (s *Storage) Conn() *ristretto.Cache ``` **Note:** `Set` calls Ristretto's `Wait` before returning, so a `Get` that follows a `Set` no longer races the cache's background writer. `Wait` drains the write buffer; it does not guarantee the entry was admitted. Ristretto is a cache with an admission policy, so an entry may be rejected on write or evicted later and a `Get` can still report a miss — read-after-write here is best-effort, not a guarantee, and this driver should not be used where a value must be readable once stored. Set `SkipWaitForWrite: true` to keep Ristretto's buffered write behaviour instead, which is roughly 3x faster on `Set` but makes a `Get` straight after a `Set` racy. **Note:** Ristretto has no native context support, so the context methods run the operation to completion. They do honour a context that is already cancelled or past its deadline, returning the context error without touching the storage. #### Installation Ristretto is tested on the 2 last [Go versions](https://golang.org/dl/) with support for modules. So make sure to initialize one first if you didn't do that yet: ```bash go mod init github.com// ``` And then install the ristretto implementation: ```bash go get github.com/gofiber/storage/ristretto/v2 ``` #### Examples Import the storage package. ```go import "github.com/gofiber/storage/ristretto/v2" ``` You can use the following possibilities to create a storage: ```go // Initialize default config store := ristretto.New() // Initialize custom config store := ristretto.New(ristretto.Config{ NumCounters: 1e7, // number of keys to track frequency of (10M). MaxCost: 1 << 30, // maximum cost of cache (1GB). BufferItems: 64, // number of keys per Get buffer. }) ``` #### Config ```go type Config struct { // NumCounters number of keys to track frequency of (10M). NumCounters int64 // MaxCost maximum cost of cache (1GB). MaxCost int64 // BufferItems number of keys per Get buffer. BufferItems int64 DefaultCost int64 // SkipWaitForWrite makes Set return as soon as the write is buffered. It is // faster, and Ristretto's own design, but a Get straight after a Set can // miss. Leave it off for the read-after-write the storage interface implies. // // Optional. Default is false SkipWaitForWrite bool } ``` #### Default Config ```go var ConfigDefault = Config{ NumCounters: 1e7, MaxCost: 1 << 30, BufferItems: 64, DefaultCost: 1, } ``` #### Using an Existing Ristretto Cache If your application already holds a `*ristretto.Cache`, you can build the storage on it instead of creating a second one. Only `DefaultCost` and `SkipWaitForWrite` are read; the sizing options come from the cache. The cache stays yours to close: `Close` on a storage built this way leaves it open, so the rest of your application keeps working. Storages built on the same cache share one operation lock, so each one's `Reset` excludes the others' operations the same way it excludes its own. Closing the storage that created the cache closes the cache itself: the others then report `ErrClosed`, since a closed Ristretto cache drops operations in silence rather than reporting them. > **Warning:** `Reset` clears the entire shared cache, not just this storage's entries. ```go import ( "github.com/dgraph-io/ristretto" ristrettostorage "github.com/gofiber/storage/ristretto/v2" ) func main() { cache, err := ristretto.NewCache(&ristretto.Config{ NumCounters: 1e7, MaxCost: 1 << 30, BufferItems: 64, }) if err != nil { panic(err) } defer cache.Close() store := ristrettostorage.NewFromConnection(cache) defer store.Close() } ``` --- ## Rueidis ![Release](https://img.shields.io/github/v/tag/gofiber/storage?filter=rueidis*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-rueidis.yml?label=Tests) A fast Redis Storage that does auto pipelining and supports client side caching. [redis/rueidis](https://github.com/redis/rueidis). #### Table of Contents - [Signatures](#signatures) - [Installation](#installation) - [Examples](#examples) - [Config](#config) - [Default Config](#default-config) #### Signatures ```go func New(config ...Config) Storage func NewWithContext(ctx context.Context, config ...Config) *Storage func NewFromConnection(conn rueidis.Client, config ...Config) *Storage func NewFromConnectionWithContext(ctx context.Context, conn rueidis.Client, config ...Config) *Storage func (s *Storage) Get(key string) ([]byte, error) func (s *Storage) GetWithContext(ctx context.Context, key string) ([]byte, error) func (s *Storage) Set(key string, val []byte, exp time.Duration) error func (s *Storage) SetWithContext(ctx context.Context, key string, val []byte, exp time.Duration) error func (s *Storage) Delete(key string) error func (s *Storage) DeleteWithContext(ctx context.Context, key string) error func (s *Storage) Reset() error func (s *Storage) ResetWithContext(ctx context.Context) error func (s *Storage) Close() error func (s *Storage) Conn() rueidis.Client ``` **Note:** `AlwaysPipelining` defaults to true and this config reads a zero value as "not set", so setting it to false has no effect. Use `DisableAlwaysPipelining: true` to turn it off, the same way `DisableRetry` and `DisableCache` work. #### Installation Rueidis is tested on the latest [Go version](https://golang.org/dl/) with support for modules. So make sure to initialize one first if you didn't do that yet: ```bash go mod init github.com// ``` And then install the rueidis implementation: ```bash go get github.com/gofiber/storage/rueidis ``` #### Examples Import the storage package. ```go import "github.com/gofiber/storage/rueidis" ``` You can use the one of the following options to create a Rueidis Storage: ```go // Initialize default config (localhost:6379) store := rueidis.New() // Initialize custom config store := rueidis.New(rueidis.Config{ InitAddress: []string{"localhost:6380"}, Username: "", Password: "", Database: 0, Reset: false, TLSConfig: nil, }) // Initialize using Rueidis URL store := rueidis.New(rueidis.Config{ URL: "redis://localhost:6379", }) // Initialize Rueidis Cluster Client store := rueidis.New(rueidis.Config{ InitAddress: []string{":6379", ":6380"}, }) // Create a client with support for TLS cer, err := tls.LoadX509KeyPair("./client.crt", "./client.key") if err != nil { log.Println(err) return } tlsCfg := &tls.Config{ MinVersion: tls.VersionTLS12, InsecureSkipVerify: true, Certificates: []tls.Certificate{cer}, } store = rueidis.New(rueidis.Config{ InitAddress: []string{"localhost:6380"}, Username: "", Password: "", SelectDB: 0, TLSConfig: tlsCfg, }) ``` #### Config ```go type Config struct { // Server username // // Optional. Default is "" Username string // Server password // // Optional. Default is "" Password string // ClientName will execute the `CLIENT SETNAME ClientName` command for each conn. // // Optional. Default is "" ClientName string // URL standard format Redis URL. If this is set all other config options, InitAddress, Username, Password, ClientName, and SelectDB have no effect. // // Example: redis://:@localhost:6379/ // Optional. Default is "" URL string // SelectDB to be selected after connecting to the server. // // Optional. Default is 0 SelectDB int // Either a single address or a seed list of host:port addresses, this enables FailoverClient and ClusterClient // // Optional. Default is []string{"127.0.0.1:6379"} InitAddress []string // TLS Config to use. When set TLS will be negotiated. // // Optional. Default is nil TLSConfig *tls.Config // CacheSizeEachConn is redis client side cache size that bind to each TCP connection to a single redis instance. // // Optional. The default is DefaultCacheBytes: 128 * (1 << 20) CacheSizeEachConn int // RingScaleEachConn sets the size of the ring buffer in each connection to (2 ^ RingScaleEachConn). // // Optional. The default is RingScaleEachConn, which results into having a ring of size 2^10 for each connection. RingScaleEachConn int // ReadBufferEachConn is the size of the bufio.NewReaderSize for each connection, default to DefaultReadBuffer (0.5 MiB). // // Optional. The default is DefaultReadBuffer: 1 << 19 ReadBufferEachConn int // WriteBufferEachConn is the size of the bufio.NewWriterSize for each connection, default to DefaultWriteBuffer (0.5 MiB). // // Optional. The default is DefaultWriteBuffer: 1 << 19 WriteBufferEachConn int // BlockingPoolSize is the size of the connection pool shared by blocking commands (ex BLPOP, XREAD with BLOCK). // // Optional. The default is DefaultPoolSize: 1000 BlockingPoolSize int // PipelineMultiplex determines how many tcp connections used to pipeline commands to one redis instance. // // Optional. The default for single and sentinel clients is 2, which means 4 connections (2^2). PipelineMultiplex int // DisableRetry disables retrying read-only commands under network errors // // Optional. The default is False DisableRetry bool // DisableCache falls back Client.DoCache/Client.DoMultiCache to Client.Do/Client.DoMulti // // Optional. The default is false DisableCache bool // AlwaysPipelining makes rueidis.Client always pipeline redis commands even if they are not issued concurrently. // // Optional. The default is true AlwaysPipelining bool // DisableAlwaysPipelining turns AlwaysPipelining off. That field defaults to // true and a zero value reads as "not set", so setting it false does nothing; // this is the way to disable it, matching DisableRetry and DisableCache. // // Optional. Default is false DisableAlwaysPipelining bool // Reset clears any existing keys in existing Collection // // Optional. Default is false Reset bool // CacheTTL TTL // // Optional. Default is time.Minute CacheTTL time.Duration } ``` #### Default Config ```go var ConfigDefault = Config{ Username: "", Password: "", ClientName: "", SelectDB: 0, InitAddress: []string{"127.0.0.1:6379"}, TLSConfig: nil, CacheSizeEachConn: rueidis.DefaultCacheBytes, RingScaleEachConn: rueidis.DefaultRingScale, ReadBufferEachConn: rueidis.DefaultReadBuffer, WriteBufferEachConn: rueidis.DefaultWriteBuffer, BlockingPoolSize: rueidis.DefaultPoolSize, PipelineMultiplex: 2, DisableRetry: false, DisableCache: false, AlwaysPipelining: true, DisableAlwaysPipelining: false, Reset: false, CacheTTL: time.Minute, } ``` #### Using an Existing Rueidis Connection If your application already holds a `rueidis.Client`, you can build the storage on it instead of creating a second one. Only `CacheTTL` and `Reset` are read from the config; the connection settings come from the client, and nothing touches the network unless `Reset` is set. The client stays yours to close: `Close` on a storage built this way leaves it open, so the rest of your application keeps working. The storage itself is closed, and any operation on it afterwards returns `ErrClosed`. > **Warning:** `Reset` — the config option and the method — issues `FLUSHDB` on the client's database, deleting every key in it, not just this storage's entries. ```go import ( "github.com/gofiber/storage/rueidis" rueidislib "github.com/redis/rueidis" ) func main() { client, err := rueidislib.NewClient(rueidislib.ClientOption{ InitAddress: []string{"127.0.0.1:6379"}, }) if err != nil { panic(err) } defer client.Close() store := rueidis.NewFromConnection(client) defer store.Close() } ``` --- ## S3 ![Release](https://img.shields.io/github/v/tag/gofiber/storage?filter=s3*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-s3.yml?label=Tests) A S3 storage driver using [aws/aws-sdk-go-v2](https://github.com/aws/aws-sdk-go-v2). **Note:** If config fields of credentials not given, credentials are using from the environment variables, ~/.aws/credentials, or EC2 instance role. If config fields of credentials given, credentials are using from config. Look at: [specifying credentials](https://aws.github.io/aws-sdk-go-v2/docs/configuring-sdk/#specifying-credentials) #### Table of Contents - [Signatures](#signatures) - [Installation](#installation) - [Examples](#examples) - [Config](#config) - [Default Config](#default-config) #### Signatures ```go func New(config ...Config) Storage func NewFromConnection(svc *s3.Client, config ...Config) *Storage func (s *Storage) Get(key string) ([]byte, error) func (s *Storage) GetWithContext(ctx context.Context, key string) ([]byte, error) func (s *Storage) Set(key string, val []byte, exp time.Duration) error func (s *Storage) SetWithContext(ctx context.Context, key string, val []byte, exp time.Duration) error func (s *Storage) Delete(key string) error func (s *Storage) DeleteWithContext(ctx context.Context, key string) error func (s *Storage) Reset() error func (s *Storage) ResetWithContext(ctx context.Context) error func (s *Storage) Close() error func (s *Storage) Conn() *s3.Client // Additional useful methods. func (s *Storage) CreateBucket(bucket string) error func (s *Storage) DeleteBucket(bucket string) error func (s *Storage) DeleteMany(keys ...string) error func (s *Storage) SetWithChecksum(key string, val []byte, checksum map[types.ChecksumAlgorithm][]byte) error ``` #### Installation S3 is tested on the 2 last [Go versions](https://golang.org/dl/) with support for modules. So make sure to initialize one first if you didn't do that yet: ```bash go mod init github.com// ``` And then install the s3 implementation: ```bash go get github.com/gofiber/storage/s3/v2 ``` #### Examples Import the storage package. ```go import "github.com/gofiber/storage/s3/v2" ``` You can use the following possibilities to create a storage: ```go // Initialize default config store := s3.New() // Initialize custom config store := s3.New(s3.Config{ Bucket: "my-bucket-url", Endpoint: "my-endpoint", Region: "my-region", Reset: false, }) ``` Create an object with `Set()`: ```go err := store.Set("my-key", []byte("my-value")) ``` Or, call `SetWithChecksum()` to create an object with checksum to ask S3 server to verify data integrity on server side: > Currently 4 algorithms are supported: > - types.ChecksumAlgorithmCrc32 (`CRC32`) > - types.ChecksumAlgorithmCrc32c (`CRC32C`) > - types.ChecksumAlgorithmSha1 (`SHA1`) > - types.ChecksumAlgorithmSha256 (`SHA256`) > > For more information, see [PutObjectInput](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/s3#PutObjectInput). ```go key := "my-key" val := []byte("my-value") hash := sha256.New() hash.Write(val) sha256sum := hash.Sum(nil) // import "github.com/aws/aws-sdk-go-v2/service/s3/types" checksum = map[types.ChecksumAlgorithm][]byte{ types.ChecksumAlgorithmSha256: sha256sum, } err := store.SetWithChecksum(key, val, checksum) ``` #### Config ```go // Config defines the config for storage. type Config struct { // S3 bucket name Bucket string // AWS endpoint Endpoint string // AWS region Region string // Request timeout // // Optional. Default is 0 (no timeout) RequestTimeout time.Duration // Reset clears any existing keys in existing Bucket // // Optional. Default is false Reset bool // Credentials overrides AWS access key and AWS secret access key. Not recommended. // // Optional. Default is Credentials{} Credentials Credentials // The maximum number of times requests that encounter retryable failures should be attempted. // // Optional. Default is 3 MaxAttempts int } type Credentials struct { AccessKey string SecretAccessKey string } ``` #### Default Config The default configuration lacks Bucket, Region, and Endpoint which are all required and must be overwritten: ```go // ConfigDefault is the default config var ConfigDefault = Config{ Bucket: "", Region: "", Endpoint: "", Credentials: Credentials{}, MaxAttempts: 3, RequestTimeout: 0, Reset: false, } ``` #### Using an Existing S3 Client If your application already holds an `*s3.Client`, you can build the storage on it instead of creating a second one. Only the `Bucket`, `RequestTimeout` and `Reset` options are read; the endpoint and credentials come from the client. The client stays yours to manage: `Close` on a storage built this way is a no-op, so the rest of your application keeps working. ```go import ( "context" awsconfig "github.com/aws/aws-sdk-go-v2/config" awss3 "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/gofiber/storage/s3/v2" ) func main() { awscfg, err := awsconfig.LoadDefaultConfig(context.Background()) if err != nil { panic(err) } client := awss3.NewFromConfig(awscfg) store := s3.NewFromConnection(client, s3.Config{ Bucket: "my-bucket", }) defer store.Close() } ``` --- ## ScyllaDb ![Release](https://img.shields.io/github/v/tag/gofiber/storage?filter=scylladb*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-scylladb.yml?label=Tests) A ScyllaDb storage engine for [Fiber](https://github.com/gofiber/fiber) using [gocql](https://github.com/scylladb/gocql). #### Table of Contents - [Signatures](#signatures) - [Installation](#installation) - [Examples](#examples) - [Config](#config) - [Default Config](#default-config) #### Signatures ```go func New(config ...Config) Storage func NewFromConnection(session *gocql.Session, config ...Config) *Storage func (s *Storage) GetWithContext(ctx context.Context, key string) ([]byte, error) func (s *Storage) Get(key string) ([]byte, error) func (s *Storage) SetWithContext(ctx context.Context, key string, val []byte, exp time.Duration) error func (s *Storage) Set(key string, value []byte, expire time.Duration) error func (s *Storage) DeleteWithContext(ctx context.Context, key string) error func (s *Storage) Delete(key string) error func (s *Storage) ResetWithContext(ctx context.Context) error func (s *Storage) Reset() error func (s *Storage) Close() error func (s *Storage) Conn() *gocql.Session ``` #### Installation ScyllaDb is tested on the 2 last [Go versions](https://golang.org/dl/) with support for modules. So make sure to initialize one first if you didn't do that yet: ```bash go mod init github.com// ``` And then install the scylladb implementation: ```bash go get github.com/gofiber/storage/scylladb ``` #### Examples Import the storage package. ```go import "github.com/gofiber/storage/scylladb" ``` You can use the following possibilities to create a storage: ```go // Initialize default config store := scylladb.New() // Initialize custom config store := scylladb.New(scylladb.Config{ Keyspace: "fiber", Hosts: []string{"127.0.0.1"}, Port: 9042, Table: "fiber_storage", Consistency: "ONE", Reset: false, }) // Initialize with support for TLS (SslOptions configures TLS use) // // InsecureSkipVerify and EnableHostVerification interact as follows: // // |Config.InsecureSkipVerify | EnableHostVerification | Result | // |--------------------------|------------------------|--------------------| // |Config is nil | false | do not verify host | // |Config is nil | true | verify host | // |false | false | verify host | // |true | false | do not verify host | // |false | true | verify host | // |true | true | verify host | store := New( Config{ Keyspace: "fiber", Hosts: []string{"127.0.0.1"}, Port: 9042, Table: "fiber_storage", Consistency: "ONE", SslOpts: &gocql.SslOptions{ Config: &tls.Config{ InsecureSkipVerify: false, // Set this too false to enable certificate verification }, CertPath: "/path/to/client_cert.pem", // Path to the client certificate KeyPath: "/path/to/client_key.pem", // Path to the client certificate's private key CaPath: "/path/to/ca_cert.pem", // Path to the CA certificate EnableHostVerification: true, // Enable hostname verification }, Reset: false, }, ) // Initialize custom config using scylladb connection cluster, _ := gocql.NewCluster("127.0.0.1") cluster.Keyspace = "fiber" cluster.Port = 9042 session, _ := cluster.CreateSession() store := scylladb.New(scylladb.Config{ Session: session, Keyspace: "fiber", Table: "fiber_storage", Reset: false, }) ``` #### Config ```go type Config struct { // Session is provided by the user to use an existing ScyllaDb session // Session Will override Keyspace and all other authentication values if used // // Optional. Default is nil Session *gocql.Session // Keyspace name // // Optional. Default is "fiber" Keyspace string // Hosts are an array of network addresses for establishing initial connections // You have the flexibility to specify one or multiple addresses as needed // // Optional. Default is "127.0.0.1" Hosts []string // Port where the ScyllaDb cluster is listening on // // Optional. Default is 9042 Port int // Username for ScyllaDb cluster // // Optional. Default is "" Username string // Password for ScyllaDb cluster // // Optional. Default is "" Password string // Table name // // Optional. Default is "fiber_storage" Table string // Level of the consistency // // Optional. Default is "LOCAL_ONE" Consistency string // SslOpts configures TLS use. // // Optional. Default is nil SslOpts *gocql.SslOptions // Reset clears any existing keys in existing Table // // Optional. Default is false Reset bool // DisableInitialHostLookup disables the initial host lookup // // Optional. Default is false DisableInitialHostLookup bool } ``` #### Default Config ```go // ConfigDefault is the default config var ConfigDefault = Config{ Session: nil, Keyspace: "fiber", Hosts: []string{"127.0.0.1"}, Username: "", Password: "", Port: 9042, Table: "fiber_storage", Consistency: "LOCAL_ONE", SslOpts: nil, Reset: false, DisableInitialHostLookup: false, } ``` #### Using an Existing ScyllaDB Session If your application already holds a `*gocql.Session`, you can build the storage on it instead of creating a second one. This is the same as setting `Config.Session`. The session stays yours to close: `Close` on a storage built this way leaves it open, so the rest of your application keeps working. The storage itself is closed: any operation on it afterwards returns `ErrClosed`. ```go import ( "github.com/gocql/gocql" "github.com/gofiber/storage/scylladb" ) func main() { cluster := gocql.NewCluster("localhost:9042") session, err := cluster.CreateSession() if err != nil { panic(err) } defer session.Close() store := scylladb.NewFromConnection(session, scylladb.Config{ Keyspace: "fiber", Table: "fiber_storage", }) defer store.Close() } ``` --- ## SQLite3 ![Release](https://img.shields.io/github/v/tag/gofiber/storage?filter=sqlite3*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-sqlite3.yml?label=Tests) A SQLite3 storage driver using [mattn/go-sqlite3](https://github.com/mattn/go-sqlite3). #### Table of Contents - [Signatures](#signatures) - [Installation](#installation) - [Examples](#examples) - [Config](#config) - [Default Config](#default-config) #### Signatures ```go func New(config ...Config) Storage func NewFromConnection(db *sql.DB, config ...Config) *Storage func (s *Storage) Get(key string) ([]byte, error) func (s *Storage) GetWithContext(ctx context.Context, key string) ([]byte, error) func (s *Storage) Set(key string, val []byte, exp time.Duration) error func (s *Storage) SetWithContext(ctx context.Context, key string, val []byte, exp time.Duration) error func (s *Storage) Delete(key string) error func (s *Storage) DeleteWithContext(ctx context.Context, key string) error func (s *Storage) Reset() error func (s *Storage) ResetWithContext(ctx context.Context) error func (s *Storage) Close() error func (s *Storage) Conn() *sql.DB ``` **Note:** Expirations are stored with a one-second granularity and rounded up, so an entry is never dropped before its expiration but may outlive it by up to a second. #### Installation SQLite3 is tested on the 2 last [Go versions](https://golang.org/dl/) with support for modules. So make sure to initialize one first if you didn't do that yet: ```bash go mod init github.com// ``` And then install the sqlite3 implementation: ```bash go get github.com/gofiber/storage/sqlite3/v2 ``` #### Examples Import the storage package. ```go import "github.com/gofiber/storage/sqlite3/v2" ``` You can use the following possibilities to create a storage: ```go // Initialize default config store := sqlite3.New() // Initialize custom config store := sqlite3.New(sqlite3.Config{ Database: "./fiber.sqlite3", Table: "fiber_storage", Reset: false, GCInterval: 10 * time.Second, MaxOpenConns: 100, MaxIdleConns: 100, ConnMaxLifetime: 1 * time.Second, }) ``` #### Config ```go type Config struct { // Database name // // Optional. Default is "fiber" Database string // Table name // // Optional. Default is "fiber_storage" Table string // Reset clears any existing keys in existing Table // // Optional. Default is false Reset bool // Time before deleting expired keys // // Optional. Default is 10 * time.Second GCInterval time.Duration // ////////////////////////////////// // Adaptor related config options // // ////////////////////////////////// // MaxIdleConns sets the maximum number of connections in the idle connection pool. // // Optional. Default is 100. MaxIdleConns int // MaxOpenConns sets the maximum number of open connections to the database. // // Optional. Default is 100. MaxOpenConns int // ConnMaxLifetime sets the maximum amount of time a connection may be reused. // // Optional. Default is 1 second. ConnMaxLifetime time.Duration } ``` #### Default Config ```go var ConfigDefault = Config{ Database: "./fiber.sqlite3", Table: "fiber_storage", Reset: false, GCInterval: 10 * time.Second, MaxOpenConns: 100, MaxIdleConns: 100, ConnMaxLifetime: 1 * time.Second, } ``` #### Using an Existing SQLite3 Connection If your application already holds a `*sql.DB`, you can build the storage on it instead of opening a second handle. The remaining config options (table name, GC interval, reset) still apply. The handle stays yours to close: `Close` on a storage built this way stops the garbage collector but leaves the database open, so the rest of your application keeps working. The storage itself is closed: any operation on it afterwards returns `ErrClosed`. ```go import ( "database/sql" "github.com/gofiber/storage/sqlite3/v2" _ "github.com/mattn/go-sqlite3" ) func main() { db, err := sql.Open("sqlite3", "./fiber.sqlite3") if err != nil { panic(err) } defer db.Close() store := sqlite3.NewFromConnection(db, sqlite3.Config{ Table: "fiber_storage", }) defer store.Close() } ``` --- ## SurrealDB ![Release](https://img.shields.io/github/v/tag/gofiber/storage?filter=surrealdb*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-surrealdb.yml?label=Tests) #### Table of Contents - [Signatures](#signatures) - [Installation](#installation) - [Examples](#examples) - [Config](#config) - [Default Config](#default-config) #### Signatures ```go func New(config ...Config) *Storage func NewWithContext(ctx context.Context, config ...Config) *Storage func NewFromConnection(db *surrealdb.DB, config ...Config) *Storage func (s *Storage) Get(key string) ([]byte, error) func (s *Storage) GetWithContext(ctx context.Context, key string) ([]byte, error) func (s *Storage) Set(key string, val []byte, exp time.Duration) error func (s *Storage) SetWithContext(ctx context.Context, key string, val []byte, exp time.Duration) error func (s *Storage) Delete(key string) error func (s *Storage) DeleteWithContext(ctx context.Context, key string) error func (s *Storage) Reset() error func (s *Storage) ResetWithContext(ctx context.Context) error func (s *Storage) Close() error func (s *Storage) Conn() *surrealdb.DB func (s *Storage) List() ([]byte, error) { ``` **Note:** The context methods pass the context through to the SurrealDB client, so a cancelled context or an expired deadline aborts the query. **Note:** Expirations are stored with a one-second granularity and rounded up, so an entry is never dropped before its expiration but may outlive it by up to a second. #### Installation SurrealDB is tested on latest two version of Golang. Make sure to initialize a Go module first if you haven’t already: ```bash go get github.com/gofiber/storage/surrealdb ``` #### Examples Import the storage package. ```go import "github.com/gofiber/storage/surrealdb" ``` You can use the following possibilities to create a storage: ```go // Initialize default config store := surrealdb.New() // Initialize SurrealDB storage with custom config store := surrealdb.New(Config{ ConnectionString: "ws://localhost:8000", Namespace: "fiber_storage", Database: "fiber_storage", Username: "root", Password: "root", Access: "full", Scope: "all", DefaultTable: "fiber_storage", GCInterval: time.Second * 10, }) ``` #### Config ```go type Config struct { // The connection URL to connect to SurrealDB ConnectionString string // The namespace to be used in SurrealDB Namespace string // The database to be used within the specified namespace Database string // The application username to connect to SurrealDB Username string // The application password to connect to SurrealDB Password string // Optional access token or access type Access string // Optional scope for scoped logins (e.g., user-defined scopes) Scope string // The default table used to store key-value records DefaultTable string // Optional. Default is 10 * time.Second GCInterval time.Duration } ``` #### Default Config ```go // ConfigDefault is the default config var ConfigDefault = Config{ ConnectionString: "ws://localhost:8000", Namespace: "fiber_storage", Database: "fiber_storage", Username: "root", Password: "root", Access: "full", Scope: "all", DefaultTable: "fiber_storage", GCInterval: time.Second * 10, } ``` #### Using an Existing SurrealDB Connection If your application already holds a `*surrealdb.DB`, you can build the storage on it instead of connecting a second time. Selecting the namespace and database and signing in stay yours to do; only the `DefaultTable` and `GCInterval` options are read. The connection stays yours to close: `Close` on a storage built this way stops the garbage collector but leaves the connection open, so the rest of your application keeps working. The storage itself is closed: any operation on it afterwards returns `ErrClosed`. ```go import ( "context" storage "github.com/gofiber/storage/surrealdb" "github.com/surrealdb/surrealdb.go" ) func main() { ctx := context.Background() db, err := surrealdb.FromEndpointURLString(ctx, "ws://localhost:8000") if err != nil { panic(err) } defer db.Close(ctx) if err := db.Use(ctx, "fiber", "fiber"); err != nil { panic(err) } token, err := db.SignIn(ctx, &surrealdb.Auth{Username: "root", Password: "root"}) if err != nil { panic(err) } if err := db.Authenticate(ctx, token); err != nil { panic(err) } store := storage.NewFromConnection(db, storage.Config{ DefaultTable: "fiber_storage", }) defer store.Close() } ``` --- ## Test Helpers This directory hosts reusable utilities for exercising storage implementations in end-to-end and compatibility testing scenarios. Each helper is maintained as its own Go module so it can be consumed independently. ### Available helpers - **Redis** (`testhelpers/redis`): Spins up Redis with Testcontainers and exposes convenience functions for running integration tests against Redis-backed storage implementations. - **Test Compatibility Kit (TCK)** (`testhelpers/tck`): Provides a reusable test suite that validates any `storage.Storage` implementation for correctness and API parity. ### Running tests locally From the repository root, run tests for a helper by changing into its directory and executing `go test`: ```sh cd testhelpers/redis go test ./... -v -race ``` The helpers rely on Docker via [testcontainers-go](https://github.com/testcontainers/testcontainers-go), so ensure Docker is available and running before executing the tests. --- ## Redis Test Helper This module provides utilities for starting a disposable Redis instance with [testcontainers-go](https://github.com/testcontainers/testcontainers-go). It is useful for integration tests against storage implementations that rely on Redis. ### Features - Starts Redis containers with optional TLS, host/port, address, or URL connection helpers. - Supports container reuse via `WithReuse` for faster local iteration. - Exposes connection details (URL, host/port, addresses, TLS config) through the returned `Container` struct. ### Usage Import the helper and start a Redis container in your tests: ```go import ( testredis "github.com/gofiber/storage/testhelpers/redis" ) func TestExample(t *testing.T) { ctr := testredis.Start(t, "redis:7-alpine") // Use ctr.URL, ctr.Host/Port, or ctr.TLSConfig in your test code. } ``` ### Running locally From the repository root, execute the helper's tests: ```sh cd testhelpers/redis go test ./... -v -race ``` Docker must be available and running for the tests to start Redis containers. --- ## Test Compatibility Kit (TCK) for Storage Implementations The Test Compatibility Kit (TCK) is a standardized test suite for validating storage implementations in the Fiber Storage repository. It provides a comprehensive set of tests that ensure all storage backends behave consistently and correctly implement the `storage.Storage` interface. ### Overview The TCK leverages [testify/suite](https://github.com/stretchr/testify#suite-package) to provide a structured testing approach with setup/teardown hooks and consistent test execution. It automatically tests all core storage operations including: - Basic CRUD operations (Set, Get, Delete) - Context-aware operations (SetWithContext, GetWithContext, etc.) - TTL (Time-To-Live) functionality - Storage reset and cleanup - Connection handling for stores that implement `StorageWithConn` ### Why Use the TCK? - **Consistency**: Ensures all storage implementations behave identically - **Completeness**: Tests all required storage interface methods - **Maintenance**: Reduces test code duplication across storage implementations - **Quality**: Provides comprehensive edge case and error condition testing - **Integration**: Works seamlessly with testcontainers for isolated testing ### Core Concepts #### TCKSuite Interface To use the TCK, you must implement the `TCKSuite` interface: ```go // TCKSuite is the interface that must be implemented by the test suite. // It defines how to create a new store with a container. // The generic parameters are the storage type, the driver type returned by the Conn method, // and the container type used to back the storage. // // IMPORTANT: The container type must exist as a Testcontainers module. // Please refer to the [testcontainers] package for more information. type TCKSuite[T storage.Storage, D any, C testcontainers.Container] interface { // NewStore is a function that returns a new store. // It is called by the [New] function to create a new store. NewStore() func(ctx context.Context, tb testing.TB, ctr C) (T, error) // NewContainer is a function that returns a new container. // It is called by the [New] function to create a new container. NewContainer() func(ctx context.Context, tb testing.TB) (C, error) } ``` **Generic Parameters:** - `T`: Your concrete storage type (e.g., `*mysql.Storage`) - `D`: The driver type returned by `Conn()` method (e.g., `*sql.DB`) - `C`: The testcontainer type (e.g., `*mysql.MySQLContainer`) Please verify that a suitable Testcontainers module exists for your container type. See the [Testcontainers modules catalog](https://testcontainers.com/modules/?language=go) for details. #### Test Execution Modes The TCK supports two execution modes: - **PerTest** (default): Creates a new container and storage instance for each test - **PerSuite**: Creates one container and storage instance for the entire test suite ### Implementation Guide: Example Here's how to implement TCK tests for a new storage backend: #### Step 1: Define Your TCK Implementation ```go // ExampleStorageTCK is the test suite for the Example storage. type ExampleStorageTCK struct{} // NewStore is a function that returns a new Example storage. // It implements the [tck.TCKSuite] interface, allowing the TCK to create a new Example storage // from the container created by the TCK. func (s *ExampleStorageTCK) NewStore() func(ctx context.Context, tb testing.TB, ctr *ExampleContainer) (*Storage, error) { return func(ctx context.Context, tb testing.TB, ctr *example.Container) (*Storage, error) { // Use container APIs to get connection details conn, err := ctr.ConnectionString(ctx) require.NoError(tb, err) store := New(Config{ // Apply the storage-specific configuration ConnectionURI: conn, Reset: true, }) return store, nil } } // NewContainer is a function that returns a new Example container. // It implements the [tck.TCKSuite] interface, allowing the TCK to create a new Example container // for the Example storage. func (s *ExampleStorageTCK) NewContainer() func(ctx context.Context, tb testing.TB) (*example.Container, error) { return func(ctx context.Context, tb testing.TB) (*example.Container, error) { return mustStartExample(tb), nil } } ``` #### Step 2: Implement Container Creation Create a helper function to start your storage backend's container: ```go func mustStartExample(t testing.TB) *example.Container { img := exampleImage if imgFromEnv := os.Getenv(exampleImageEnvVar); imgFromEnv != "" { img = imgFromEnv } ctx := context.Background() c, err := example.Run(ctx, img, example.WithOptionA("valueA"), example.WithOptionB("valueB"), testcontainers.WithWaitStrategy( wait.ForListeningPort("examplePort/tcp"), ), ) testcontainers.CleanupContainer(t, c) require.NoError(t, err) return c } ``` #### Step 3: Create and Run the TCK Test ```go func TestExampleStorageTCK(t *testing.T) { // Create the TCK suite with proper generic type parameters s, err := tck.New[*ExampleStorage, *ExampleDriver, *ExampleContainer]( context.Background(), t, &ExampleStorageTCK{}, tck.PerTest(), // or tck.PerSuite() for suite-level containers ) require.NoError(t, err) // Run all TCK tests suite.Run(t, s) } ``` ### Key Implementation Guidelines #### 1. Generic Type Parameters When calling `tck.New`, specify the correct type parameters: - `T`: Your storage pointer type (e.g., `*Storage`) - `D`: The driver type returned by `Conn()` (or `any` if not applicable) - `C`: The container type returned by `NewContainer()` #### 2. Error Handling Always use `require.NoError(tb, err)` in your factory functions to ensure test failures are properly reported. #### 3. Container Cleanup The TCK handles container cleanup, but ensure your `mustStart*` helpers call `testcontainers.CleanupContainer(t, container)`. For ad‑hoc tests outside the TCK, call `CleanupContainer` to avoid leaving containers running until the test process exits. Although Ryuk will prune them, it’s better to clean up immediately. #### 4. Configuration Configure your storage with appropriate test settings: - Enable `Reset: true` if your storage supports it - Use test-specific database/namespace names - Configure appropriate timeouts and connection limits #### 5. Context Handling Always respect the provided `context.Context` in your factory functions, especially for container startup and storage initialization. ### Testing Different Scenarios #### PerTest Mode (Recommended) Use when you need complete isolation between tests: ```go s, err := tck.New[*Storage, *sql.DB](ctx, t, &ExampleStorageTCK{}, tck.PerTest()) ``` **Pros:** - Complete test isolation - No cross-test contamination - Easier debugging of individual test failures **Cons:** - Slower execution due to container startup overhead - Higher resource usage, although mitigated by Testcontainers' cleanup mechanism #### PerSuite Mode Use when container startup is expensive and tests can share state: ```go s, err := tck.New[*Storage, *sql.DB](ctx, t, &ExampleStorageTCK{}, tck.PerSuite()) ``` **Pros:** - Faster execution - Lower resource usage **Cons:** - Tests may affect each other - Requires careful state management ### Troubleshooting #### Common Issues 1. **Wrong Generic Types**: Ensure type parameters match your actual storage and driver types 2. **Container Startup Failures**: Check wait strategies and ensure proper service readiness 3. **Connection Issues**: Verify connection strings and authentication in your `NewStore()` implementation 4. **Test Isolation**: If tests interfere with each other, consider switching from `PerSuite` to `PerTest` #### Best Practices - Use environment variables for container image versions - Implement proper wait strategies for container readiness - Include cleanup calls even though TCK handles them automatically - Test your TCK implementation with both `PerTest` and `PerSuite` modes - Use meaningful test data that won't conflict across parallel test runs ### Complete Example Template Here's a complete template for implementing TCK tests for a new storage backend: ```go package newstorage import ( "context" "os" "testing" "github.com/gofiber/storage/testhelpers/tck" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "github.com/testcontainers/testcontainers-go" // Import your specific testcontainer module ) const ( defaultImage = "your-storage-image:latest" imageEnvVar = "TEST_YOUR_STORAGE_IMAGE" ) type YourStorageTCK struct{} func (s *YourStorageTCK) NewStore() func(ctx context.Context, tb testing.TB, ctr *YourContainer) (*Storage, error) { return func(ctx context.Context, tb testing.TB, ctr *YourContainer) (*Storage, error) { // Get connection details from container conn, err := ctr.ConnectionString(ctx) require.NoError(tb, err) // Create and configure your storage store := New(Config{ ConnectionURI: conn, Reset: true, // Add other test-specific configuration }) return store, nil } } func (s *YourStorageTCK) NewContainer() func(ctx context.Context, tb testing.TB) (*YourContainer, error) { return func(ctx context.Context, tb testing.TB) (*YourContainer, error) { return mustStartYourStorage(tb), nil } } func mustStartYourStorage(t testing.TB) *YourContainer { img := defaultImage if imgFromEnv := os.Getenv(imageEnvVar); imgFromEnv != "" { img = imgFromEnv } ctx := context.Background() c, err := yourstorage.Run(ctx, img, // Add your storage-specific configuration testcontainers.WithWaitStrategy( // Add appropriate wait strategies ), ) testcontainers.CleanupContainer(t, c) require.NoError(t, err) return c } func TestYourStorageTCK(t *testing.T) { s, err := tck.New[*Storage, YourDriverType, *YourContainer]( context.Background(), t, &YourStorageTCK{}, tck.PerTest(), ) require.NoError(t, err) suite.Run(t, s) } ``` This template provides a solid foundation for implementing TCK tests for any new storage backend in the Fiber Storage repository. --- ## Valkey ![Release](https://img.shields.io/github/v/tag/gofiber/storage?filter=valkey*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-valkey.yml?label=Tests) A fast Valkey Storage that does auto pipelining and supports client side caching. Implementation is based on [valkey-io/valkey](https://github.com/valkey-io/valkey-go). #### Table of Contents - [Signatures](#signatures) - [Installation](#installation) - [Examples](#examples) - [Config](#config) - [Default Config](#default-config) #### Signatures ```go func New(config ...Config) Storage func NewWithContext(ctx context.Context, config ...Config) *Storage func NewFromConnection(conn valkey.Client, config ...Config) *Storage func NewFromConnectionWithContext(ctx context.Context, conn valkey.Client, config ...Config) *Storage func (s *Storage) Get(key string) ([]byte, error) func (s *Storage) GetWithContext(ctx context.Context, key string) ([]byte, error) func (s *Storage) Set(key string, val []byte, exp time.Duration) error func (s *Storage) SetWithContext(ctx context.Context, key string, val []byte, exp time.Duration) error func (s *Storage) Delete(key string) error func (s *Storage) DeleteWithContext(ctx context.Context, key string) error func (s *Storage) Reset() error func (s *Storage) ResetWithContext(ctx context.Context) error func (s *Storage) Close() error func (s *Storage) Conn() valkey.Client ``` **Note:** `AlwaysPipelining` defaults to true and this config reads a zero value as "not set", so setting it to false has no effect. Use `DisableAlwaysPipelining: true` to turn it off, the same way `DisableRetry` and `DisableCache` work. #### Installation The valkey driver is tested on the latest two [Go version](https://golang.org/dl/) with support for modules. So make sure to initialize one first if you didn't do that yet: ```bash go mod init github.com// ``` And then install the valkey implementation: ```bash go get github.com/gofiber/storage/valkey ``` #### Examples Import the storage package. ```go import "github.com/gofiber/storage/valkey" ``` You can use the one of the following options to create a Valkey Storage: ```go // Initialize default config (localhost:6379) store := valkey.New() // Initialize custom config store := valkey.New(valkey.Config{ InitAddress: []string{"localhost:6380"}, Username: "", Password: "", Database: 0, Reset: false, TLSConfig: nil, }) // Initialize using Redis-style URL store := valkey.New(valkey.Config{ URL: "redis://localhost:6379", }) // Initialize Valkey Cluster Client store := valkey.New(valkey.Config{ InitAddress: []string{":6379", ":6380"}, }) // Create a client with support for TLS cer, err := tls.LoadX509KeyPair("./client.crt", "./client.key") if err != nil { log.Println(err) return } tlsCfg := &tls.Config{ MinVersion: tls.VersionTLS12, InsecureSkipVerify: true, Certificates: []tls.Certificate{cer}, } store = valkey.New(valkey.Config{ InitAddress: []string{"localhost:6380"}, Username: "", Password: "", SelectDB: 0, TLSConfig: tlsCfg, }) ``` #### Config ```go type Config struct { // Server username // // Optional. Default is "" Username string // Server password // // Optional. Default is "" Password string // ClientName will execute the `CLIENT SETNAME ClientName` command for each conn. // // Optional. Default is "" ClientName string // URL standard format Redis-style URL. If this is set all other config options, InitAddress, Username, Password, ClientName, and SelectDB have no effect. // // Example: redis://:@localhost:6379/ // Optional. Default is "" URL string // SelectDB to be selected after connecting to the server. // // Optional. Default is 0 SelectDB int // Either a single address or a seed list of host:port addresses, this enables FailoverClient and ClusterClient // // Optional. Default is []string{"127.0.0.1:6379"} InitAddress []string // TLS Config to use. When set TLS will be negotiated. // // Optional. Default is nil TLSConfig *tls.Config // CacheSizeEachConn is valkey client side cache size that bind to each TCP connection to a single valkey instance. // // Optional. The default is DefaultCacheBytes: 128 * (1 << 20) CacheSizeEachConn int // RingScaleEachConn sets the size of the ring buffer in each connection to (2 ^ RingScaleEachConn). // // Optional. The default is RingScaleEachConn, which results into having a ring of size 2^10 for each connection. RingScaleEachConn int // ReadBufferEachConn is the size of the bufio.NewReaderSize for each connection, default to DefaultReadBuffer (0.5 MiB). // // Optional. The default is DefaultReadBuffer: 1 << 19 ReadBufferEachConn int // WriteBufferEachConn is the size of the bufio.NewWriterSize for each connection, default to DefaultWriteBuffer (0.5 MiB). // // Optional. The default is DefaultWriteBuffer: 1 << 19 WriteBufferEachConn int // BlockingPoolSize is the size of the connection pool shared by blocking commands (ex BLPOP, XREAD with BLOCK). // // Optional. The default is DefaultPoolSize: 1000 BlockingPoolSize int // PipelineMultiplex determines how many tcp connections used to pipeline commands to one valkey instance. // // Optional. The default for single and sentinel clients is 2, which means 4 connections (2^2). PipelineMultiplex int // DisableRetry disables retrying read-only commands under network errors // // Optional. The default is False DisableRetry bool // DisableCache falls back Client.DoCache/Client.DoMultiCache to Client.Do/Client.DoMulti // // Optional. The default is false DisableCache bool // AlwaysPipelining makes valkey.Client always pipeline valkey commands even if they are not issued concurrently. // // Optional. The default is true AlwaysPipelining bool // DisableAlwaysPipelining turns AlwaysPipelining off. That field defaults to // true and a zero value reads as "not set", so setting it false does nothing; // this is the way to disable it, matching DisableRetry and DisableCache. // // Optional. Default is false DisableAlwaysPipelining bool // Reset clears any existing keys in existing Collection // // Optional. Default is false Reset bool // CacheTTL TTL // // Optional. Default is time.Minute CacheTTL time.Duration } ``` #### Default Config ```go var ConfigDefault = Config{ Username: "", Password: "", ClientName: "", SelectDB: 0, InitAddress: []string{"127.0.0.1:6379"}, TLSConfig: nil, CacheSizeEachConn: valkey.DefaultCacheBytes, RingScaleEachConn: valkey.DefaultRingScale, ReadBufferEachConn: valkey.DefaultReadBuffer, WriteBufferEachConn: valkey.DefaultWriteBuffer, BlockingPoolSize: valkey.DefaultPoolSize, PipelineMultiplex: 2, DisableRetry: false, DisableCache: false, AlwaysPipelining: true, DisableAlwaysPipelining: false, Reset: false, CacheTTL: time.Minute, } ``` #### Using an Existing Valkey Connection If your application already holds a `valkey.Client`, you can build the storage on it instead of creating a second one. Only `CacheTTL` and `Reset` are read from the config; the connection settings come from the client, and nothing touches the network unless `Reset` is set. The client stays yours to close: `Close` on a storage built this way leaves it open, so the rest of your application keeps working. The storage itself is closed, and any operation on it afterwards returns `ErrClosed`. > **Warning:** `Reset` — the config option and the method — issues `FLUSHDB` on the client's database, deleting every key in it, not just this storage's entries. ```go import ( "github.com/gofiber/storage/valkey" valkeylib "github.com/valkey-io/valkey-go" ) func main() { client, err := valkeylib.NewClient(valkeylib.ClientOption{ InitAddress: []string{"127.0.0.1:6379"}, }) if err != nil { panic(err) } defer client.Close() store := valkey.NewFromConnection(client) defer store.Close() } ``` --- ## 👋 Welcome (Template) ![Fiber](https://raw.githubusercontent.com/gofiber/template/master/.github/logo-dark.svg#gh-dark-mode-only) ![Fiber](https://raw.githubusercontent.com/gofiber/template/master/.github/logo.svg#gh-light-mode-only) ![](https://img.shields.io/badge/%F0%9F%93%9A%20godoc-pkg-00ACD7.svg?color=00ACD7&style=flat) ![](https://img.shields.io/badge/%F0%9F%93%9D%20goreport-A%2B-75C46B) ![](https://img.shields.io/badge/%F0%9F%93%8A%20benchmarks-charts-00ACD7?style=flat) ![](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7) This package provides universal methods to use multiple template engines with the [Fiber web framework](https://github.com/gofiber/fiber) using the new [Views](https://godoc.org/github.com/gofiber/fiber#Views) interface that is available from `> v1.11.1`. Special thanks to @bdtomlin & @arsmn for helping! 9 template engines are supported: - [ace](./ace/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/template/test-ace.yml?branch=master&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [amber](./amber/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/template/test-amber.yml?branch=master&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [django](./django/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/template/test-django.yml?branch=master&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [handlebars](./handlebars/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/template/test-handlebars.yml?branch=master&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [html](./html/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/template/test-html.yml?branch=master&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [jet](./jet/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/template/test-jet.yml?branch=master&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [mustache](./mustache/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/template/test-mustache.yml?branch=master&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [pug](./pug/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/template/test-pug.yml?branch=master&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) - [slim](./slim/README.md) ![](https://img.shields.io/github/actions/workflow/status/gofiber/template/test-slim.yml?branch=master&label=%F0%9F%A7%AA%20&style=flat&color=75C46B) #### Installation Go version support: We only support the latest two versions of Go. Visit https://go.dev/doc/devel/release for more information. ``` go get -u github.com/gofiber/fiber/v3 go get -u github.com/gofiber/template/any_template_engine/vX ``` #### Example ```go package main import ( "log" "github.com/gofiber/fiber/v3" // To use a specific template engine, import as shown below: // "github.com/gofiber/template/pug/v3" // "github.com/gofiber/template/mustache/v3" // etc.. // In this example we use the html template engine "github.com/gofiber/template/html/v3" ) func main() { // Create a new engine by passing the template folder // and template extension using .New(dir, ext string) engine := html.New("./views", ".html") // We also support the http.FileSystem interface // See examples below to load templates from embedded files engine := html.NewFileSystem(http.Dir("./views"), ".html") // Reload the templates on each render, good for development engine.Reload(true) // Optional. Default: false // Debug will print each template that is parsed, good for debugging engine.Debug(true) // Optional. Default: false // Layout defines the variable name that is used to yield templates within layouts engine.Layout("embed") // Optional. Default: "embed" // Delims sets the action delimiters to the specified strings engine.Delims("{{", "}}") // Optional. Default: engine delimiters // AddFunc adds a function to the template's global function map. engine.AddFunc("greet", func(name string) string { return "Hello, " + name + "!" }) // After you created your engine, you can pass it to Fiber's Views Engine app := fiber.New(fiber.Config{ Views: engine, }) // To render a template, you can call the ctx.Render function // Render(tmpl string, values interface{}, layout ...string) app.Get("/", func(c fiber.Ctx) error { return c.Render("index", fiber.Map{ "Title": "Hello, World!", }) }) // Render with layout example app.Get("/layout", func(c fiber.Ctx) error { return c.Render("index", fiber.Map{ "Title": "Hello, World!", }, "layouts/main") }) log.Fatal(app.Listen(":3000")) } ``` #### More Examples To view more specific examples, you could visit each engine folder to learn more - [ace](./ace/README.md) - [amber](./amber/README.md) - [django](./django/README.md) - [handlebars](./handlebars/README.md) - [html](./html/README.md) - [jet](./jet/README.md) - [mustache](./mustache/README.md) - [pug](./pug/README.md) - [slim](./slim/README.md) #### embedded Systems We support the `http.FileSystem` interface, so you can use different libraries to load the templates from embedded binaries. ##### pkger Read documentation: https://github.com/markbates/pkger ```go package main import ( "log" "github.com/gofiber/fiber/v3" "github.com/gofiber/template/html/v3" "github.com/markbates/pkger" ) func main() { engine := html.NewFileSystem(pkger.Dir("/views"), ".html") app := fiber.New(fiber.Config{ Views: engine, }) // run pkger && go build } ``` ##### packr Read documentation: https://github.com/gobuffalo/packr ```go package main import ( "log" "github.com/gofiber/fiber/v3" "github.com/gofiber/template/html/v3" "github.com/gobuffalo/packr/v2" ) func main() { engine := html.NewFileSystem(packr.New("Templates", "/views"), ".html") app := fiber.New(fiber.Config{ Views: engine, }) // run packr && go build } ``` ##### go.rice Read documentation: https://github.com/GeertJohan/go.rice ```go package main import ( "log" "github.com/gofiber/fiber/v3" "github.com/gofiber/template/html/v3" "github.com/GeertJohan/go.rice" ) func main() { engine := html.NewFileSystem(rice.MustFindBox("views").HTTPBox(), ".html") app := fiber.New(fiber.Config{ Views: engine, }) // run rice embed-go && go build } ``` ##### fileb0x Read documentation: https://github.com/UnnoTed/fileb0x ```go package main import ( "log" "github.com/gofiber/fiber/v3" "github.com/gofiber/template/html/v3" // your generated package "github.com///static" ) func main() { engine := html.NewFileSystem(static.HTTP, ".html") app := fiber.New(fiber.Config{ Views: engine, }) // Read the documentation on how to use fileb0x } ``` #### Benchmarks See the benchmarks under https://gofiber.github.io/template/benchmarks ##### Simple ![](https://raw.githubusercontent.com/gofiber/template/master/.github/data/Simple-TimeperOperation.png) ##### Extended ![](https://raw.githubusercontent.com/gofiber/template/master/.github/data/Extended-TimeperOperation.png) Benchmarks were ran on Apple Macbook M1. Each engine was benchmarked 20 times and the results averaged into a single xlsx file. Mustache was excluded from the extended benchmark --- ## Ace ![Release](https://img.shields.io/github/v/tag/gofiber/template?filter=ace*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://github.com/gofiber/template/workflows/Tests%20Ace/badge.svg) Ace is a template engine create by [yossi](https://github.com/yosssi/ace), to see the original syntax documentation please [click here](https://github.com/yosssi/ace/blob/master/documentation/syntax.md) ### Installation Go version support: We only support the latest two versions of Go. Visit https://go.dev/doc/devel/release for more information. ``` go get github.com/gofiber/template/ace/v3 ``` ### Basic Example _**./views/index.ace**_ ```html = include ./views/partials/header . h1 {{.Title}} = include ./views/partials/footer . ``` _**./views/partials/header.ace**_ ```html h1 Header ``` _**./views/partials/footer.ace**_ ```html h1 Footer ``` _**./views/layouts/main.ace**_ ```html = doctype html html head title Main body {{embed}} ``` ```go package main import ( "log" "github.com/gofiber/fiber/v3" "github.com/gofiber/template/ace/v3" ) func main() { // Create a new engine engine := ace.New("./views", ".ace") // Or from an embedded system // See github.com/gofiber/embed for examples // engine := html.NewFileSystem(http.Dir("./views", ".ace")) // Pass the engine to the Views app := fiber.New(fiber.Config{ Views: engine, }) app.Get("/", func(c fiber.Ctx) error { // Render index return c.Render("index", fiber.Map{ "Title": "Hello, World!", }) }) app.Get("/layout", func(c fiber.Ctx) error { // Render index within layouts/main return c.Render("index", fiber.Map{ "Title": "Hello, World!", }, "layouts/main") }) log.Fatal(app.Listen(":3000")) } ``` --- ## Amber ![Release](https://img.shields.io/github/v/tag/gofiber/template?filter=amber*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://github.com/gofiber/template/workflows/Tests%20Amber/badge.svg) Amber is a template engine create by [eknkc](https://github.com/eknkc/amber), to see the original syntax documentation please [click here](https://github.com/eknkc/amber#tags) ### Installation Go version support: We only support the latest two versions of Go. Visit https://go.dev/doc/devel/release for more information. ``` go get github.com/gofiber/template/amber/v3 ``` ### Basic Example _**./views/index.amber**_ ```html import ./views/partials/header h1 #{Title} import ./views/partials/footer ``` _**./views/partials/header.amber**_ ```html h1 Header ``` _**./views/partials/footer.amber**_ ```html h1 Footer ``` _**./views/layouts/main.amber**_ ```html doctype html html head title Main body #{embed()} ``` ```go package main import ( "log" "github.com/gofiber/fiber/v3" "github.com/gofiber/template/amber/v3" ) func main() { // Create a new engine engine := amber.New("./views", ".amber") // Or from an embedded system // See github.com/gofiber/embed for examples // engine := html.NewFileSystem(http.Dir("./views", ".amber")) // Pass the engine to the Views app := fiber.New(fiber.Config{ Views: engine, }) app.Get("/", func(c fiber.Ctx) error { // Render index return c.Render("index", fiber.Map{ "Title": "Hello, World!", }) }) app.Get("/layout", func(c fiber.Ctx) error { // Render index within layouts/main return c.Render("index", fiber.Map{ "Title": "Hello, World!", }, "layouts/main") }) log.Fatal(app.Listen(":3000")) } ``` --- ## Django ![Release](https://img.shields.io/github/v/tag/gofiber/template?filter=django*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://github.com/gofiber/template/workflows/Tests%20Django/badge.svg) Django is a template engine create by [flosch](https://github.com/flosch/pongo2), to see the original syntax documentation please [click here](https://docs.djangoproject.com/en/dev/topics/templates/) ### Installation Go version support: We only support the latest two versions of Go. Visit https://go.dev/doc/devel/release for more information. ``` go get github.com/gofiber/template/django/v4 ``` ### Basic Example _**./views/index.django**_ ```html {% include "partials/header.django" %}

{{ Title }}

{% include "partials/footer.django" %} ``` _**./views/partials/header.django**_ ```html

Header

``` _**./views/partials/footer.django**_ ```html

Footer

``` _**./views/layouts/main.django**_ ```html Main {{embed}} ``` ```go package main import ( "log" "github.com/gofiber/fiber/v3" "github.com/gofiber/template/django/v4" ) func main() { // Create a new engine engine := django.New("./views", ".django") // Or from an embedded system // See github.com/gofiber/embed for examples // engine := html.NewFileSystem(http.Dir("./views", ".django")) // Pass the engine to the Views app := fiber.New(fiber.Config{ Views: engine, }) app.Get("/", func(c fiber.Ctx) error { // Render index return c.Render("index", fiber.Map{ "Title": "Hello, World!", }) }) app.Get("/layout", func(c fiber.Ctx) error { // Render index within layouts/main return c.Render("index", fiber.Map{ "Title": "Hello, World!", }, "layouts/main") }) log.Fatal(app.Listen(":3000")) } ``` #### Using embedded file system (1.16+ only) When using the `// go:embed` directive, resolution of inherited templates using django's `{% extend '' %}` keyword fails when instantiating the template engine with `django.NewFileSystem()`. In that case, use the `django.NewPathForwardingFileSystem()` function to instantiate the template engine. This function provides the proper configuration for resolving inherited templates. Assume you have the following files: - [views/ancenstor.django](https://github.com/gofiber/template/blob/master/django/views/ancestor.django) - [views/descendant.djando](https://github.com/gofiber/template/blob/master/django/views/descendant.django) then ```go package main import ( "log" "embed" "net/http" "github.com/gofiber/fiber/v3" "github.com/gofiber/template/django/v4" ) //go:embed views var viewsAsssets embed.FS func main() { // Create a new engine engine := django.NewPathForwardingFileSystem(http.FS(viewsAsssets), "/views", ".django") // Pass the engine to the Views app := fiber.New(fiber.Config{ Views: engine, }) app.Get("/", func(c fiber.Ctx) error { // Render descendant return c.Render("descendant", fiber.Map{ "greeting": "World", }) }) log.Fatal(app.Listen(":3000")) } ``` #### Register and use custom functions ```go // My custom function func Nl2brHtml(value interface{}) string { if str, ok := value.(string); ok { return strings.Replace(str, "\n", "
", -1) } return "" } // Create a new engine engine := django.New("./views", ".django") // register functions engine.AddFunc("nl2br", Nl2brHtml) // Pass the engine to the Views app := fiber.New(fiber.Config{Views: engine}) ``` _**in the handler**_ ```go c.Render("index", fiber.Map{ "Fiber": "Hello, World!\n\nGreetings from Fiber Team", }) ``` _**./views/index.django**_ ```html {{ nl2br(Fiber) }} ``` **Output:** ```html Hello, World!

Greetings from Fiber Team ``` #### Important Information on Template Data Binding When working with Pongo2 and this template engine, it's crucial to understand the specific rules for data binding. Only keys that match the following regular expression are supported: `^[a-zA-Z0-9_]+$`. This means that keys with special characters or punctuation, such as `my-key` or `my.key`, are not compatible and will not be bound to the template. This is a restriction imposed by the underlying Pongo2 template engine. Please ensure your keys adhere to these rules to avoid any binding issues. If you need to access a value in the template that doesn't adhere to the key naming restrictions imposed by the Pongo2 template engine, you can bind the value to a new field when calling `fiber.Render`. Here's an example: ```go c.Render("index", fiber.Map{ "Fiber": "Hello, World!\n\nGreetings from Fiber Team", "MyKey": c.Locals("my-key"), }) ``` #### AutoEscape is enabled by default When you create a new instance of the `Engine`, the auto-escape is **enabled by default**. This setting automatically escapes output, providing a critical security measure against Cross-Site Scripting (XSS) attacks. #### Disabling Auto-Escape Auto-escaping can be disabled if necessary, using the `SetAutoEscape` method: ```go engine := django.New("./views", ".django") engine.SetAutoEscape(false) ``` #### Setting AutoEscape using Django built-in template tags - Explicitly turning off autoescaping for a section: ```django {% autoescape off %} {{ "" }} {% endautoescape %} ``` - Turning autoescaping back on for a section: ```django {% autoescape on %} {{ "" }} {% endautoescape %} ``` - It can also be done on a per variable basis using the *safe* built-in: ```django

{{ someSafeVar | safe }}

{{ " ``` ```js // Javascript var cat = {"Name":"Sam", "Age":12} ``` --- ##### Safe Strings and HTML Comments The `html/template` package will remove any comments from a template by default. This can cause issues when comments are necessary such as detecting internet explorer. ```html ``` We can use the Custom Functions method (Globally) to create a function that returns html preserving comments. Define a function `htmlSafe` in the FuncMap of the template. ```go testTemplate, err = template.New("hello.gohtml").Funcs(template.FuncMap{ "htmlSafe": func(html string) template.HTML { return template.HTML(html) }, }).ParseFiles("hello.gohtml") ``` This function takes a string and produces the unaltered HTML code. This function can be used in a template like so to preserve the comments `` : ```go {{htmlSafe "" }} ``` --- ### Template Variables ##### The dot character (.) A template variable can be a boolean, string, character, integer, floating-point, imaginary, or complex constant in Go syntax. Data passed to the template can be accessed using dot `{{ . }}`. If the data is a complex type then it’s fields can be accessed using the dot with the field name `{{ .FieldName }}`. Dots can be chained together if the data contains multiple complex structures. `{{ .Struct.StructTwo.Field }}` --- ##### Variables in Templates Data passed to the template can be saved in a variable and used throughout the template. `{{$number := .}}` We use the `$number` to create a variable then initialize it with the value passed to the template. To use the variable we call it in the template with `{{$number}}`. ```go {{$number := .}}

It is day number {{$number}} of the month

``` ```go var tpl *template.Template tpl = template.Must(template.ParseFiles("templateName")) err := tpl.ExecuteTemplate(os.Stdout, "templateName", 23) ``` In this example we pass 23 to the template and stored in the `$number` variable which can be used anywhere in the template --- ### Template Actions ##### If/Else Statements Go templates support if/else statements like many programming languages. We can use the if statement to check for values, if it doesn’t exist we can use an else value. The empty values are false, 0, any nil pointer or interface value, and any array, slice, map, or string of length zero. ```html

Hello, {{if .Name}} {{.Name}} {{else}} Anonymous {{end}}!

``` If .Name exists then `Hello, Name` will be printed (replaced with the name value) otherwise it will print `Hello, Anonymous`. Templates also provide the else if statment `{{else if .Name2 }}` which can be used to evaluate other options after an if. --- ##### Removing Whitespace Adding different values to a template can add various amounts of whitespace. We can either change our template to better handle it, by ignoring or minimizing effects, or we can use the minus sign `-` within out template. `

Hello, {{if .Name}} {{.Name}} {{- else}} Anonymous {{- end}}!

` Here we are telling the template to remove all spaces between the `Name` variable and whatever comes after it. We are doing the same with the end keyword. This allows us to have whitespace within the template for easier reading but remove it in production. --- ##### Range Blocks Go templates have a `range` keyword to iterate over all objects in a structure. Suppose we had the Go structures: ```go type Item struct { Name string Price int } type ViewData struct { Name string Items []Item } ``` We have an Item, with a name and price, then a ViewData which is the structure sent to the template. Consider the template containing the following: ```html {{range .Items}}

{{.Name}}

${{.Price}}
{{end}} ``` For each Item in the range of Items (in the ViewData structure) get the Name and Price of that item and create html for each Item automatically. Within a range each Item becomes the `{{.}}` and the item properties therefore become `{{.Name}}` or `{{.Price}}` in this example. --- ### Template Functions The template package provides a list of predefined global functions. Below are some of the most used. --- ##### Indexing structures in Templates If the data passed to the template is a map, slice, or array it can be indexed from the template. We use `{{index x number}}` where index is the keyword, x is the data and number is a integer for the index value. If we had `{{index names 2}}` it is equivalent to `names[2]`. We can add more integers to index deeper into data. `{{index names 2 3 4}}` is equivalent to `names[2][3][4]`. ```html

{{index .FavNums 2 }}

``` ```go type person struct { Name string FavNums []int } func main() { tpl := template.Must(template.ParseGlob("*.gohtml")) tpl.Execute(os.Stdout, &person{"Curtis", []int{7, 11, 94}}) } ``` This code example passes a person structure and gets the 3rd favourite number from the FavNums slice. --- ##### The `and` Function The and function returns the boolean AND of its arguments by returning the first empty argument or the last argument. `and x y` behaves logically as `if x then y else x` . Consider the following go code ```go type User struct { Admin bool } type ViewData struct { *User } ``` Pass a ViewData with a User that has Admin set true to the following template ```go {{if and .User .User.Admin}} You are an admin user! {{else}} Access denied! {{end}} ``` The result will be `You are an admin user!`. However if the ViewData did not include a \*User object or Admin was set as false then the result will be `Access denied!`. --- ##### The `or` Function The or function operates similarly to the and function however will stop at the first true. `or x y` is equivalent to `if x then x else y` so y will never be evaluated if x is not empty. --- ##### The `not` Function The not function returns the boolean negation of the argument. ```go {{ if not .Authenticated}} Access Denied! {{ end }} ``` --- ### Template Comparison Functions ##### Comparisons The `html/template` package provides a variety of functions to do comparisons between operators. The operators may only be basic types or named basic types such as `type Temp float32` Remember that template functions take the form `{{ function arg1 arg2 }}`. - `eq` Returns the result of `arg1 == arg2` - `ne` Returns the result of `arg1 != arg2` - `lt` Returns the result of `arg1 < arg2` - `le` Returns the result of `arg1 <= arg2` - `gt` Returns the result of `arg1 > arg2` - `ge` Returns the result of `arg1 >= arg2` Of special note `eq` can be used with two or more arguments by comparing all arguments to the first. `{{ eq arg1 arg2 arg3 arg4}}` will result in the following logical expression: `arg1==arg2 || arg1==arg3 || arg1==arg4` --- ### Nested Templates and Layouts ##### Nesting Templates Nested templates can be used for parts of code frequently used across templates, a footer or header for example. Rather than updating each template separately we can use a nested template that all other templates can use. You can define a template as follows: ```go {{define "footer"}}

Here is the footer

{{end}} ``` A template named “footer” is defined which can be used in other templates like so to add the footer template content into the other template: ```go {{template "footer"}} ``` --- ##### Passing Variables between Templates The `template` action used to include nested templates also allows a second parameter to pass data to the nested template. ```html // Define a nested template called header {{define "header"}}

{{.}}

{{end}} // Call template and pass a name parameter {{range .Items}}
{{template "header" .Name}} ${{.Price}}
{{end}} ``` We use the same range to loop through Items as before but we pass the name to the header template each time in this simple example. --- ##### Creating Layouts Glob patterns specify sets of filenames with wildcard characters. The `template.ParseGlob(pattern string)` function will parse all templates that match the string pattern. `template.ParseFiles(files...)` can also be used with a list of file names. The templates are named by default based on the base names of the argument files. This mean `views/layouts/hello.gohtml` will have the name `hello.gohtml` . If the template has a `{{define “templateName”}}` within it then that name will be usable. A specific template can be executed using `t.ExecuteTemplate(w, "templateName", nil)` . `t` is an object of type Template, `w` is type io.Writer such as an `http.ResponseWriter`, Then there is the name of the template to execute, and finally passing any data to the template, in this case a nil value. Example main.go file ```go // Omitted imports & package var LayoutDir string = "views/layouts" var bootstrap *template.Template func main() { var err error bootstrap, err = template.ParseGlob(LayoutDir + "/*.gohtml") if err != nil { panic(err) } http.HandleFunc("/", handler) http.ListenAndServe(":8080", nil) } func handler(w http.ResponseWriter, r *http.Request) { bootstrap.ExecuteTemplate(w, "bootstrap", nil) } ``` All `.gohtml` files are parsed in main. When route `/` is reached the template defined as `bootstrap` is executed using the handler function. Example views/layouts/bootstrap.gohtml file ```html {{define "bootstrap"}} Go Templates

Filler header

Filler paragraph

{{end}} ``` ### Templates Calling Functions ##### Function Variables (calling struct methods) We can use templates to call the methods of objects in the template to return data. Consider the User struct with the following method. ```go type User struct { ID int Email string } func (u User) HasPermission(feature string) bool { if feature == "feature-a" { return true } else { return false } } ``` When a type User has been passed to the template we can then call this method from the template. ```html {{if .User.HasPermission "feature-a"}}

Feature A

Some other stuff here...

{{else}}

Feature A

To enable Feature A please upgrade your plan

{{end}} ``` The template checks if the User HasPermission for the feature and renders depending on the result. --- ##### Function Variables (call) If the Method HasPermission has to change at times then the Function Variables (Methods) implementation may not fit the design. Instead a `HasPermission func(string) bool` attribute can be added on the `User` type. This can then have a function assigned to it at creation. ```go // Structs type ViewData struct { User User } type User struct { ID int Email string HasPermission func(string) bool } // Example of creating a ViewData vd := ViewData{ User: User{ ID: 1, Email: "curtis.vermeeren@gmail.com", // Create the HasPermission function HasPermission: func(feature string) bool { if feature == "feature-b" { return true } return false }, }, } // Executing the ViewData with the template err := testTemplate.Execute(w, vd) ``` We need to tell the Go template that we want to call this function so we must change the template from the Function Variables (Methods) implementation to do this. We use the `call` keyword supplied by the go `html/template` package. Changing the previous template to use `call` results in: ```html {{if (call .User.HasPermission "feature-b")}}

Feature B

Some other stuff here...

{{else}}

Feature B

To enable Feature B please upgrade your plan

{{end}} ``` --- ##### Custom Functions Another way to call functions is to create custom functions with `template.FuncMap` . This method creates global methods that can be used throughout the entire application. FuncMap has type `map[string]interface{}` mapping a string, the function name, to a function. The mapped functions must have either a single return value, or two return values where the second has type error. ```go // Creating a template with function hasPermission testTemplate, err = template.New("hello.gohtml").Funcs(template.FuncMap{ "hasPermission": func(user User, feature string) bool { if user.ID == 1 && feature == "feature-a" { return true } return false }, }).ParseFiles("hello.gohtml") ``` Here the function to check if a user has permission for a feature is mapped to the string `"hasPermission"` and stored in the FuncMap. Note that the custom functions must be created before calling `ParseFiles()` The function could be executed in the template as follows: ```go {{ if hasPermission .User "feature-a" }} ``` The `.User` and string `"feature-a"` are both passed to `hasPermission` as arguments. --- ##### Custom Functions (Globally) The previous two methods of custom functions rely on `.User` being passed to the template. This works in many cases but in a large application passing too many objects to a template can become difficult to maintain across many templates. We can change the implementation of the custom function to work without the .User being passed. Using a similar feature example as the other 2 sections first you would have to create a default `hasPermission` function and define it in the template’s function map. ```go testTemplate, err = template.New("hello.gohtml").Funcs(template.FuncMap{ "hasPermission": func(feature string) bool { return false }, }).ParseFiles("hello.gohtml") ``` This function could be placed in `main()` or somewhere that ensures the default `hasPermission` is created in the `hello.gohtml` function map. The default function just returns false but it defines the function and implementation that doesn’t require `User` . Next a closure could be used to redefine the `hasPermission` function. It would use the `User` data available when it is created in a handler rather than having `User` data passed to it. Within the handler for the template you can redefine any functions to use the information available. ```go func handler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html") user := User{ ID: 1, Email: "Curtis.vermeeren@gmail.com", } vd := ViewData{} err := testTemplate.Funcs(template.FuncMap{ "hasPermission": func(feature string) bool { if user.ID == 1 && feature == "feature-a" { return true } return false }, }).Execute(w, vd) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } ``` In this handler a `User` is created with ID and Email, Then a `ViewData` is created without passing the user to it. The `hasPermission` function is redefined using `user.ID` which is available when the function is created. `{{if hasPermission "feature-a"}}` can be used in a template without having to pass a `User` to the template as the User object in the handler is used instead. --- --- ## Jet ![Release](https://img.shields.io/github/v/tag/gofiber/template?filter=jet*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://github.com/gofiber/template/workflows/Tests%20Jet/badge.svg) Jet is a template engine create by [cloudykit](https://github.com/CloudyKit/jet), to see the original syntax documentation please [click here](https://github.com/CloudyKit/jet/wiki/3.-Jet-template-syntax) ### Installation Go version support: We only support the latest two versions of Go. Visit https://go.dev/doc/devel/release for more information. ``` go get github.com/gofiber/template/jet/v3 ``` ### Basic Example _**./views/index.jet**_ ```html {{include "partials/header"}}

{{ Title }}

{{include "partials/footer"}} ``` _**./views/partials/header.jet**_ ```html

Header

``` _**./views/partials/footer.jet**_ ```html

Footer

``` _**./views/layouts/main.jet**_ ```html Title {{ embed() }} ``` ```go package main import ( "log" "github.com/gofiber/fiber/v3" "github.com/gofiber/template/jet/v3" ) func main() { // Create a new engine engine := jet.New("./views", ".jet") // Or from an embedded system // See github.com/gofiber/embed for examples // engine := jet.NewFileSystem(http.Dir("./views"), ".jet") // Pass the engine to the views app := fiber.New(fiber.Config{ Views: engine, }) app.Get("/", func(c fiber.Ctx) error { // Render index return c.Render("index", fiber.Map{ "Title": "Hello, World!", }) }) app.Get("/layout", func(c fiber.Ctx) error { // Render index within layouts/main return c.Render("index", fiber.Map{ "Title": "Hello, World!", }, "layouts/main") }) log.Fatal(app.Listen(":3000")) } ``` --- ## Mustache ![Release](https://img.shields.io/github/v/tag/gofiber/template?filter=mustache*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://github.com/gofiber/template/workflows/Tests%20Mustache/badge.svg) Mustache is a template engine created by [hoisie/cbroglie](https://github.com/cbroglie/mustache), to see the original syntax documentation please [click here](https://mustache.github.io/mustache.5.html) ### Installation Go version support: We only support the latest two versions of Go. Visit https://go.dev/doc/devel/release for more information. ``` go get github.com/gofiber/template/mustache/v3 ``` ### Basic Example _**./views/index.mustache**_ ```html {{> partials/header }}

{{Title}}

{{> partials/footer }} ``` _**./views/partials/header.mustache**_ ```html

Header

``` _**./views/partials/footer.mustache**_ ```html

Footer

``` _**./views/layouts/main.mustache**_ ```html Main {{{embed}}} ``` ```go package main import ( "log" "github.com/gofiber/fiber/v3" "github.com/gofiber/template/mustache/v3" // "net/http" // embedded system ) func main() { // Create a new engine engine := mustache.New("./views", ".mustache") // Or from an embedded system // engine := mustache.NewFileSystem(http.Dir("./views"), ".mustache") // Pass the engine to the Views app := fiber.New(fiber.Config{ Views: engine, }) app.Get("/", func(c fiber.Ctx) error { // Render index return c.Render("index", fiber.Map{ "Title": "Hello, World!", }) }) app.Get("/layout", func(c fiber.Ctx) error { // Render index within layouts/main return c.Render("index", fiber.Map{ "Title": "Hello, World!", }, "layouts/main") }) log.Fatal(app.Listen(":3000")) } ``` #### Partials A `{{> name }}` include is served from the templates the engine loaded, so a partial is named exactly the way `Render` names a template: `{{> partials/header }}` for `./views/partials/header.mustache` under `mustache.New("./views", ".mustache")`. The directory-qualified form (`{{> views/partials/header }}`) and the root-anchored form (`{{> /partials/header }}`) resolve to the same file, so templates written against earlier versions keep working. With an embedded filesystem the names are relative to the filesystem's root, not to the working directory, so `//go:embed views` makes the partial above `{{> views/partials/header }}`. Embedding the contents of `views` instead, or passing `http.Dir("./views")`, makes it `{{> partials/header }}`. `mustache.NewFileSystemPartials(fs, ".mustache", partialsFS)` takes the partials from `partialsFS` as well, for partials that do not sit beside the templates. Because the partials come from what was loaded, an include cannot reach outside the engine directory. The leading slash in `{{> /partials/header }}` anchors the name to that root, not to the filesystem, so a filesystem path such as `/etc/passwd`, a name that climbs out with `..`, and one that points through a symbolic link the loader did not walk are all simply not there. `Load` reports it rather than rendering nothing, naming the template and the partial, and it reports an include cycle the same way instead of letting the render recurse. Both are load-time failures, so a bad include shows up when the engine starts rather than once per request. --- ## Pug ![Release](https://img.shields.io/github/v/tag/gofiber/template?filter=pug*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://github.com/gofiber/template/workflows/Tests%20Pug/badge.svg) Pug is a template engine create by [joker](https://github.com/Joker/jade), to see the original syntax documentation please [click here](https://pugjs.org/language/tags.html) ### Installation Go version support: We only support the latest two versions of Go. Visit https://go.dev/doc/devel/release for more information. ``` go get github.com/gofiber/template/pug/v3 ``` ### Basic Example _**./views/index.pug**_ ```html include partials/header.pug h1 #{.Title} include partials/footer.pug ``` _**./views/partials/header.pug**_ ```html h2 Header ``` _**./views/partials/footer.pug**_ ```html h2 Footer ``` _**./views/layouts/main.pug**_ ```html doctype html html head title Main include ../partials/meta.pug body | {{embed}} ``` ```go package main import ( "log" "github.com/gofiber/fiber/v3" "github.com/gofiber/template/pug/v3" // "net/http" // embedded system ) func main() { // Create a new engine engine := pug.New("./views", ".pug") // Or from an embedded system // See github.com/gofiber/embed for examples // engine := pug.NewFileSystem(http.Dir("./views"), ".pug") // Pass the engine to the views app := fiber.New(fiber.Config{ Views: engine, }) app.Get("/", func(c fiber.Ctx) error { // Render index return c.Render("index", fiber.Map{ "Title": "Hello, World!", }) }) app.Get("/layout", func(c fiber.Ctx) error { // Render index within layouts/main return c.Render("index", fiber.Map{ "Title": "Hello, World!", }, "layouts/main") }) log.Fatal(app.Listen(":3000")) } ``` --- ## Slim ![Release](https://img.shields.io/github/v/tag/gofiber/template?filter=slim*) [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) ![Test](https://github.com/gofiber/template/workflows/Tests%20Slim/badge.svg) Slim is a template engine created by [mattn](https://github.com/mattn/go-slim), to see the original syntax documentation please [click here](https://rubydoc.info/gems/slim/frames) ### Installation Go version support: We only support the latest two versions of Go. Visit https://go.dev/doc/devel/release for more information. ``` go get github.com/gofiber/template/slim/v3 ``` ### Basic Example _**./views/index.slim**_ ```html == render("partials/header.slim") h1 = Title == render("partials/footer.slim") ``` _**./views/partials/header.slim**_ ```html h2 = Header ``` _**./views/partials/footer.slim**_ ```html h2 = Footer ``` _**./views/layouts/main.slim**_ ```html doctype html html head title Main include ../partials/meta.slim body == embed ``` ```go package main import ( "log" "github.com/gofiber/fiber/v3" "github.com/gofiber/template/slim/v3" // "net/http" // embedded system ) func main() { // Create a new engine engine := slim.New("./views", ".slim") // Or from an embedded system // See github.com/gofiber/embed for examples // engine := slim.NewFileSystem(http.Dir("./views", ".slim")) // Pass the engine to the Views app := fiber.New(fiber.Config{ Views: engine, }) app.Get("/", func(c fiber.Ctx) error { // Render index return c.Render("index", fiber.Map{ "Title": "Hello, World!", }) }) app.Get("/layout", func(c fiber.Ctx) error { // Render index within layouts/main return c.Render("index", fiber.Map{ "Title": "Hello, World!", }, "layouts/main") }) log.Fatal(app.Listen(":3000")) } ```