Summary
HttxClient today is options-object only — every per-request concern goes through a RequestOptions / HttxConfig bag:
await client.post(url, body, {
json: true,
headers: { Authorization: `Bearer ${token}`, 'X-Foo': 'bar' },
retry: { retries: 3, retryDelay: 200 },
timeout: 5000,
})That's complete but not discoverable — you have to know the option keys, and there's no autocomplete-driven path through the common cases (auth, headers, content-type, retries). Laravel's Http facade solves this with a chainable PendingRequest builder.
Proposal
Add a fluent builder that accumulates config and then dispatches via the existing request() path (so all the current retry/circuit-breaker/timing machinery is reused — this is sugar, not a parallel implementation):
import { http } from '@stacksjs/httx'
const res = await http()
.withToken(token) // Authorization: Bearer <token>
.withHeaders({ 'X-Tenant': id })
.acceptJson() // Accept: application/json
.retry(3, 200) // retries, delay ms
.timeout(5000)
.post<CreatedOrder>(url, body)
// ^ still returns Result<HttxResponse<CreatedOrder>, Error>Builder methods (Laravel-aligned)
| Method | Effect |
|---|---|
withHeaders(record) | merge headers |
replaceHeaders(record) | overwrite headers |
withToken(token, type='Bearer') | Authorization header |
withBasicAuth(user, pass) | base64 Authorization: Basic |
withQueryParameters(record) | merge into query |
withBody(body) | set body |
asJson() / asForm() / asMultipart() | set the json/form/multipart flag + content-type |
accept(mime) / acceptJson() | Accept header |
withUserAgent(ua) | User-Agent header |
baseUrl(url) | per-builder base URL |
timeout(ms) | request timeout |
retry(times, delayMs?, shouldRetry?) | maps to RetryOptions |
withOptions(partial RequestOptions) | escape hatch — merge raw options |
terminal get/post/put/patch/delete/head<T>(url, body?) | dispatch, return Result<HttxResponse<T>, Error> |
Notes
http()returns a freshPendingRequest;http(config)seeds defaults (baseUrl, defaultHeaders, etc.) so it composes with the existingHttxConfig.- Keep the existing
HttxClient.get/post/...options-object API — the builder lives alongside it and delegates to it. No breaking change. - Generic
<T>flows through toHttxResponse<T>.dataexactly as it does today.
Why
Discoverability + ergonomics on the 90% path (auth + JSON + retries). Matches the mental model anyone coming from Laravel/Guzzle already has, while keeping httx's Result-based error handling.
Discovered while
Auditing the HTTP client surface from the Stacks framework side (stacksjs/stacks#1924) — the typing is already solid (generics work, zero any), but the fluent layer is the missing DX piece.