cURL Alternatives Compared: HTTPie, xh, wget2, and Native fetch
curl will never die — it's preinstalled everywhere and speaks every protocol — but "default" and "best" are different words. Here's an honest map of when the alternatives are genuinely better, tested against the same requests.
HTTPie (the friendly one)
http POST api.example.com/v1/jobs model:=small input="hello"
http api.github.com/users/octocat Authorization:'Bearer ghp_...'
- Wins: the
:=/=/:=@typed-data syntax makes JSON bodies pleasant; headers are justName:value; output is pretty and colorized by default;--print=hreads like English. - Losers: Python runtime (slow startup, needs a pip/pipx install); JSON-by-default is wrong for form endpoints and bites you once; not installed on any production server you didn't provision.
- Honest take: the best interactive tool for API poking. The moment you need the command in a Dockerfile or a colleague's bare server, you're back to curl.
xh (HTTPie's ergonomics, Rust's footprint)
xh post api.example.com/v1/jobs model:=small
xh :8080/health # localhost shorthand
- Wins: same human syntax as HTTPie but a single static binary — no Python, cold start in milliseconds, trivially vendorable. Good TLS defaults and
--curlflag that emits the equivalent curl command (the inverse of this site). - Losers: young project; no multipart file upload until recent versions matured; fewer auth plugins than HTTPie's ecosystem.
- Honest take: if you're allowed to install one binary on your team's machines, this is the one. For scripts that must run anywhere, still curl.
wget2 (when downloading is the whole job)
- Wins: recursion, timestamped resumption (
-c), rate limiting (--limit-rate), and content-disposition filename handling that curl needs three flags to approximate. It was born for mirrors. - Losers: API work. Custom headers, methods, and auth are clunky compared to curl; no per-request flexibility worth the name.
- Honest take:
wget2 -c URLfor flaky-network downloads, curl for anything with a status code you care about. Note classicwget(1.x) and wget2 differ subtly in quoting; see quoting hell for the Windows twist.
Node 18+ native fetch (in code, not the terminal)
- Wins: zero dependencies, spec-standard
ResponseAPI, streaming bodies viaReadableStream. For our curl-to-node route, modern fetch output is the default target for a reason. - Losers: no timeouts built in — you're assembling
AbortSignal.timeout()by hand; redirects preserve curl-like method changes only viaredirect: 'manual'gymnastics; error-on-non-2xx must be your ownif (!res.ok). - Honest take: fine for edge functions and scripts; the missing-defaults tax is exactly what the debugging guide documents from the other side.
Python requests / httpx
- Wins: sessions with cookie jars (the
-b/-cpattern, done right), connection pooling, retries via adapters; httpx adds HTTP/2 and proper timeouts-by-default. - Losers:
data=vsjson=— the form/JSON confusion that generates half the 400s on the internet. Set a timeout or inherit a 0-second patience budget from nobody. - Honest take: the right answer inside applications; the wrong answer for the "quick check" that curl was built for.
Comparison table
| Tool | Install barrier | JSON ergonomics | Scripting everywhere | Timeouts by default |
|---|---|---|---|---|
| curl | none (preinstalled) | manual flags | yes | no |
| HTTPie | Python | best-in-class | no | no |
| xh | one binary | best-in-class | usually | yes |
| wget2 | package | n/a | common | yes |
| Node fetch | Node 18+ | native objects | inside apps | no |
| requests/httpx | pip | json= kwarg | inside apps | requests: no, httpx: yes |
FAQ
Which one should I paste into bug reports? curl, because everyone receiving it can run it. If you found the bug with xh, run xh --curl and attach that.
Does any alternative follow redirects by default? HTTPie and xh do; curl needs -L, and the failure mode is documented in the flags guide. wget follows by default, which is why its authors never needed -L.
Won't using HTTPie at work get me in trouble? It gets you in trouble the first time your one-liner has to run on a prod box with no pip. xh's single-binary story exists for exactly that sentence.