diff --git a/README.md b/README.md
index 56f84fc..2aaf020 100644
--- a/README.md
+++ b/README.md
@@ -22,12 +22,10 @@ It keeps req's power and escape hatches, while making the 90% use case feel effo
-## v2 Status
-
-httpx v1 has been tagged and is now frozen. The `main` branch is v2, which includes intentional breaking changes to improve API clarity and ergonomics (for example, request helpers return `(T, error)`).
-
## Why httpx
+- Developer-first ergonomics: fast to read, fast to write, predictable to use.
+- Reliability by default: sensible timeouts, safe error handling, resilient retries.
- Typed, zero-ceremony requests with generics.
- Opinionated defaults (timeouts, result handling, safe error mapping).
- Built on req, with full escape hatches via `Client.Req()` and `Client.Raw()`.
@@ -42,58 +40,71 @@ go get github.com/goforj/httpx
## Quick Start
```go
-package main
-
-import (
- "context"
- "fmt"
- "net/http"
+type User struct {
+ ID int `json:"id"`
+ Name string `json:"name"`
+}
- "github.com/goforj/httpx"
- "github.com/imroc/req/v3"
+c := httpx.New(
+ httpx.BaseURL("https://api.example.com"),
+ httpx.Bearer("token"),
+ // options...
)
-func main() {
- c := httpx.New()
-
- // Simple typed GET.
- res, err := httpx.Get[map[string]any](c, "https://httpbin.org/get")
- if err != nil {
- panic(err)
- }
- fmt.Println(res)
-
- // Context-aware GET.
- ctx := context.Background()
- res2, err := httpx.GetCtx[map[string]any](c, ctx, "https://httpbin.org/get")
- if err != nil {
- panic(err)
- }
- fmt.Println(res2)
-
- // Access the underlying response when you need it.
- r := req.C().R()
- r.SetURL("https://httpbin.org/get")
- r.Method = http.MethodGet
- res3, resp, err := httpx.Do[map[string]any](r)
- if err != nil {
- panic(err)
- }
- fmt.Println(res3)
- fmt.Println(resp.Status)
-}
+// httpx.Get[T](client, url, options...)
+user, _ := httpx.Get[User](c, "/users/42", httpx.Query("include", "profile"))
+fmt.Println(user.Name)
```
-## v2 Error Handling
+All request helpers return `(T, error)`. The low-level escape hatch `Do` returns `(T, *req.Response, error)` when you need raw access.
+
+## Core Surface (use these first)
-httpx v2 returns errors as the second return value from request helpers.
+- Requests: `Get / Post / Put / Patch / Delete` (+ `*Ctx`)
+- Composition: `Query`, `Path`, `Header`, `JSON`
+- Client defaults: `New(...)` with BaseURL, auth, timeouts
+- Resiliency: `Retry*`
+- Debug: `Dump`
+- Power-user: `Req` / `Raw` escape hatch
+
+## Most Common Tasks
+
+Brevity-first, idiomatic patterns. Handle errors; dump what matters.
+
+Singular helpers (`Query`, `Path`, `Header`) set one value; plural helpers (`Queries`, `Paths`, `Headers`) set many at once.
```go
-res, err := httpx.Get[map[string]any](c, "https://httpbin.org/get")
-if err != nil {
- return err
+type GetResponse struct {
+ URL string `json:"url"`
+}
+type CreateUser struct {
+ Name string `json:"name"`
+}
+type CreateUserResponse struct {
+ JSON CreateUser `json:"json"`
}
-_ = res
+
+c := httpx.New()
+
+// Simple request
+res, _ := httpx.Get[GetResponse](c, "https://httpbin.org/get")
+httpx.Dump(res) // URL => "https://httpbin.org/get"
+
+// POST JSON with typed request/response
+resPost, _ := httpx.Post[CreateUser, CreateUserResponse](c, "https://httpbin.org/post", CreateUser{Name: "Ana"})
+httpx.Dump(resPost) // JSON.Name => "Ana"
+
+// Passing a header option
+res, _ = httpx.Get[map[string]any](c, "https://httpbin.org/headers", httpx.Header("X-Test", "true"))
+httpx.Dump(res) // headers.X-Test => "true"
+
+// Passing query params
+res, _ = httpx.Get[map[string]any](c, "https://httpbin.org/get", httpx.Query("q", "search"))
+httpx.Dump(res) // args.q => "search"
+
+// File upload
+resUpload, _ := httpx.Post[any, map[string]any](c, "https://httpbin.org/post", nil, httpx.File("file", "./report.txt"))
+httpx.Dump(resUpload) // files.file => "...report.txt"
```
## Browser Profiles
@@ -138,11 +149,12 @@ res, err := httpx.Get[map[string]any](
httpx.Path("id", "42"),
httpx.Query("include", "teams", "active", "1"),
httpx.Header("Accept", "application/json"),
+ // stack more options...
)
if err != nil {
- panic(err)
+ // handle error
}
-_ = res
+httpx.Dump(res) // dumps map[string]any
```
## Debugging and Tracing
@@ -162,6 +174,10 @@ They are compiled by `example_compile_test.go` to keep docs and code in sync.
- Run `go run ./docs/readme/main.go` to refresh the API index and test count.
- Run `go test ./...`.
+## v2 Status
+
+httpx v1 has been tagged and is now frozen. The `main` branch is v2, which includes intentional breaking changes to improve API clarity and ergonomics (for example, request helpers return `(T, error)`).
+
## API Index
@@ -722,7 +738,7 @@ httpx.Dump(res) // dumps map[string]any
### JSON
-JSON sets the request body as JSON.
+JSON forces JSON encoding for the request body (even when inference might choose differently).
```go
type Payload struct {
@@ -890,6 +906,7 @@ httpx.Dump(res) // dumps DeleteResponse
### Do
Do executes a pre-configured req request and returns the decoded body and response.
+This is the low-level escape hatch when you need full req control.
```go
r := req.C().R().SetHeader("X-Trace", "1")
@@ -921,10 +938,7 @@ type GetResponse struct {
}
c := httpx.New()
-res, err := httpx.Get[GetResponse](c, "https://httpbin.org/get")
-if err != nil {
- return
-}
+res, _ := httpx.Get[GetResponse](c, "https://httpbin.org/get")
httpx.Dump(res)
// #GetResponse {
// URL => "https://httpbin.org/get" #string
@@ -934,11 +948,8 @@ httpx.Dump(res)
_Example: bind to a string body_
```go
-resText, err := httpx.Get[string](c, "https://httpbin.org/uuid")
-if err != nil {
- return
-}
-println(resText) // dumps string
+resString, _ := httpx.Get[string](c, "https://httpbin.org/uuid")
+println(resString) // dumps string
// {
// "uuid": "becbda6d-9950-4966-ae23-0369617ba065"
// }
diff --git a/client.go b/client.go
index 79c1e72..b4d5400 100644
--- a/client.go
+++ b/client.go
@@ -118,10 +118,7 @@ func (c *Client) clone() *Client {
// }
//
// c := httpx.New()
-// res, err := httpx.Get[GetResponse](c, "https://httpbin.org/get")
-// if err != nil {
-// return
-// }
+// res, _ := httpx.Get[GetResponse](c, "https://httpbin.org/get")
// httpx.Dump(res)
// // #GetResponse {
// // URL => "https://httpbin.org/get" #string
@@ -129,11 +126,8 @@ func (c *Client) clone() *Client {
//
// Example: bind to a string body
//
-// resText, err := httpx.Get[string](c, "https://httpbin.org/uuid")
-// if err != nil {
-// return
-// }
-// println(resText) // dumps string
+// resString, _ := httpx.Get[string](c, "https://httpbin.org/uuid")
+// println(resString) // dumps string
// // {
// // "uuid": "becbda6d-9950-4966-ae23-0369617ba065"
// // }
@@ -447,6 +441,7 @@ func OptionsCtx[T any](client *Client, ctx context.Context, url string, opts ...
}
// Do executes a pre-configured req request and returns the decoded body and response.
+// This is the low-level escape hatch when you need full req control.
// @group Requests
//
// Example: advanced request with response access
diff --git a/examples/do/main.go b/examples/do/main.go
index 6885aac..33663b3 100644
--- a/examples/do/main.go
+++ b/examples/do/main.go
@@ -11,6 +11,7 @@ import (
func main() {
// Do executes a pre-configured req request and returns the decoded body and response.
+ // This is the low-level escape hatch when you need full req control.
// Example: advanced request with response access
r := req.C().R().SetHeader("X-Trace", "1")
diff --git a/examples/get/main.go b/examples/get/main.go
index 1484d59..43d8e0c 100644
--- a/examples/get/main.go
+++ b/examples/get/main.go
@@ -14,21 +14,15 @@ func main() {
}
c := httpx.New()
- res, err := httpx.Get[GetResponse](c, "https://httpbin.org/get")
- if err != nil {
- return
- }
+ res, _ := httpx.Get[GetResponse](c, "https://httpbin.org/get")
httpx.Dump(res)
// #GetResponse {
// URL => "https://httpbin.org/get" #string
// }
// Example: bind to a string body
- resText, err := httpx.Get[string](c, "https://httpbin.org/uuid")
- if err != nil {
- return
- }
- println(resText) // dumps string
+ resString, _ := httpx.Get[string](c, "https://httpbin.org/uuid")
+ println(resString) // dumps string
// {
// "uuid": "becbda6d-9950-4966-ae23-0369617ba065"
// }
diff --git a/examples/json/main.go b/examples/json/main.go
index bc278d6..2a28b5a 100644
--- a/examples/json/main.go
+++ b/examples/json/main.go
@@ -6,7 +6,7 @@ package main
import "github.com/goforj/httpx"
func main() {
- // JSON sets the request body as JSON.
+ // JSON forces JSON encoding for the request body (even when inference might choose differently).
// Example: force JSON body
type Payload struct {
diff --git a/options_request.go b/options_request.go
index 506d5f1..8206bbb 100644
--- a/options_request.go
+++ b/options_request.go
@@ -278,7 +278,7 @@ func (b OptionBuilder) Body(value any) OptionBuilder {
}))
}
-// JSON sets the request body as JSON.
+// JSON forces JSON encoding for the request body (even when inference might choose differently).
// @group Request Composition
//
// Applies to individual requests only.