Skip to content

Response

Neon32eeee edited this page Oct 17, 2026 · 1 revision

Response

The Response module is the layer between your handler and the client. Every function here sets a status code and sends a body - nothing more. There is no request inspection, no header manipulation, no content negotiation. Just the primitive: "here's what happened, here's the payload."

The reason it exists as its own module is that sending a body in zap returns an error, and every handler would otherwise repeat the same catch |err| std.debug.print(...) dance. Response collapses that into one call.

Functions

  • sendError
    Purpose: the primitive. Sets a status and sends a body.
    Signature:

    pub fn sendError(r: zap.Request, status: zap.http.StatusCode, msg: []const u8) void
  • sendBadRequest
    Purpose: shortcut for 400 Bad Request.
    Signature:

    pub fn sendBadRequest(r: zap.Request, msg: []const u8) void
  • sendInternalServerError
    Purpose: shortcut for 500 Internal Server Error.
    Signature:

    pub fn sendInternalServerError(r: zap.Request, msg: []const u8) void
  • sendErrorPrint
    Purpose: formats a message with std.fmt.allocPrint and sends it as an error.
    Signature:

    pub fn sendErrorPrint(
        r: zap.Request,
        status: zap.http.StatusCode,
        allocator: Allocator,
        comptime fmt: []const u8,
        args: anytype,
    ) void
  • sendBody
    Purpose: sends 200 OK with a body.
    Signature:

    pub fn sendBody(r: zap.Request, msg: []const u8) void
  • getParamStrSave
    Purpose: fetches a request parameter by name, sending an error response on failure.
    Signature:

    pub fn getParamStrSave(
        r: zap.Request,
        allocator: Allocator,
        name: []const u8,
    ) ?[]const u8
  • notFound
    Purpose: sends 404 Not Found with the requested path in the body.
    Signature:

    pub fn notFound(r: zap.Request, allocator: std.mem.Allocator, path: []const u8) void

Behavior

All senders except getParamStrSave return void. They call r.setStatus(...), then r.sendBody(...), and if the send fails, print the error to stderr and return normally.

getParamStrSave returns ?[]const u8: the parameter value on success, null after sending an error response on failure. Both failure modes (parse error, missing parameter) send 500 Internal Server Error, not 400. If your API contract requires 400 for missing parameters, call r.getParamStr directly.

Examples

1. Sending a plain text body

const Mesh = @import("root.zig");
const response = Mesh.response;

fn ping(r: Mesh.zap.Request) void {
    response.sendBody(r, "pong");
}

2. Sending an error with a formatted message

fn findUser(r: Mesh.zap.Request) void {
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();

    const id = response.getParamStrSave(r, arena.allocator(), "id") orelse return;

    // If `id` didn't parse, we never reach here - getParamStrSave already replied.
    _ = id;
}

3. Returning 404 with the requested path

fn fallback(r: Mesh.zap.Request) void {
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();

    response.notFound(r, arena.allocator(), r.path orelse "/");
}

4. Custom error message with arguments

fn validate(r: Mesh.zap.Request, name: []const u8) void {
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();

    if (name.len == 0) {
        response.sendErrorPrint(
            r,
            .bad_request,
            arena.allocator(),
            "field `{s}` must not be empty",
            .{"name"},
        );
        return;
    }
}

Notes

  • The send-swallowing behavior is deliberate. Once a response is on the wire, there is nothing useful a handler can do about a failed write. The error goes to stderr for diagnostics; the handler continues normally.
  • sendErrorPrint allocates through the supplied allocator. An arena is the natural choice - the message only needs to live long enough to be copied into the socket buffer.

Clone this wiki locally