diff --git a/docs/whats_new.md b/docs/whats_new.md index b6b036a6827..054f6b68c29 100644 --- a/docs/whats_new.md +++ b/docs/whats_new.md @@ -846,6 +846,31 @@ The default redirect status code has been updated from `302 Found` to `303 See O 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") + +resp, err := cc.Get("/users/:tenant/:id", client.Config{ + PathParam: map[string]string{"id": "42"}, + Param: map[string]string{"include": "profile"}, + DisablePathNormalizing: true, +}) +if err != nil { + panic(err) +} +defer resp.Close() +fmt.Println(resp.StatusCode(), resp.String()) +``` + ### 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. @@ -2152,6 +2177,185 @@ 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. + ::: + +
+ ### 🛠️ 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.