Basic Requests ​
The httpx.zig client supports all standard HTTP methods and provides convenient wrappers for common operations.
Creating a Client ​
For simple usage, create a client with the default configuration:
const io = std.Io.Threaded.global_single_threaded.io();
var client = httpx.Client.init(allocator, io, .{});
defer client.deinit();For more control, pass a configuration to Client.init. Defaults remain implicit unless you explicitly override fields:
const io = std.Io.Threaded.global_single_threaded.io();
var client = httpx.Client.init(allocator, io, .{
.timeoutMs = 10_000,
.followRedirects = true,
.maxRedirects = 5,
.maxRetries = 3,
.retryDelayMs = 500,
.http2 = true,
.http3 = false,
.maxResponseSize = 32 * 1024 * 1024,
.pool = .{ .maxConnections = 64, .maxPerHost = 16 },
});
defer client.deinit();Protocol Selection ​
- Set
.http2 = trueto use the high-level HTTP/2 request path. - Set
.http3 = trueto use the high-level HTTP/3 request path over UDP + QUIC/HTTP3/QPACK primitives.
Making Requests ​
Primary API: fetch ​
client.fetch(url, options) is the primary unified HTTP operation. It supports all HTTP methods, strongly typed Zig struct serialization (via std.json), headers, and query parameters:
// Simple GET
var res = try client.fetch("https://httpbun.com/get", .{});
defer res.deinit();
std.debug.print("Status: {d}, Body: {s}\n", .{ res.status, res.bytes() });
// POST with typed JSON struct
const CreateUser = struct { name: []const u8, email: []const u8 };
const User = struct { id: u64, name: []const u8, email: []const u8 };
var post_res = try client.fetch("https://httpbun.com/post", .{
.method = .POST,
.json = CreateUser{ .name = "Alice", .email = "alice@example.com" },
});
defer post_res.deinit();
// Typed JSON response decoding
const user = try post_res.json(User);
std.debug.print("User: {s} ({s})\n", .{ user.name, user.email });GET with explicit timeout and error handling ​
For external endpoints, you can specify per-request timeouts:
var response = client.fetch("https://httpbun.com/get", .{
.timeoutMs = 10_000,
}) catch |err| {
std.debug.print("request failed: {s}\n", .{@errorName(err)});
return;
};
defer response.deinit();Verb Shortcuts ​
For quick requests, convenience methods like .get() and .post() are also available:
var res = try client.get("https://httpbun.com/get", .{});
defer res.deinit();
var post = try client.post("https://httpbun.com/post", .{ .json = .{ .name = "Alice", .role = "admin" },
});
defer post.deinit();Other Methods ​
// PUT
_ = try client.put("/users/1", .{ .json = updated_json });
// DELETE
_ = try client.delete("/users/1", .{});
// Short alias for DELETE
// HEAD
var head_res = try client.head("/large-file", .{});
defer head_res.deinit();
// OPTIONS
var opt_res = try client.options("/users", .{});
defer opt_res.deinit();Cookie Jar ​
Pass cookies per request with the cookie option, or keep a standalone httpx.client.cookieJar across requests (domain-aware per RFC 6265):
var jar = httpx.client.cookieJar.init(allocator, io);
defer jar.deinit();
if (loginRes.header("Set-Cookie")) |sc| {
jar.setFromHeader(sc, "example.com");
}
var cookieBuf: [512]u8 = undefined;
const cookieHeader = jar.cookieHeader("example.com", "/profile", true, &cookieBuf);
var profileRes = try client.get("https://example.com/profile", .{ .cookie = cookieHeader });
defer profileRes.deinit();Use an initialized client for requests and connection reuse:
var res = try client.get("https://httpbun.com/get", .{});
defer res.deinit();
var custom = try client.request("https://httpbun.com/headers", .{
.method = .GET,
.timeoutMs = 10_000,
});
defer custom.deinit();Auth Helpers ​
Use built-in request auth helpers instead of manually building Authorization headers:
var bearer_res = try client.get("/protected", .{
.bearerAuth = "demo-token",
.headers = &.{.{ .name = "Accept", .value = "application/json" }},
});
defer bearer_res.deinit();
var basic_res = try client.get("/admin", .{
.basicAuth = "demo:pass",
.headers = &.{.{ .name = "Accept", .value = "application/json" }},
});
defer basic_res.deinit();Request Options ​
The second argument to request methods is a duck-typed options struct passed inline as .{}. Convenience methods supply url as their first argument, so .{} is valid for them. See API: Request for the accepted fields.
Proxy Configuration ​
Configure forward proxies when initializing the client:
const io = std.Io.Threaded.global_single_threaded.io();
var client = httpx.Client.init(allocator, io, .{
.proxy = "http://127.0.0.1:8080",
});
defer client.deinit();For SOCKS5h (remote DNS), use a socks5h:// URL:
var client = httpx.Client.init(allocator, io, .{
.proxy = "socks5h://127.0.0.1:1080",
});
defer client.deinit();Response Handling ​
The Response object provides helpers to access data:
// Check status
if (response.isSuccess()) { ... }
// Get headers
if (response.header("Content-Type")) |ct| { ... }
// Parse JSON response into a struct (ignores unknown fields)
const MyStruct = struct { id: u32, name: []const u8 };
const data = try response.json(MyStruct);
// Or parse with an explicit allocator (returns std.json.Parsed(T))
const parsed = try response.jsonAlloc(MyStruct, allocator);
defer parsed.deinit();