Skip to content

Server API ​

The httpx.zig server module provides a robust, Express-inspired HTTP server with middleware support, routing, and proper context handling. Supports HTTP/1.0, HTTP/1.1, HTTP/2, and HTTP/3 protocols.

Protocol Support ​

ProtocolStatusFeatures
HTTP/1.0✅ FullBasic request/response
HTTP/1.1✅ FullKeep-Alive, chunked transfer, pipelining
HTTP/2✅ FullMultiplexing, HPACK, server push, flow control
HTTP/3✅ FullQUIC transport, QPACK, 0-RTT

Server ​

The Server struct manages the listener, router, and middleware processing.

Initialization ​

zig
const httpx = @import("httpx");

// Initialize with default config
var server = httpx.Server.init(allocator);
defer server.deinit();

// Initialize with custom config
var server = httpx.Server.initWithConfig(allocator, .{
    .port = 3000,
    .host = "0.0.0.0",
    .max_body_size = 1048576, // 1MB
});

Configuration (ServerConfig) ​

FieldTypeDefaultDescription
host[]const u8"127.0.0.1"Interface to bind to.
portu168080Port to listen on.
max_body_sizeusize10MBMax request body size.
request_timeout_msu6430000Timeout for request processing.
keep_alivebooltrueEnable HTTP Keep-Alive.
max_connectionsu321000Max concurrent connections.
http2_enabledboolfalseEnable HTTP/2 support
http3_enabledboolfalseEnable HTTP/3 support
tls_cert_path?[]const u8nullPath to TLS certificate
tls_key_path?[]const u8nullPath to TLS private key

Methods ​

listen ​

Starts the server. This method blocks.

zig
try server.listen();

stop ​

Stops the server gracefully.

zig
server.stop();

use ​

Adds a middleware to the global stack.

zig
try server.use(httpx.middleware.logger());

Routing Methods ​

MethodDescription
get(path, handler)Register GET route
post(path, handler)Register POST route
put(path, handler)Register PUT route
delete(path, handler)Register DELETE route
patch(path, handler)Register PATCH route
head(path, handler)Register HEAD route
options(path, handler)Register OPTIONS route
route(method, path, handler)Register any method

Quick Example ​

zig
const httpx = @import("httpx");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    var server = httpx.Server.init(allocator);
    defer server.deinit();

    // Add middleware
    try server.use(httpx.middleware.logger());
    try server.use(httpx.middleware.cors(.{}));

    // Register routes
    try server.get("/", homePage);
    try server.get("/api/users", listUsers);
    try server.post("/api/users", createUser);
    try server.get("/api/users/:id", getUser);
    try server.put("/api/users/:id", updateUser);
    try server.delete("/api/users/:id", deleteUser);

    std.debug.print("Server listening on http://localhost:8080\n", .{});
    try server.listen();
}

fn homePage(ctx: *httpx.Context) !httpx.Response {
    return ctx.html("<h1>Welcome to httpx.zig!</h1>");
}

fn listUsers(ctx: *httpx.Context) !httpx.Response {
    return ctx.json(.{ .users = &.{} });
}

Route Groups ​

Organize routes with a common prefix.

zig
var api = server.router.group("/api/v1");
try api.get("/users", listUsers);
try api.post("/users", createUser);
try api.get("/users/:id", getUser);
try api.put("/users/:id", updateUser);
try api.delete("/users/:id", deleteUser);

var admin = server.router.group("/admin");
try admin.use(httpx.middleware.auth());
try admin.get("/dashboard", adminDashboard);

Custom 404 Handler ​

zig
server.router.setNotFound(fn(ctx: *httpx.Context) !httpx.Response {
    return ctx.status(404).json(.{
        .error = "Not Found",
        .path = ctx.request.path,
    });
});

Context ​

The Context struct is passed to every route handler and middleware. It wraps the request and response objects.

Fields ​

FieldTypeDescription
request*RequestThe incoming request
responseResponseBuilderResponse builder
allocatorAllocatorRequest-scoped allocator
paramsStringMapURL path parameters
state?*anyopaqueUser-defined state

Request Accessors ​

MethodDescription
param(name)Get URL path parameter (:id, :name)
query(name)Get query string parameter
header(name)Get request header value
body()Get request body
jsonBody(T)Parse body as JSON type T

Response Helpers ​

These methods allow fluent response chaining.

MethodDescription
status(code)Set HTTP status code
setHeader(name, value)Set response header
json(value)Send JSON response
text(data)Send plain text
html(data)Send HTML response
file(path)Stream a file
redirect(url, code)Send redirect
send(data)Send raw bytes
noContent()204 No Content

Example Context Usage ​

zig
fn getUser(ctx: *httpx.Context) !httpx.Response {
    // Get URL parameter
    const id = ctx.param("id") orelse return ctx.status(400).json(.{
        .error = "Missing user ID",
    });
    
    // Get query parameter
    const format = ctx.query("format") orelse "json";
    
    // Get request header
    const auth = ctx.header("Authorization");
    
    // Set response headers
    try ctx.setHeader("X-Request-Id", "12345");
    
    // Return JSON response
    return ctx.json(.{
        .id = id,
        .name = "John Doe",
        .email = "john@example.com",
    });
}

Handlers ​

Handlers are functions that take a *Context and return a !Response.

zig
const Handler = *const fn (*Context) anyerror!Response;

fn myHandler(ctx: *httpx.Context) !httpx.Response {
    return ctx.json(.{ .message = "Hello World" });
}

Handler Patterns ​

zig
// Simple text response
fn hello(ctx: *httpx.Context) !httpx.Response {
    return ctx.text("Hello, World!");
}

// JSON response with status
fn created(ctx: *httpx.Context) !httpx.Response {
    return ctx.status(201).json(.{ .id = 1, .created = true });
}

// File download
fn download(ctx: *httpx.Context) !httpx.Response {
    try ctx.setHeader("Content-Disposition", "attachment; filename=\"report.pdf\"");
    return ctx.file("/path/to/report.pdf");
}

// Redirect
fn redirectHome(ctx: *httpx.Context) !httpx.Response {
    return ctx.redirect("/", 302);
}

// Error handling
fn riskyHandler(ctx: *httpx.Context) !httpx.Response {
    const data = doSomethingRisky() catch |err| {
        return ctx.status(500).json(.{
            .error = "Internal Server Error",
            .message = @errorName(err),
        });
    };
    return ctx.json(data);
}

Static Files ​

Serve static files from a directory.

zig
// Serve files from ./public at /static/*
try server.static("/static", "./public");

// Or use middleware
try server.use(httpx.middleware.staticFiles(.{
    .root = "./public",
    .prefix = "/static",
    .index = "index.html",
    .cache_control = "public, max-age=3600",
}));

Error Handling ​

zig
// Global error handler
server.setErrorHandler(fn(ctx: *httpx.Context, err: anyerror) !httpx.Response {
    std.debug.print("Error: {}\n", .{err});
    return ctx.status(500).json(.{
        .error = "Internal Server Error",
    });
});

// Per-route error handling
fn handler(ctx: *httpx.Context) !httpx.Response {
    const result = riskyOperation() catch |err| switch (err) {
        error.NotFound => return ctx.status(404).json(.{ .error = "Not Found" }),
        error.Unauthorized => return ctx.status(401).json(.{ .error = "Unauthorized" }),
        else => return err,
    };
    return ctx.json(result);
}

See Also ​

Released under the MIT License.