From 93b6c36e49bb7226f105a8f7f7e12a82ceb7d663 Mon Sep 17 00:00:00 2001
From: Chris Miles
Date: Mon, 29 Dec 2025 20:41:02 -0600
Subject: [PATCH 1/9] docs: cleanup
---
README.md | 77 +++++++++++++++++++++++++++++++++++++++------
client.go | 1 +
examples/do/main.go | 1 +
3 files changed, 70 insertions(+), 9 deletions(-)
diff --git a/README.md b/README.md
index 56f84fc..e23cc0a 100644
--- a/README.md
+++ b/README.md
@@ -47,6 +47,7 @@ package main
import (
"context"
"fmt"
+ "log"
"net/http"
"github.com/goforj/httpx"
@@ -54,22 +55,26 @@ import (
)
func main() {
- c := httpx.New()
+ type GetResponse struct {
+ URL string `json:"url"`
+ }
+
+ c := httpx.New(httpx.UserAgent("demo/1.0"))
// Simple typed GET.
- res, err := httpx.Get[map[string]any](c, "https://httpbin.org/get")
+ res, err := httpx.Get[GetResponse](c, "https://httpbin.org/get")
if err != nil {
- panic(err)
+ log.Fatal(err)
}
- fmt.Println(res)
+ fmt.Println(res.URL)
// Context-aware GET.
ctx := context.Background()
- res2, err := httpx.GetCtx[map[string]any](c, ctx, "https://httpbin.org/get")
+ res2, err := httpx.GetCtx[GetResponse](c, ctx, "https://httpbin.org/get")
if err != nil {
- panic(err)
+ log.Fatal(err)
}
- fmt.Println(res2)
+ fmt.Println(res2.URL)
// Access the underlying response when you need it.
r := req.C().R()
@@ -77,11 +82,64 @@ func main() {
r.Method = http.MethodGet
res3, resp, err := httpx.Do[map[string]any](r)
if err != nil {
- panic(err)
+ log.Fatal(err)
}
fmt.Println(res3)
- fmt.Println(resp.Status)
+ fmt.Println(resp.StatusCode)
+}
+```
+
+## Most Common Tasks
+
+Brevity-first, idiomatic patterns. Handle errors; dump what matters.
+
+```go
+type GetResponse struct {
+ URL string `json:"url"`
+}
+
+c := httpx.New()
+
+// Typed GET
+res, err := httpx.Get[GetResponse](c, "https://httpbin.org/get")
+if err != nil {
+ // handler error
+}
+httpx.Dump(res) // URL => "https://httpbin.org/get"
+
+// POST JSON (typed request/response)
+type CreateUser struct {
+ Name string `json:"name"`
+}
+type CreateUserResponse struct {
+ JSON CreateUser `json:"json"`
+}
+resPost, err := httpx.Post[CreateUser, CreateUserResponse](c, "https://httpbin.org/post", CreateUser{Name: "Ana"})
+if err != nil {
+ panic(err)
+}
+httpx.Dump(resPost) // JSON.Name => "Ana"
+
+// Headers
+res, err = httpx.Get[map[string]any](c, "https://httpbin.org/headers", httpx.Header("X-Test", "true"))
+if err != nil {
+ panic(err)
+}
+httpx.Dump(res) // headers.X-Test => "true"
+
+// Query params
+res, err = httpx.Get[map[string]any](c, "https://httpbin.org/get", httpx.Query("q", "search"))
+if err != nil {
+ panic(err)
+}
+httpx.Dump(res) // args.q => "search"
+
+// File upload
+resUpload, err := httpx.Post[any, map[string]any](c, "https://httpbin.org/post", nil, httpx.File("file", "./report.txt"))
+if err != nil {
+ panic(err)
}
+httpx.Dump(resUpload) // files.file => "...report.txt"
```
## v2 Error Handling
@@ -890,6 +948,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")
diff --git a/client.go b/client.go
index 79c1e72..b389e90 100644
--- a/client.go
+++ b/client.go
@@ -447,6 +447,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")
From 31b6c46bc3389b9edc5ed5110ab8b49b82e73829 Mon Sep 17 00:00:00 2001
From: Chris Miles
Date: Mon, 29 Dec 2025 20:48:39 -0600
Subject: [PATCH 2/9] docs: cleanup
---
README.md | 124 ++++++++++++++++++------------------------------------
1 file changed, 42 insertions(+), 82 deletions(-)
diff --git a/README.md b/README.md
index e23cc0a..5bd609a 100644
--- a/README.md
+++ b/README.md
@@ -26,6 +26,17 @@ It keeps req's power and escape hatches, while making the 90% use case feel effo
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)`).
+## The 90% You Need
+
+Learn these first—most users never need more:
+
+1. `Get / Post / Put / Patch / Delete` (+ `*Ctx` variants)
+2. `Query`, `Path`, `Header`, `JSON`
+3. `New(...)` client options (BaseURL, auth, timeouts)
+4. `Retry*` (when you need resiliency)
+5. `Dump` (quick debugging)
+6. `Req`/`Raw` escape hatch when you outgrow the helpers
+
## Why httpx
- Typed, zero-ceremony requests with generics.
@@ -42,118 +53,67 @@ go get github.com/goforj/httpx
## Quick Start
```go
-package main
-
-import (
- "context"
- "fmt"
- "log"
- "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"),
)
-func main() {
- type GetResponse struct {
- URL string `json:"url"`
- }
-
- c := httpx.New(httpx.UserAgent("demo/1.0"))
-
- // Simple typed GET.
- res, err := httpx.Get[GetResponse](c, "https://httpbin.org/get")
- if err != nil {
- log.Fatal(err)
- }
- fmt.Println(res.URL)
-
- // Context-aware GET.
- ctx := context.Background()
- res2, err := httpx.GetCtx[GetResponse](c, ctx, "https://httpbin.org/get")
- if err != nil {
- log.Fatal(err)
- }
- fmt.Println(res2.URL)
-
- // 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 {
- log.Fatal(err)
- }
- fmt.Println(res3)
- fmt.Println(resp.StatusCode)
+user, err := httpx.Get[User](c, "/users/42", httpx.Query("include", "profile"))
+if err != nil {
+ log.Fatal(err)
}
+
+fmt.Println(user.Name)
```
+All request helpers return `(T, error)`. The low-level escape hatch `Do` returns `(T, *req.Response, error)` when you need raw access.
+
## 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
type GetResponse struct {
URL string `json:"url"`
}
-
-c := httpx.New()
-
-// Typed GET
-res, err := httpx.Get[GetResponse](c, "https://httpbin.org/get")
-if err != nil {
- // handler error
-}
-httpx.Dump(res) // URL => "https://httpbin.org/get"
-
-// POST JSON (typed request/response)
type CreateUser struct {
Name string `json:"name"`
}
type CreateUserResponse struct {
JSON CreateUser `json:"json"`
}
-resPost, err := httpx.Post[CreateUser, CreateUserResponse](c, "https://httpbin.org/post", CreateUser{Name: "Ana"})
-if err != nil {
- panic(err)
+
+c := httpx.New()
+must := func[T any](v T, err error) T {
+ if err != nil {
+ log.Fatal(err)
+ }
+ return v
}
+
+res := must(httpx.Get[GetResponse](c, "https://httpbin.org/get"))
+httpx.Dump(res) // URL => "https://httpbin.org/get"
+
+resPost := must(httpx.Post[CreateUser, CreateUserResponse](c, "https://httpbin.org/post", CreateUser{Name: "Ana"}))
httpx.Dump(resPost) // JSON.Name => "Ana"
-// Headers
-res, err = httpx.Get[map[string]any](c, "https://httpbin.org/headers", httpx.Header("X-Test", "true"))
-if err != nil {
- panic(err)
-}
+res = must(httpx.Get[map[string]any](c, "https://httpbin.org/headers", httpx.Header("X-Test", "true")))
httpx.Dump(res) // headers.X-Test => "true"
-// Query params
-res, err = httpx.Get[map[string]any](c, "https://httpbin.org/get", httpx.Query("q", "search"))
-if err != nil {
- panic(err)
-}
+res = must(httpx.Get[map[string]any](c, "https://httpbin.org/get", httpx.Query("q", "search")))
httpx.Dump(res) // args.q => "search"
-// File upload
-resUpload, err := httpx.Post[any, map[string]any](c, "https://httpbin.org/post", nil, httpx.File("file", "./report.txt"))
-if err != nil {
- panic(err)
-}
+resUpload := must(httpx.Post[any, map[string]any](c, "https://httpbin.org/post", nil, httpx.File("file", "./report.txt")))
httpx.Dump(resUpload) // files.file => "...report.txt"
```
-## v2 Error Handling
-
-httpx v2 returns errors as the second return value from request helpers.
-
-```go
-res, err := httpx.Get[map[string]any](c, "https://httpbin.org/get")
-if err != nil {
- return err
-}
-_ = res
-```
-
## Browser Profiles
Browser profiles provide a simple way to match common client behavior without exposing low-level details.
From f1e0981a94cd0e5c7e3a15a8a0659fb9482a29f5 Mon Sep 17 00:00:00 2001
From: Chris Miles
Date: Mon, 29 Dec 2025 20:55:29 -0600
Subject: [PATCH 3/9] docs: cleanup
---
README.md | 40 +++++++++++++++-------------------------
1 file changed, 15 insertions(+), 25 deletions(-)
diff --git a/README.md b/README.md
index 5bd609a..9eb3a49 100644
--- a/README.md
+++ b/README.md
@@ -26,17 +26,6 @@ It keeps req's power and escape hatches, while making the 90% use case feel effo
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)`).
-## The 90% You Need
-
-Learn these first—most users never need more:
-
-1. `Get / Post / Put / Patch / Delete` (+ `*Ctx` variants)
-2. `Query`, `Path`, `Header`, `JSON`
-3. `New(...)` client options (BaseURL, auth, timeouts)
-4. `Retry*` (when you need resiliency)
-5. `Dump` (quick debugging)
-6. `Req`/`Raw` escape hatch when you outgrow the helpers
-
## Why httpx
- Typed, zero-ceremony requests with generics.
@@ -64,15 +53,22 @@ c := httpx.New(
)
user, err := httpx.Get[User](c, "/users/42", httpx.Query("include", "profile"))
-if err != nil {
- log.Fatal(err)
-}
+_ = err
fmt.Println(user.Name)
```
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)
+
+- 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.
@@ -91,26 +87,20 @@ type CreateUserResponse struct {
}
c := httpx.New()
-must := func[T any](v T, err error) T {
- if err != nil {
- log.Fatal(err)
- }
- return v
-}
-res := must(httpx.Get[GetResponse](c, "https://httpbin.org/get"))
+res, _ := httpx.Get[GetResponse](c, "https://httpbin.org/get")
httpx.Dump(res) // URL => "https://httpbin.org/get"
-resPost := must(httpx.Post[CreateUser, CreateUserResponse](c, "https://httpbin.org/post", CreateUser{Name: "Ana"}))
+resPost, _ := httpx.Post[CreateUser, CreateUserResponse](c, "https://httpbin.org/post", CreateUser{Name: "Ana"})
httpx.Dump(resPost) // JSON.Name => "Ana"
-res = must(httpx.Get[map[string]any](c, "https://httpbin.org/headers", httpx.Header("X-Test", "true")))
+res, _ = httpx.Get[map[string]any](c, "https://httpbin.org/headers", httpx.Header("X-Test", "true"))
httpx.Dump(res) // headers.X-Test => "true"
-res = must(httpx.Get[map[string]any](c, "https://httpbin.org/get", httpx.Query("q", "search")))
+res, _ = httpx.Get[map[string]any](c, "https://httpbin.org/get", httpx.Query("q", "search"))
httpx.Dump(res) // args.q => "search"
-resUpload := must(httpx.Post[any, map[string]any](c, "https://httpbin.org/post", nil, httpx.File("file", "./report.txt")))
+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"
```
From 01491436380b0d5e5fbf2092a615d5d788248fb2 Mon Sep 17 00:00:00 2001
From: Chris Miles
Date: Mon, 29 Dec 2025 21:13:04 -0600
Subject: [PATCH 4/9] docs: cleanup
---
README.md | 17 ++++++++---------
client.go | 12 +++---------
examples/get/main.go | 12 +++---------
3 files changed, 14 insertions(+), 27 deletions(-)
diff --git a/README.md b/README.md
index 9eb3a49..9d5792c 100644
--- a/README.md
+++ b/README.md
@@ -88,18 +88,23 @@ type CreateUserResponse struct {
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"
```
@@ -930,10 +935,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
@@ -943,11 +945,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 b389e90..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"
// // }
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"
// }
From d8088317021fb2190bdaac595f5e898efa4470df Mon Sep 17 00:00:00 2001
From: Chris Miles
Date: Mon, 29 Dec 2025 21:16:19 -0600
Subject: [PATCH 5/9] docs: cleanup
---
README.md | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
index 9d5792c..74ad305 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()`.
@@ -175,6 +173,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
From f8df0aca2a3dbef22cc6d353c5f03bce5390d88c Mon Sep 17 00:00:00 2001
From: Chris Miles
Date: Mon, 29 Dec 2025 21:17:39 -0600
Subject: [PATCH 6/9] docs: cleanup
---
README.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 74ad305..2246752 100644
--- a/README.md
+++ b/README.md
@@ -48,11 +48,11 @@ type User struct {
c := httpx.New(
httpx.BaseURL("https://api.example.com"),
httpx.Bearer("token"),
+ // options...
)
-user, err := httpx.Get[User](c, "/users/42", httpx.Query("include", "profile"))
-_ = err
-
+// httpx.Get[T](client, url, options...)
+user, _ := httpx.Get[User](c, "/users/42", httpx.Query("include", "profile"))
fmt.Println(user.Name)
```
From be315670cea5d6388152b02f517ab971477a60b8 Mon Sep 17 00:00:00 2001
From: Chris Miles
Date: Mon, 29 Dec 2025 21:26:10 -0600
Subject: [PATCH 7/9] docs: cleanup
---
README.md | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 2246752..bb98b14 100644
--- a/README.md
+++ b/README.md
@@ -149,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)
+ // handler error
}
-_ = res
+httpx.Dump(res) // dumps map[string]any
```
## Debugging and Tracing
From 14b8a60127ecaf6a21b88cba00e89eaf0ea0dbbf Mon Sep 17 00:00:00 2001
From: Chris Miles
Date: Mon, 29 Dec 2025 21:26:44 -0600
Subject: [PATCH 8/9] docs: cleanup
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index bb98b14..95ae1bc 100644
--- a/README.md
+++ b/README.md
@@ -152,7 +152,7 @@ res, err := httpx.Get[map[string]any](
// stack more options...
)
if err != nil {
- // handler error
+ // handle error
}
httpx.Dump(res) // dumps map[string]any
```
From 7df697242634310b9917a270de31ab06f6ebb040 Mon Sep 17 00:00:00 2001
From: Chris Miles
Date: Mon, 29 Dec 2025 21:30:52 -0600
Subject: [PATCH 9/9] docs: tweaks
---
README.md | 2 +-
examples/json/main.go | 2 +-
options_request.go | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 95ae1bc..2aaf020 100644
--- a/README.md
+++ b/README.md
@@ -738,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 {
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.