Skip to content

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:

zig
    const io = std.Io.Threaded.global_single_threaded.io();
var client = httpx.Client.init(allocator, io, .{});
defer client.deinit();

For more control, use ClientConfig. Defaults remain implicit unless you explicitly override fields:

zig
    const io = std.Io.Threaded.global_single_threaded.io();
const config: httpx.ClientConfig = .{
    .timeoutMs = 10_000,
    .followRedirects = true,
    .maxRedirects = 5,
    .maxRetries = 3,
    .retryDelayMs = 500,
    .http2 = true,
    .http3 = false,
    .maxResponseSize = 32 * 1024 * 1024,
    .pool = .{ .maxConnections = 64, .maxPerHost = 16 },
};

var client = httpx.Client.init(allocator, io, config);
defer client.deinit();

Protocol Selection ​

  • Set .http2 = true to use the high-level HTTP/2 request path.
  • Set .http3 = true to 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:

zig
// 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:

zig
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:

zig
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 ​

zig
// PUT
_ = try client.put("/users/1", .{ .json = updated_json });

// DELETE
_ = try client.delete("/users/1", .{});

// Short alias for DELETE
// HEAD
const head_res = try client.head("/large-file", .{});

// OPTIONS
const opt_res = try client.options("/users", .{});

_ = head_res;
_ = opt_res;

Pass cookies per request with the cookie option, or keep a standalone httpx.CookieJar across requests (domain-aware per RFC 6265):

zig
var jar = httpx.CookieJar.init(allocator);
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();

For top-level convenience in smaller programs, use functions from the root module:

zig
var res = try httpx.get("https://httpbun.com/get", .{});
defer res.deinit();

var custom = try httpx.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:

zig
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 RequestOptions:

zig
pub const RequestOptions = struct {
    url: []const u8,
    method: ?httpx.Method = null,       // Explicit method for generic request
    headers: []const httpx.Header = &.{}, // Custom headers
    query: []const httpx.Header = &.{},   // Optional URL query params
    body: ?[]const u8 = null,           // Raw body
    json: ?[]const u8 = null,           // JSON body
    form: ?[]const u8 = null,           // x-www-form-urlencoded body
    text: ?[]const u8 = null,           // Plain-text body
    bearerAuth: ?[]const u8 = null,     // Authorization: Bearer <token>
    basicAuth: ?[]const u8 = null,      // Authorization: Basic ...
    timeoutMs: ?u64 = null,             // Request-specific timeout
    followRedirects: ?bool = null,      // Override redirect policy
    maxRedirects: ?u8 = null,           // Override redirect limit
    httpVersion: ?httpx.HttpVersion = null, // Optional per-request protocol override
    proxy: ?[]const u8 = null,          // Per-request proxy URL override
    tls: ?TlsOptions = null,            // Per-request TLS override
};

All fields except url are optional customizations. Passing . {} keeps defaults implicit.

Proxy Configuration ​

Configure forward proxies when initializing the client:

zig
    const io = std.Io.Threaded.global_single_threaded.io();
const config: httpx.ClientConfig = .{
    .proxy = "http://127.0.0.1:8080",
};

var client = httpx.Client.init(allocator, io, config);
defer client.deinit();

For SOCKS5h (remote DNS), use a socks5h:// URL:

zig
const socks_config: httpx.ClientConfig = .{
    .proxy = "socks5h://127.0.0.1:1080",
};

Response Handling ​

The Response object provides helpers to access data:

zig
// 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();

Released under the MIT License.