Fetch
Server-side outbound HTTP, integrated with the request arena.
ctx.fetch
const resp = try ctx.fetch("https://api.example.com/items", .{});
// resp.status: u16, resp.body: []u8 (arena-owned), resp.truncated: bool
const resp2 = try ctx.fetch(url, .{
.method = .POST,
.body = payload,
.content_type = "application/json",
.extra_headers = &.{ .{ .name = "authorization", .value = bearer } },
.max_body = 1024 * 1024, // default 1MB; bodies beyond → truncated=true
});The body is allocated from the request arena — it lives until the response is sent, no frees.
Typed JSON
const Item = struct { id: u32, name: []const u8 };
const items = try resp.json([]Item);Where it runs
ctx.fetch is server-only. WASM builds return error.UnsupportedOnClient — client-side outbound work goes through a server function instead:
// island chunk: declarative — signal backed by a server fn
verve.fetchSignal(i32, "getCount", .{}, "count_bind");
// island chunk: manual round-trip
verve.registerResponseHandler("myAction", &onReply);
verve.serverFnPost("myAction", "{\"q\":\"zig\"}");This keeps API keys on the server and turns every external call into a typed, CSRF-checked endpoint.
Composing with rendering
Fetch in the route handler, render the result — it's just control flow:
fn renderWeather(ctx: *verve.Context) !*verve.Node {
const resp = try ctx.fetch(weather_url, .{});
if (resp.status != 200) return errorCard(ctx);
const w = try resp.json(Weather);
return weatherCard(ctx, w);
}For slow upstreams, wrap the section in a suspense boundary so the shell streams immediately.
Next: Visualization.