diff --git a/docs/core/api/app.md b/docs/core/api/app.md index c595aae8574c..94cabbc93c89 100644 --- a/docs/core/api/app.md +++ b/docs/core/api/app.md @@ -3,58 +3,29 @@ id: app title: 🚀 App description: The `App` type represents your Fiber application. sidebar_position: 2 +toc_max_heading_level: 4 --- import Reference from '@site/src/components/reference'; -## 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. 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 no view engine is configured or reloading fails. - -```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") -}) -``` - ## Routing import RoutingHandler from './../partials/routing/handler.md'; +import RoutingUse from './../partials/routing/use.md'; ### Route Handlers +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 + + + ### Mounting -Mount another Fiber instance with [`app.Use`](./app.md#use), similar to Express's [`router.use`](https://expressjs.com/en/api.html#router.use). +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 @@ -80,17 +51,9 @@ func main() { } ``` -### 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. +:::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`); there is no `req.baseUrl` equivalent. +::: ### MountPath @@ -223,6 +186,7 @@ func main() { 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") @@ -375,6 +339,7 @@ package main import ( "encoding/json" + "fmt" "log" "github.com/gofiber/fiber/v3" @@ -660,7 +625,7 @@ 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. +`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 @@ -798,6 +763,18 @@ Use `SetTLSHandler` to set [`ClientHelloInfo`](https://datatracker.ietf.org/doc/ func (app *App) SetTLSHandler(tlsHandler *TLSHandler) ``` +## 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`. @@ -855,8 +832,7 @@ config := fiber.TestConfig{ :::caution -This is **not** the same as supplying an empty `TestConfig{}` to -`app.Test(), but rather be the equivalent of supplying: +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{ @@ -877,7 +853,11 @@ This would make a Test that has no timeout. func (app *App) Hooks() *Hooks ``` -## RebuildTree +## 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. @@ -887,8 +867,6 @@ 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. -### Example Usage - Here’s an example of how to define and register routes dynamically: ```go title="Example" @@ -921,7 +899,7 @@ func main() { In this example, a new route is defined and then `RebuildTree()` is called to ensure the new route is registered and available. -## RemoveRoute +### 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. @@ -969,7 +947,7 @@ func main() { } ``` -## RemoveRouteByName +### 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. @@ -978,7 +956,7 @@ If no methods are specified, the route will be removed for all HTTP methods defi func (app *App) RemoveRouteByName(name string, methods ...string) ``` -## RemoveRouteFunc +### RemoveRouteFunc This method removes a route by function having `*Route` parameter. 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. @@ -986,3 +964,40 @@ If no methods are specified, the route will be removed for all HTTP methods defi ```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. 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 no view engine is configured or reloading fails. + +```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") +}) +``` diff --git a/docs/core/guide/grouping.md b/docs/core/guide/grouping.md index 272613f493b2..282b26bf8ec1 100644 --- a/docs/core/guide/grouping.md +++ b/docs/core/guide/grouping.md @@ -81,3 +81,29 @@ func main() { 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. An `All` in the chain runs before the verb handlers 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. +::: diff --git a/docs/core/guide/routing.md b/docs/core/guide/routing.md index bc3242098331..86052bce6497 100644 --- a/docs/core/guide/routing.md +++ b/docs/core/guide/routing.md @@ -11,54 +11,166 @@ toc_max_heading_level: 4 import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import RoutingHandler from './../partials/routing/handler.md'; +import RoutingUse from './../partials/routing/use.md'; +import RoutingHandlerTypes from './../partials/routing/handler-types.md'; +import RouteAnatomy from '@site/src/components/route-anatomy'; -## Handlers +## 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. + +## Route Handlers -## Automatic HEAD routes +Here is a complete, runnable app for context: -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. +```go title="A minimal Fiber app" +package main -```go title="GET handlers automatically expose HEAD" -app := fiber.New() +import "github.com/gofiber/fiber/v3" -app.Get("/users/:id", func(c fiber.Ctx) error { - c.Set("X-User", c.Params("id")) - return c.SendStatus(fiber.StatusOK) +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. + + + + +```go +app.Get("/users", func(c fiber.Ctx) error { + return c.SendString("GET /users") }) -// HEAD /users/:id now returns the same headers and status without a body. +// GET /users -> "GET /users" +// POST /users -> 405 Method Not Allowed +// GET /users/42 -> 404 Not Found (exact match only) ``` -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) +```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) ``` -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) -} +```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() +}) -app := fiber.New(fiber.Config{DisableHeadAutoRegister: true}) -app.Get("/users/:id", handler) // HEAD /users/:id now returns 405 unless you add it manually. +// 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 ``` -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. + + + +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 +``` + + + + +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**; a path that matches no route at all (including one rejected by a [constraint](#constraints)) returns **404 Not Found**. ## Paths A route path paired with an HTTP method defines an endpoint. It can be a plain **string** or a **pattern**. -### Examples of route paths based on strings - ```go // This route path will match requests to the root route, "/": app.Get("/", func(c fiber.Ctx) error { @@ -76,8 +188,7 @@ app.Get("/random.txt", func(c fiber.Ctx) error { }) ``` -As with the Express.js framework, the order in which routes are declared matters. -Routes are evaluated sequentially, so more specific paths should appear before those with variables. +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. @@ -87,49 +198,39 @@ Place routes with variable parameters after fixed paths to avoid unintended matc 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. - -Use `*` or `+` to capture segments greedily. - -You can define optional parameters by appending `?` to a named segment. The `+` sign is greedy and required, while `*` acts as an optional greedy wildcard. +The characters `:`, `+`, and `*` introduce parameters. Append `?` to a named segment 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). -### Example of defining routes with route parameters + + ```go -// Parameters +// 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 - not optional + +// Plus - greedy, required (matches at least one character) app.Get("/user/+", func(c fiber.Ctx) error { return c.SendString(c.Params("+")) }) -// Optional parameter +// Optional named parameter app.Get("/user/:name?", func(c fiber.Ctx) error { return c.SendString(c.Params("name")) }) -// Wildcard - greedy - optional +// Wildcard - greedy, optional (may match nothing) app.Get("/user/*", func(c fiber.Ctx) error { return c.SendString(c.Params("*")) }) - -// This route path will match requests to "/v1/some/resource/name:customVerb", since the parameter character is escaped -app.Get(`/v1/some/resource/name\:customVerb`, func(c fiber.Ctx) error { - return c.SendString("Hello, Community") -}) ``` -:::info -The hyphen \(`-`\) and dot \(`.`\) are treated literally, so you can combine them with route parameters. -::: + + -:::info -Escape special parameter characters with `\\` to treat them literally. This technique 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. -::: +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 @@ -137,19 +238,13 @@ 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 }) -``` -```go // 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 }) -``` - -Fiber's router detects when these characters belong to the literal path and handles them accordingly. -```go // 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")) @@ -157,7 +252,22 @@ app.Get("/shop/product/color::color/size::size", func(c fiber.Ctx) error { }) ``` -You can chain multiple named or unnamed parameters—including wildcard and plus segments—giving the router greater flexibility. + + + +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") +}) +``` + + + + +You can chain multiple named or unnamed parameters, including wildcard and plus segments, within a single segment. ```go // GET /@v1 @@ -177,14 +287,23 @@ app.Get("/*v1*/proxy", handler) app.Get("/v1/*/shop/*", handler) ``` -Fiber's routing is inspired by Express but intentionally omits regular expression routes due to their performance cost. You can try similar patterns using the Express route tester (v0.1.7). +:::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, each leading one captures a single character and the last captures the rest. This does not raise an error, so an unexpected pattern silently captures differently than you might assume. +::: + + + + +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 aren't validation for parameters. If constraints aren't valid for a parameter value, Fiber returns **404 handler**. +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 | @@ -192,9 +311,9 @@ Constraints aren't validation for parameters. If constraints aren't valid for a | int | `:id` | 123456789, -123456789 | | bool | `:active` | true,false | | guid | `:id` | CD2C1638-1638-72D5-1638-DEADBEEF1638 | -| float | `:weight` | 1.234, -1,001.01e8 | +| float | `:weight` | 1.234, -1001.01e8, 3.14 | | minLen(value) | `:username` | Test (must be at least 4 characters) | -| maxLen(value) | `:filename` | MyFile (must be no more than 8 characters | +| maxLen(value) | `:filename` | MyFile (must be no more than 8 characters) | | len(length) | `:filename` | somefile.txt (exactly 12 characters) | | min(value) | `:age` | 19 (Integer value must be at least 18) | | max(value) | `:age` | 91 (Integer value must be no more than 120) | @@ -243,7 +362,7 @@ app.Get("/:test", func(c fiber.Ctx) error { -Fiber precompiles the regex when registering routes, so regex constraints add no runtime overhead. +Fiber precompiles the regex when registering routes, so the pattern is matched (not recompiled) on each request. ```go app.Get(`/:date`, func(c fiber.Ctx) error { @@ -264,7 +383,7 @@ app.Get(`/:date`, func(c fiber.Ctx) error { :::caution -Prefix routing characters with `\\` when using the datetime constraint (`*`, `+`, `?`, `:`, `/`, `<`, `>`, `;`, `(`, `)`), to avoid misparsing. +When using the datetime constraint, prefix routing characters (`*`, `+`, `?`, `:`, `/`, `<`, `>`, `;`, `(`, `)`) with `\\` to avoid misparsing. ::: #### Optional Parameter Example @@ -341,11 +460,9 @@ func main() { ## Middleware -Functions that are designed to make changes to the request or response are called **middleware functions**. The [Next](../api/ctx.md#next) is a **Fiber** router function, when called, executes the **next** function that **matches** the current route. +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. -### Example of a middleware function - -```go +```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") @@ -359,16 +476,16 @@ app.Get("/", func(c fiber.Ctx) error { }) ``` -`Use` method path is a **mount**, or **prefix** path, and limits middleware to only apply to any paths requested that begin with it. +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. -:::note -Prefix matches must now end at a slash boundary (or be an exact match). For example, `/api` runs for `/api` and `/api/users` but no longer for `/apiv2`. Parameter tokens such as `:name`, `:name?`, `*`, and `+` are still expanded before this boundary check runs. -::: +### Use + + -### Constraints on Adding Routes Dynamically +### Adding or removing routes at runtime :::caution -Adding routes dynamically after the application has started is not supported due to design and performance considerations. Make sure to define all your routes before the application starts. +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 @@ -393,4 +510,73 @@ func main() { } ``` -More information about this in our [Grouping Guide](./grouping.md) +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. An `All` in the chain runs before the verb handlers 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`. + +## 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. + +```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. +``` + +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 + + diff --git a/docs/core/partials/routing/handler-types.md b/docs/core/partials/routing/handler-types.md new file mode 100644 index 000000000000..8b27afa1337e --- /dev/null +++ b/docs/core/partials/routing/handler-types.md @@ -0,0 +1,80 @@ +--- +id: handler-types +title: 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 is rejected when the route is registered. 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` +- **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) +app.Get("/bar", func(ctx *fasthttp.RequestCtx) { + ctx.SetStatusCode(fiber.StatusAccepted) +}) + +// ...or the error-returning variant (case 17) +app.Get("/baz", func(ctx *fasthttp.RequestCtx) error { + ctx.SetStatusCode(fiber.StatusAccepted) + return nil +}) +``` diff --git a/docs/core/partials/routing/handler.md b/docs/core/partials/routing/handler.md index b2b2fea7d69a..6baa6414b414 100644 --- a/docs/core/partials/routing/handler.md +++ b/docs/core/partials/routing/handler.md @@ -3,9 +3,7 @@ id: route-handlers title: Route Handlers --- -import Reference from '@site/src/components/reference'; - -Registers a route bound to a specific [HTTP method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods). +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 @@ -19,152 +17,23 @@ 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 -// Add allows you to specify multiple methods at once -// The provided handlers are executed in order, starting with `handler` and then the variadic `handlers`. +// 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 will register the route on all HTTP methods -// Almost the same as app.Use but not bound to prefixes +// 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 ``` -Fiber's adapter converts a variety of handler shapes to native -`func(fiber.Ctx) error` callbacks. It currently recognizes seventeen cases (the -numbers below match the comments in `toFiberHandler` inside `adapter.go`). 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. - -### 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` -- **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" -// Simple GET handler (Fiber accepts both func(fiber.Ctx) and func(fiber.Ctx) error) +// Simple GET handler app.Get("/api/list", func(c fiber.Ctx) error { return c.SendString("I'm a GET request!") }) -// 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 -app.Get("/bar", func(ctx *fasthttp.RequestCtx) { - ctx.SetStatusCode(fiber.StatusAccepted) -}) - // Simple POST handler app.Post("/api/register", func(c fiber.Ctx) error { return c.SendString("I'm a POST request!") }) ``` - -## Use - -Can be used for middleware packages and prefix catchers. Prefixes now 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. - -```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) -``` - -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 -app.Use("/api", func(c fiber.Ctx) error { - c.Set("X-Custom-Header", random.String(32)) - return c.Next() -}, func(c fiber.Ctx) error { - return c.Next() -}) - -// Mount a sub-app -app.Use("/api", api) -``` diff --git a/docs/core/partials/routing/use.md b/docs/core/partials/routing/use.md new file mode 100644 index 000000000000..a5ddedd0eee5 --- /dev/null +++ b/docs/core/partials/routing/use.md @@ -0,0 +1,46 @@ +--- +id: route-use +title: 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) +``` + +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) +``` diff --git a/src/components/route-anatomy/index.tsx b/src/components/route-anatomy/index.tsx new file mode 100644 index 000000000000..f59aa3e85f8e --- /dev/null +++ b/src/components/route-anatomy/index.tsx @@ -0,0 +1,53 @@ +import React from "react"; +import Link from "@docusaurus/Link"; +import styles from "./styles.module.css"; + +// RouteAnatomy renders a multi-line Fiber route declaration where each +// meaningful part is color-coded (matching the Go syntax theme) and links to +// the guide section that explains it. Hovering a part shows its role; clicking +// jumps to that section. +// +// Separators (parentheses, quotes, comma, body) are explicit string +// expressions so the surrounding `white-space: pre` does not pick up stray +// whitespace from the JSX layout. +export default function RouteAnatomy(): JSX.Element { + return ( +
+            
+                app.Get
+            
+            {'("'}
+            
+                /users/
+            
+            
+                :id
+            
+            {'", '}
+            
+                func(c fiber.Ctx) error
+            
+            {' {\n    return c.SendString("GET /users")\n})'}
+        
+ ); +} diff --git a/src/components/route-anatomy/styles.module.css b/src/components/route-anatomy/styles.module.css new file mode 100644 index 000000000000..c44279463a28 --- /dev/null +++ b/src/components/route-anatomy/styles.module.css @@ -0,0 +1,70 @@ +.anatomy { + display: block; + margin: 1.5rem 0; + padding: 1.5rem 1.75rem; + border-radius: 10px; + background: #f6f8fa; + color: #393a34; + font-family: var(--ifm-font-family-monospace); + font-size: 1.15rem; + line-height: 1.9; + overflow-x: auto; + white-space: pre; + border: 1px solid var(--ifm-color-emphasis-200); + box-shadow: var(--ifm-global-shadow-lw); +} + +[data-theme="dark"] .anatomy { + background: #282a36; + color: #f8f8f2; + border-color: var(--ifm-color-emphasis-300); +} + +.anatomy a { + font-weight: 600; + text-decoration: none; + border-bottom: 2px dashed currentcolor; +} + +.anatomy a:hover { + border-bottom-style: solid; +} + +/* + * Part colors mirror the syntax themes configured in docusaurus.config.ts: + * light mode uses the GitHub Prism theme, dark mode uses Dracula. Each part is + * colored like the matching Go token (method -> function, path -> string, + * parameter -> property, handler -> keyword) so the anatomy reads like a real + * code block in both color modes. + */ +.method { + color: #d73a49; +} + +.path { + color: #e3116c; +} + +.param { + color: #36acaa; +} + +.handler { + color: #00009f; +} + +[data-theme="dark"] .method { + color: #50fa7b; +} + +[data-theme="dark"] .path { + color: #f1fa8c; +} + +[data-theme="dark"] .param { + color: #bd93f9; +} + +[data-theme="dark"] .handler { + color: #ff79c6; +} diff --git a/versioned_docs/version-v3.x/api/app.md b/versioned_docs/version-v3.x/api/app.md index c595aae8574c..94cabbc93c89 100644 --- a/versioned_docs/version-v3.x/api/app.md +++ b/versioned_docs/version-v3.x/api/app.md @@ -3,58 +3,29 @@ id: app title: 🚀 App description: The `App` type represents your Fiber application. sidebar_position: 2 +toc_max_heading_level: 4 --- import Reference from '@site/src/components/reference'; -## 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. 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 no view engine is configured or reloading fails. - -```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") -}) -``` - ## Routing import RoutingHandler from './../partials/routing/handler.md'; +import RoutingUse from './../partials/routing/use.md'; ### Route Handlers +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 + + + ### Mounting -Mount another Fiber instance with [`app.Use`](./app.md#use), similar to Express's [`router.use`](https://expressjs.com/en/api.html#router.use). +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 @@ -80,17 +51,9 @@ func main() { } ``` -### 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. +:::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`); there is no `req.baseUrl` equivalent. +::: ### MountPath @@ -223,6 +186,7 @@ func main() { 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") @@ -375,6 +339,7 @@ package main import ( "encoding/json" + "fmt" "log" "github.com/gofiber/fiber/v3" @@ -660,7 +625,7 @@ 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. +`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 @@ -798,6 +763,18 @@ Use `SetTLSHandler` to set [`ClientHelloInfo`](https://datatracker.ietf.org/doc/ func (app *App) SetTLSHandler(tlsHandler *TLSHandler) ``` +## 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`. @@ -855,8 +832,7 @@ config := fiber.TestConfig{ :::caution -This is **not** the same as supplying an empty `TestConfig{}` to -`app.Test(), but rather be the equivalent of supplying: +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{ @@ -877,7 +853,11 @@ This would make a Test that has no timeout. func (app *App) Hooks() *Hooks ``` -## RebuildTree +## 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. @@ -887,8 +867,6 @@ 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. -### Example Usage - Here’s an example of how to define and register routes dynamically: ```go title="Example" @@ -921,7 +899,7 @@ func main() { In this example, a new route is defined and then `RebuildTree()` is called to ensure the new route is registered and available. -## RemoveRoute +### 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. @@ -969,7 +947,7 @@ func main() { } ``` -## RemoveRouteByName +### 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. @@ -978,7 +956,7 @@ If no methods are specified, the route will be removed for all HTTP methods defi func (app *App) RemoveRouteByName(name string, methods ...string) ``` -## RemoveRouteFunc +### RemoveRouteFunc This method removes a route by function having `*Route` parameter. 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. @@ -986,3 +964,40 @@ If no methods are specified, the route will be removed for all HTTP methods defi ```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. 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 no view engine is configured or reloading fails. + +```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") +}) +``` diff --git a/versioned_docs/version-v3.x/guide/grouping.md b/versioned_docs/version-v3.x/guide/grouping.md index 272613f493b2..282b26bf8ec1 100644 --- a/versioned_docs/version-v3.x/guide/grouping.md +++ b/versioned_docs/version-v3.x/guide/grouping.md @@ -81,3 +81,29 @@ func main() { 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. An `All` in the chain runs before the verb handlers 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. +::: diff --git a/versioned_docs/version-v3.x/guide/routing.md b/versioned_docs/version-v3.x/guide/routing.md index bc3242098331..86052bce6497 100644 --- a/versioned_docs/version-v3.x/guide/routing.md +++ b/versioned_docs/version-v3.x/guide/routing.md @@ -11,54 +11,166 @@ toc_max_heading_level: 4 import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import RoutingHandler from './../partials/routing/handler.md'; +import RoutingUse from './../partials/routing/use.md'; +import RoutingHandlerTypes from './../partials/routing/handler-types.md'; +import RouteAnatomy from '@site/src/components/route-anatomy'; -## Handlers +## 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. + +## Route Handlers -## Automatic HEAD routes +Here is a complete, runnable app for context: -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. +```go title="A minimal Fiber app" +package main -```go title="GET handlers automatically expose HEAD" -app := fiber.New() +import "github.com/gofiber/fiber/v3" -app.Get("/users/:id", func(c fiber.Ctx) error { - c.Set("X-User", c.Params("id")) - return c.SendStatus(fiber.StatusOK) +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. + + + + +```go +app.Get("/users", func(c fiber.Ctx) error { + return c.SendString("GET /users") }) -// HEAD /users/:id now returns the same headers and status without a body. +// GET /users -> "GET /users" +// POST /users -> 405 Method Not Allowed +// GET /users/42 -> 404 Not Found (exact match only) ``` -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) +```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) ``` -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) -} +```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() +}) -app := fiber.New(fiber.Config{DisableHeadAutoRegister: true}) -app.Get("/users/:id", handler) // HEAD /users/:id now returns 405 unless you add it manually. +// 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 ``` -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. + + + +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 +``` + + + + +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**; a path that matches no route at all (including one rejected by a [constraint](#constraints)) returns **404 Not Found**. ## Paths A route path paired with an HTTP method defines an endpoint. It can be a plain **string** or a **pattern**. -### Examples of route paths based on strings - ```go // This route path will match requests to the root route, "/": app.Get("/", func(c fiber.Ctx) error { @@ -76,8 +188,7 @@ app.Get("/random.txt", func(c fiber.Ctx) error { }) ``` -As with the Express.js framework, the order in which routes are declared matters. -Routes are evaluated sequentially, so more specific paths should appear before those with variables. +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. @@ -87,49 +198,39 @@ Place routes with variable parameters after fixed paths to avoid unintended matc 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. - -Use `*` or `+` to capture segments greedily. - -You can define optional parameters by appending `?` to a named segment. The `+` sign is greedy and required, while `*` acts as an optional greedy wildcard. +The characters `:`, `+`, and `*` introduce parameters. Append `?` to a named segment 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). -### Example of defining routes with route parameters + + ```go -// Parameters +// 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 - not optional + +// Plus - greedy, required (matches at least one character) app.Get("/user/+", func(c fiber.Ctx) error { return c.SendString(c.Params("+")) }) -// Optional parameter +// Optional named parameter app.Get("/user/:name?", func(c fiber.Ctx) error { return c.SendString(c.Params("name")) }) -// Wildcard - greedy - optional +// Wildcard - greedy, optional (may match nothing) app.Get("/user/*", func(c fiber.Ctx) error { return c.SendString(c.Params("*")) }) - -// This route path will match requests to "/v1/some/resource/name:customVerb", since the parameter character is escaped -app.Get(`/v1/some/resource/name\:customVerb`, func(c fiber.Ctx) error { - return c.SendString("Hello, Community") -}) ``` -:::info -The hyphen \(`-`\) and dot \(`.`\) are treated literally, so you can combine them with route parameters. -::: + + -:::info -Escape special parameter characters with `\\` to treat them literally. This technique 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. -::: +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 @@ -137,19 +238,13 @@ 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 }) -``` -```go // 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 }) -``` - -Fiber's router detects when these characters belong to the literal path and handles them accordingly. -```go // 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")) @@ -157,7 +252,22 @@ app.Get("/shop/product/color::color/size::size", func(c fiber.Ctx) error { }) ``` -You can chain multiple named or unnamed parameters—including wildcard and plus segments—giving the router greater flexibility. + + + +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") +}) +``` + + + + +You can chain multiple named or unnamed parameters, including wildcard and plus segments, within a single segment. ```go // GET /@v1 @@ -177,14 +287,23 @@ app.Get("/*v1*/proxy", handler) app.Get("/v1/*/shop/*", handler) ``` -Fiber's routing is inspired by Express but intentionally omits regular expression routes due to their performance cost. You can try similar patterns using the Express route tester (v0.1.7). +:::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, each leading one captures a single character and the last captures the rest. This does not raise an error, so an unexpected pattern silently captures differently than you might assume. +::: + + + + +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 aren't validation for parameters. If constraints aren't valid for a parameter value, Fiber returns **404 handler**. +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 | @@ -192,9 +311,9 @@ Constraints aren't validation for parameters. If constraints aren't valid for a | int | `:id` | 123456789, -123456789 | | bool | `:active` | true,false | | guid | `:id` | CD2C1638-1638-72D5-1638-DEADBEEF1638 | -| float | `:weight` | 1.234, -1,001.01e8 | +| float | `:weight` | 1.234, -1001.01e8, 3.14 | | minLen(value) | `:username` | Test (must be at least 4 characters) | -| maxLen(value) | `:filename` | MyFile (must be no more than 8 characters | +| maxLen(value) | `:filename` | MyFile (must be no more than 8 characters) | | len(length) | `:filename` | somefile.txt (exactly 12 characters) | | min(value) | `:age` | 19 (Integer value must be at least 18) | | max(value) | `:age` | 91 (Integer value must be no more than 120) | @@ -243,7 +362,7 @@ app.Get("/:test", func(c fiber.Ctx) error {
-Fiber precompiles the regex when registering routes, so regex constraints add no runtime overhead. +Fiber precompiles the regex when registering routes, so the pattern is matched (not recompiled) on each request. ```go app.Get(`/:date`, func(c fiber.Ctx) error { @@ -264,7 +383,7 @@ app.Get(`/:date`, func(c fiber.Ctx) error { :::caution -Prefix routing characters with `\\` when using the datetime constraint (`*`, `+`, `?`, `:`, `/`, `<`, `>`, `;`, `(`, `)`), to avoid misparsing. +When using the datetime constraint, prefix routing characters (`*`, `+`, `?`, `:`, `/`, `<`, `>`, `;`, `(`, `)`) with `\\` to avoid misparsing. ::: #### Optional Parameter Example @@ -341,11 +460,9 @@ func main() { ## Middleware -Functions that are designed to make changes to the request or response are called **middleware functions**. The [Next](../api/ctx.md#next) is a **Fiber** router function, when called, executes the **next** function that **matches** the current route. +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. -### Example of a middleware function - -```go +```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") @@ -359,16 +476,16 @@ app.Get("/", func(c fiber.Ctx) error { }) ``` -`Use` method path is a **mount**, or **prefix** path, and limits middleware to only apply to any paths requested that begin with it. +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. -:::note -Prefix matches must now end at a slash boundary (or be an exact match). For example, `/api` runs for `/api` and `/api/users` but no longer for `/apiv2`. Parameter tokens such as `:name`, `:name?`, `*`, and `+` are still expanded before this boundary check runs. -::: +### Use + + -### Constraints on Adding Routes Dynamically +### Adding or removing routes at runtime :::caution -Adding routes dynamically after the application has started is not supported due to design and performance considerations. Make sure to define all your routes before the application starts. +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 @@ -393,4 +510,73 @@ func main() { } ``` -More information about this in our [Grouping Guide](./grouping.md) +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. An `All` in the chain runs before the verb handlers 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`. + +## 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. + +```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. +``` + +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 + + diff --git a/versioned_docs/version-v3.x/partials/routing/handler-types.md b/versioned_docs/version-v3.x/partials/routing/handler-types.md new file mode 100644 index 000000000000..8b27afa1337e --- /dev/null +++ b/versioned_docs/version-v3.x/partials/routing/handler-types.md @@ -0,0 +1,80 @@ +--- +id: handler-types +title: 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 is rejected when the route is registered. 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` +- **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) +app.Get("/bar", func(ctx *fasthttp.RequestCtx) { + ctx.SetStatusCode(fiber.StatusAccepted) +}) + +// ...or the error-returning variant (case 17) +app.Get("/baz", func(ctx *fasthttp.RequestCtx) error { + ctx.SetStatusCode(fiber.StatusAccepted) + return nil +}) +``` diff --git a/versioned_docs/version-v3.x/partials/routing/handler.md b/versioned_docs/version-v3.x/partials/routing/handler.md index b2b2fea7d69a..6baa6414b414 100644 --- a/versioned_docs/version-v3.x/partials/routing/handler.md +++ b/versioned_docs/version-v3.x/partials/routing/handler.md @@ -3,9 +3,7 @@ id: route-handlers title: Route Handlers --- -import Reference from '@site/src/components/reference'; - -Registers a route bound to a specific [HTTP method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods). +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 @@ -19,152 +17,23 @@ 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 -// Add allows you to specify multiple methods at once -// The provided handlers are executed in order, starting with `handler` and then the variadic `handlers`. +// 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 will register the route on all HTTP methods -// Almost the same as app.Use but not bound to prefixes +// 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 ``` -Fiber's adapter converts a variety of handler shapes to native -`func(fiber.Ctx) error` callbacks. It currently recognizes seventeen cases (the -numbers below match the comments in `toFiberHandler` inside `adapter.go`). 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. - -### 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` -- **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" -// Simple GET handler (Fiber accepts both func(fiber.Ctx) and func(fiber.Ctx) error) +// Simple GET handler app.Get("/api/list", func(c fiber.Ctx) error { return c.SendString("I'm a GET request!") }) -// 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 -app.Get("/bar", func(ctx *fasthttp.RequestCtx) { - ctx.SetStatusCode(fiber.StatusAccepted) -}) - // Simple POST handler app.Post("/api/register", func(c fiber.Ctx) error { return c.SendString("I'm a POST request!") }) ``` - -## Use - -Can be used for middleware packages and prefix catchers. Prefixes now 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. - -```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) -``` - -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 -app.Use("/api", func(c fiber.Ctx) error { - c.Set("X-Custom-Header", random.String(32)) - return c.Next() -}, func(c fiber.Ctx) error { - return c.Next() -}) - -// Mount a sub-app -app.Use("/api", api) -``` diff --git a/versioned_docs/version-v3.x/partials/routing/use.md b/versioned_docs/version-v3.x/partials/routing/use.md new file mode 100644 index 000000000000..a5ddedd0eee5 --- /dev/null +++ b/versioned_docs/version-v3.x/partials/routing/use.md @@ -0,0 +1,46 @@ +--- +id: route-use +title: 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) +``` + +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) +```