A generics-first HTTP client wrapper for Go, built on top of the amazing github.com/imroc/req/v3 library.
It keeps req's power and escape hatches, while making the 90% use case feel effortless.
- Typed, zero-ceremony requests with generics.
- Opinionated defaults (timeouts, result handling, safe error mapping).
- Built on req, with full escape hatches via
Client.Req()andClient.Raw(). - Ergonomic options for headers, query params, auth, retries, dumps, and uploads.
go get github.com/goforj/httpxpackage main
import (
"context"
"fmt"
"github.com/goforj/httpx"
)
type User struct {
Name string `json:"name"`
}
func main() {
c := httpx.New()
// Simple typed GET.
res := httpx.Get[User](c, "https://api.example.com/users/1")
if res.Err != nil {
panic(res.Err)
}
fmt.Println(res.Body.Name)
// Context-aware GET.
ctx := context.Background()
res = httpx.GetCtx[User](c, ctx, "https://api.example.com/users/2")
if res.Err != nil {
panic(res.Err)
}
// Access the underlying response when you need it.
_ = res.Response
}c := httpx.New()
// Advanced req config.
c.Req().EnableDumpEachRequest()
// Drop down to raw req calls.
resp, err := c.Raw().R().Get("https://httpbin.org/uuid")
_, _ = resp, errc := httpx.New(httpx.BaseURL("https://api.example.com"))
res := httpx.Get[User](
c,
"/users/{id}",
httpx.Path("id", "42"),
httpx.Query("include", "teams", "active", "1"),
httpx.Header("Accept", "application/json"),
)
_ = resHTTP_TRACE=1enables request/response dumps for all requests.httpx.Dump()enables dump for a single request.httpx.DumpEachRequest()enables per-request dumps on a client.
All runnable examples are generated from doc comments and live in ./examples.
They are compiled by example_compile_test.go to keep docs and code in sync.
- Run
go run ./docs/examplegenafter updating doc examples. - Run
go run ./docs/readme/main.goto refresh the API index and test count. - Run
go test ./....
| Group | Functions |
|---|---|
| Auth | Auth Basic Bearer |
| Client | Default New Raw Req |
| Client Options | BaseURL ErrorMapper Middleware Transport |
| Debugging | Dump DumpAll DumpEachRequest DumpEachRequestTo DumpTo DumpToFile |
| Download Options | OutputFile |
| Errors | Error |
| Request Options | Before Body Form Header Headers JSON Path Paths Queries Query Timeout |
| Requests | Delete Get Patch Post Put |
| Requests (Context) | DeleteCtx GetCtx PatchCtx PostCtx PutCtx |
| Retry | RetryBackoff RetryCondition RetryCount RetryFixedInterval RetryHook RetryInterval |
| Retry (Client) | Retry |
| Upload Options | File FileBytes FileReader Files UploadCallback UploadCallbackWithInterval UploadProgress |
Auth sets the Authorization header using a scheme and token.
c := httpx.New()
_ = httpx.Get[string](c, "https://example.com", httpx.Auth("Token", "abc123"))Basic sets HTTP basic authentication headers.
c := httpx.New()
_ = httpx.Get[string](c, "https://example.com", httpx.Basic("user", "pass"))Bearer sets the Authorization header with a bearer token.
c := httpx.New()
_ = httpx.Get[string](c, "https://example.com", httpx.Bearer("token"))Default returns the shared default client.
c := httpx.Default()
_ = cNew creates a client with opinionated defaults and optional overrides.
var buf bytes.Buffer
c := httpx.New(httpx.
BaseURL("https://api.example.com").
Timeout(5*time.Second).
Header("X-Trace", "1").
Headers(map[string]string{
"Accept": "application/json",
}).
Transport(http.RoundTripper(http.DefaultTransport)).
Middleware(func(_ *req.Client, r *req.Request) error {
r.SetHeader("X-Middleware", "1")
return nil
}).
ErrorMapper(func(resp *req.Response) error {
return fmt.Errorf("status %d", resp.StatusCode)
}).
DumpAll().
DumpEachRequest().
DumpEachRequestTo(&buf).
Retry(func(rc *req.Client) {
rc.SetCommonRetryCount(2)
}).
RetryCount(2).
RetryFixedInterval(200 * time.Millisecond).
RetryBackoff(100*time.Millisecond, 2*time.Second).
RetryInterval(func(_ *req.Response, attempt int) time.Duration {
return time.Duration(attempt) * 100 * time.Millisecond
}).
RetryCondition(func(resp *req.Response, _ error) bool {
return resp != nil && resp.StatusCode == 503
}).
RetryHook(func(_ *req.Response, _ error) {}),
)
_ = cRaw returns the underlying req client for chaining raw requests.
c := httpx.New()
resp, err := c.Raw().R().Get("https://httpbin.org/uuid")
_, _ = resp, errReq returns the underlying req client for advanced usage.
c := httpx.New()
c.Req().EnableDumpEachRequest()BaseURL sets a base URL on the client.
c := httpx.New(httpx.BaseURL("https://api.example.com"))
_ = cErrorMapper sets a custom error mapper for non-2xx responses.
c := httpx.New(httpx.ErrorMapper(func(resp *req.Response) error {
return fmt.Errorf("status %d", resp.StatusCode)
}))
_ = cMiddleware adds request middleware to the client.
c := httpx.New(httpx.Middleware(func(_ *req.Client, r *req.Request) error {
r.SetHeader("X-Trace", "1")
return nil
}))
_ = cTransport wraps the underlying transport with a custom RoundTripper.
c := httpx.New(httpx.Transport(http.RoundTripper(http.DefaultTransport)))
_ = cDump enables req's request-level dump output.
c := httpx.New()
_ = httpx.Get[string](c, "https://example.com", httpx.Dump())DumpAll enables req's client-level dump output for all requests.
c := httpx.New(httpx.DumpAll())
_ = cDumpEachRequest enables request-level dumps for each request on the client.
c := httpx.New(httpx.DumpEachRequest())
_ = cDumpEachRequestTo enables request-level dumps for each request and writes them to the provided output.
var buf bytes.Buffer
c := httpx.New(httpx.DumpEachRequestTo(&buf))
_ = httpx.Get[string](c, "https://example.com")
_ = buf.String()DumpTo enables req's request-level dump output to a writer.
var buf bytes.Buffer
c := httpx.New()
_ = httpx.Get[string](c, "https://example.com", httpx.DumpTo(&buf))DumpToFile enables req's request-level dump output to a file path.
c := httpx.New()
_ = httpx.Get[string](c, "https://example.com", httpx.DumpToFile("httpx.dump"))OutputFile streams the response body to a file path.
c := httpx.New()
_ = httpx.Get[string](c, "https://example.com/file", httpx.OutputFile("/tmp/file.bin"))Error returns a short, human-friendly summary of the HTTP error.
type User struct {
Name string `json:"name"`
}
c := httpx.New()
res := httpx.Get[User](c, "https://example.com/users/1")
var httpErr *httpx.HTTPError
if errors.As(res.Err, &httpErr) {
_ = httpErr.StatusCode
}Before runs a hook before the request is sent.
c := httpx.New()
_ = httpx.Get[string](c, "https://example.com", httpx.Before(func(r *req.Request) {
r.EnableDump()
}))Body sets the request body and infers JSON for structs and maps.
type Payload struct {
Name string `json:"name"`
}
c := httpx.New()
_ = httpx.Post[any, string](c, "https://example.com", nil, httpx.Body(Payload{Name: "Ana"}))Form sets form data for the request.
c := httpx.New()
_ = httpx.Post[any, string](c, "https://example.com", nil, httpx.Form(map[string]string{
"name": "Ana",
}))Header sets a header on a request or client.
c := httpx.New()
_ = httpx.Get[string](c, "https://example.com", httpx.Header("X-Trace", "1"))Headers sets multiple headers on a request or client.
c := httpx.New()
_ = httpx.Get[string](c, "https://example.com", httpx.Headers(map[string]string{
"X-Trace": "1",
"Accept": "application/json",
}))JSON sets the request body as JSON.
type Payload struct {
Name string `json:"name"`
}
c := httpx.New()
_ = httpx.Post[any, string](c, "https://example.com", nil, httpx.JSON(Payload{Name: "Ana"}))Path sets a path parameter by name.
type User struct {
Name string `json:"name"`
}
c := httpx.New()
_ = httpx.Get[User](c, "https://example.com/users/{id}", httpx.Path("id", 42))Paths sets multiple path parameters.
type User struct {
Name string `json:"name"`
}
c := httpx.New()
_ = httpx.Get[User](c, "https://example.com/orgs/{org}/users/{id}", httpx.Paths(map[string]any{
"org": "goforj",
"id": 42,
}))Queries adds multiple query parameters.
c := httpx.New()
_ = httpx.Get[string](c, "https://example.com/search", httpx.Queries(map[string]string{
"q": "go",
"ok": "1",
}))Query adds query parameters as key/value pairs.
c := httpx.New()
_ = httpx.Get[string](c, "https://example.com/search", httpx.Query("q", "go", "ok", "1"))Timeout sets a per-request timeout using context cancellation.
c := httpx.New()
_ = httpx.Get[string](c, "https://example.com", httpx.Timeout(2*time.Second))Delete issues a DELETE request using the provided client.
type DeleteResponse struct {
OK bool `json:"ok"`
}
c := httpx.New()
res := httpx.Delete[DeleteResponse](c, "https://api.example.com/users/1")
_, _ = res.Body, res.ErrGet issues a GET request using the provided client.
type PullRequest struct {
Number int `json:"number"`
Title string `json:"title"`
}
c := httpx.New(httpx.Header("Accept", "application/vnd.github+json"))
res := httpx.Get[[]PullRequest](c, "https://api.github.com/repos/goforj/httpx/pulls")
if res.Err != nil {
return
}
godump.Dump(res.Body)Patch issues a PATCH request using the provided client.
type UpdateUser struct {
Name string `json:"name"`
}
type User struct {
Name string `json:"name"`
}
c := httpx.New()
res := httpx.Patch[UpdateUser, User](c, "https://api.example.com/users/1", UpdateUser{Name: "Ana"})
_, _ = res.Body, res.ErrPost issues a POST request using the provided client.
type CreateUser struct {
Name string `json:"name"`
}
type User struct {
Name string `json:"name"`
}
c := httpx.New()
res := httpx.Post[CreateUser, User](c, "https://api.example.com/users", CreateUser{Name: "Ana"})
_, _ = res.Body, res.ErrPut issues a PUT request using the provided client.
type UpdateUser struct {
Name string `json:"name"`
}
type User struct {
Name string `json:"name"`
}
c := httpx.New()
res := httpx.Put[UpdateUser, User](c, "https://api.example.com/users/1", UpdateUser{Name: "Ana"})
_, _ = res.Body, res.ErrDeleteCtx issues a DELETE request using the provided client and context.
type DeleteResponse struct {
OK bool `json:"ok"`
}
c := httpx.New()
ctx := context.Background()
res := httpx.DeleteCtx[DeleteResponse](c, ctx, "https://api.example.com/users/1")
_, _ = res.Body, res.ErrGetCtx issues a GET request using the provided client and context.
type User struct {
Name string `json:"name"`
}
c := httpx.New()
ctx := context.Background()
res := httpx.GetCtx[User](c, ctx, "https://api.example.com/users/1")
_, _ = res.Body, res.ErrPatchCtx issues a PATCH request using the provided client and context.
type UpdateUser struct {
Name string `json:"name"`
}
type User struct {
Name string `json:"name"`
}
c := httpx.New()
ctx := context.Background()
res := httpx.PatchCtx[UpdateUser, User](c, ctx, "https://api.example.com/users/1", UpdateUser{Name: "Ana"})
_, _ = res.Body, res.ErrPostCtx issues a POST request using the provided client and context.
type CreateUser struct {
Name string `json:"name"`
}
type User struct {
Name string `json:"name"`
}
c := httpx.New()
ctx := context.Background()
res := httpx.PostCtx[CreateUser, User](c, ctx, "https://api.example.com/users", CreateUser{Name: "Ana"})
_, _ = res.Body, res.ErrPutCtx issues a PUT request using the provided client and context.
type UpdateUser struct {
Name string `json:"name"`
}
type User struct {
Name string `json:"name"`
}
c := httpx.New()
ctx := context.Background()
res := httpx.PutCtx[UpdateUser, User](c, ctx, "https://api.example.com/users/1", UpdateUser{Name: "Ana"})
_, _ = res.Body, res.ErrRetryBackoff sets a capped exponential backoff retry interval for a request.
c := httpx.New()
_ = httpx.Get[string](c, "https://example.com", httpx.RetryBackoff(100*time.Millisecond, 2*time.Second))RetryCondition sets the retry condition for a request.
c := httpx.New()
_ = httpx.Get[string](c, "https://example.com", httpx.RetryCondition(func(resp *req.Response, _ error) bool {
return resp != nil && resp.StatusCode == 503
}))RetryCount enables retry for a request and sets the maximum retry count.
c := httpx.New()
_ = httpx.Get[string](c, "https://example.com", httpx.RetryCount(2))RetryFixedInterval sets a fixed retry interval for a request.
c := httpx.New()
_ = httpx.Get[string](c, "https://example.com", httpx.RetryFixedInterval(200*time.Millisecond))RetryHook registers a retry hook for a request.
c := httpx.New()
_ = httpx.Get[string](c, "https://example.com", httpx.RetryHook(func(_ *req.Response, _ error) {}))RetryInterval sets a custom retry interval function for a request.
c := httpx.New()
_ = httpx.Get[string](c, "https://example.com", httpx.RetryInterval(func(_ *req.Response, attempt int) time.Duration {
return time.Duration(attempt) * 100 * time.Millisecond
}))Retry applies a custom retry configuration to the client.
c := httpx.New(httpx.Retry(func(rc *req.Client) {
rc.SetCommonRetryCount(2)
}))
_ = cFile attaches a file from disk as multipart form data.
c := httpx.New()
_ = httpx.Post[any, string](c, "https://example.com/upload", nil, httpx.File("file", "/tmp/report.txt"))FileBytes attaches a file from bytes as multipart form data.
c := httpx.New()
_ = httpx.Post[any, string](c, "https://example.com/upload", nil, httpx.FileBytes("file", "report.txt", []byte("hello")))FileReader attaches a file from a reader as multipart form data.
c := httpx.New()
_ = httpx.Post[any, string](c, "https://example.com/upload", nil, httpx.FileReader("file", "report.txt", strings.NewReader("hello")))Files attaches multiple files from disk as multipart form data.
c := httpx.New()
_ = httpx.Post[any, string](c, "https://example.com/upload", nil, httpx.Files(map[string]string{
"fileA": "/tmp/a.txt",
"fileB": "/tmp/b.txt",
}))UploadCallback registers a callback for upload progress.
c := httpx.New()
_ = httpx.Post[any, string](c, "https://example.com/upload", nil,
httpx.File("file", "/tmp/report.bin"),
httpx.UploadCallback(func(info req.UploadInfo) {
percent := float64(info.UploadedSize) / float64(info.FileSize) * 100
fmt.Printf("\rprogress: %.1f%%", percent)
if info.FileSize > 0 && info.UploadedSize >= info.FileSize {
fmt.Print("\n")
}
}),
)UploadCallbackWithInterval registers a callback for upload progress with a minimum interval.
c := httpx.New()
_ = httpx.Post[any, string](c, "https://example.com/upload", nil,
httpx.File("file", "/tmp/report.bin"),
httpx.UploadCallbackWithInterval(func(info req.UploadInfo) {
percent := float64(info.UploadedSize) / float64(info.FileSize) * 100
fmt.Printf("\rprogress: %.1f%% (%.0f bytes)", percent, float64(info.UploadedSize))
if info.FileSize > 0 && info.UploadedSize >= info.FileSize {
fmt.Print("\n")
}
}, 200*time.Millisecond),
)UploadProgress enables a default progress spinner and bar for uploads.
c := httpx.New()
_ = httpx.Post[any, string](c, "https://example.com/upload", nil,
httpx.File("file", "/tmp/report.bin"),
httpx.UploadProgress(),
)