Servidor HTTP em Zig: Criando APIs Web com std.http.Server

Criar servidores HTTP é essencial para desenvolvimento backend moderno. Com Zig, você pode construir servidores HTTP de alta performance sem dependências externas, usando apenas a biblioteca padrão. Neste tutorial, vamos criar desde um servidor simples até uma API REST completa com roteamento, JSON e boas práticas.

Introdução ao std.http.Server

Por Zig para Backend?

Zig oferece vantagens únicas para desenvolvimento web:

  • Performance nativa: Sem garbage collection, com controle preciso de alocações
  • Footprint pequeno: Binários pequenos, ideal para containers
  • Segurança de memória: Eliminação de vulnerabilidades comuns (buffer overflows)
  • Cross-compilation: Compile para qualquer plataforma facilmente
  • Sem dependências: std.http.Server está na biblioteca padrão

Arquitetura Básica

┌─────────────────────────────────────┐
│      std.http.Server                │  ← Escuta conexões
├─────────────────────────────────────┤
│   Accept Connection                 │  ← Aceita novas conexões
├─────────────────────────────────────┤
│   Handle Request → Send Response    │  ← Processa e responde
├─────────────────────────────────────┤
│   std.http.Server.Request/Response  │  ← Tipos de requisição/resposta
└─────────────────────────────────────┘

Servidor HTTP Básico

Olá, Mundo Web

Vamos começar com o servidor mais simples possível:

const std = @import("std");

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

    // Criar o servidor na porta 8080
    const address = std.net.Address.parseIp4("0.0.0.0", 8080) catch |err| {
        std.debug.print("Erro ao parsear endereço: {}\n", .{err});
        return err;
    };

    var server = try address.listen(.{
        .reuse_address = true,
    });
    defer server.deinit();

    std.debug.print("Servidor rodando em http://localhost:8080\n", .{});

    // Loop principal — aceita conexões indefinidamente
    while (true) {
        const conn = try server.accept();
        
        // Processar conexão
        try handleConnection(allocator, conn);
    }
}

fn handleConnection(allocator: std.mem.Allocator, conn: std.net.Server.Connection) !void {
    defer conn.stream.close();

    // Criar reader/writer HTTP
    var read_buffer: [1024]u8 = undefined;
    const reader = conn.stream.reader();
    const writer = conn.stream.writer();

    // Parse da requisição HTTP
    const request_line = try reader.readUntilDelimiterOrEof(
        &read_buffer,
        '\n'
    ) orelse return;

    // Parse simples: "GET / HTTP/1.1"
    var parts = std.mem.split(u8, request_line, " ");
    const method = parts.next() orelse return;
    const path = parts.next() orelse return;

    std.debug.print("{s} {s}\n", .{ method, path });

    // Responder
    const response = "HTTP/1.1 200 OK\r\n" ++
        "Content-Type: text/plain\r\n" ++
        "Content-Length: 13\r\n" ++
        "\r\n" ++
        "Ola, Mundo!";

    try writer.writeAll(response);
}

Testando o Servidor

# Compilar
zig build-exe server_simples.zig

# Executar
./server_simples

# Em outro terminal, teste:
curl http://localhost:8080
# Output: Ola, Mundo!

Usando std.http.Server

Servidor HTTP Completo

O código anterior usava TCP diretamente. Vamos usar std.http.Server que é mais robusto:

const std = @import("std");

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

    // Criar servidor
    const address = try std.net.Address.parseIp4("127.0.0.1", 8080);
    var server = try address.listen(.{
        .reuse_address = true,
    });
    defer server.deinit();

    std.debug.print("Servidor em http://127.0.0.1:8080\n", .{});

    // Buffer para headers
    var header_buffer: [8192]u8 = undefined;

    while (true) {
        const conn = try server.accept();
        
        // Criar conexão HTTP
        var http_conn = std.http.Server.init(conn, &header_buffer);
        
        // Receber requisição
        var request = try http_conn.receiveHead();

        // Responder
        try sendResponse(conn.stream.writer(), "Hello from std.http.Server!");
    }
}

fn sendResponse(writer: anytype, body: []const u8) !void {
    const response = try std.fmt.allocPrint(
        std.heap.page_allocator,
        "HTTP/1.1 200 OK\r\n" ++
            "Content-Type: text/plain\r\n" ++
            "Content-Length: {d}\r\n" ++
            "\r\n" ++
            "{s}",
        .{ body.len, body }
    );
    defer std.heap.page_allocator.free(response);

    try writer.writeAll(response);
}

Roteamento Básico

Implementando Rotas Simples

Um servidor real precisa rotear requisições para diferentes handlers:

const std = @import("std");

const Route = struct {
    method: []const u8,
    path: []const u8,
    handler: *const fn ([]const u8, std.net.Stream.Writer) anyerror!void,
};

fn homeHandler(_: []const u8, writer: std.net.Stream.Writer) !void {
    const body = "Bem-vindo a API Zig!";
    try sendResponse(writer, 200, "OK", "text/plain", body);
}

fn usersHandler(_: []const u8, writer: std.net.Stream.Writer) !void {
    const body = "[{\"id\": 1, \"name\": \"Alice\"}, {\"id\": 2, \"name\": \"Bob\"}]";
    try sendResponse(writer, 200, "OK", "application/json", body);
}

fn aboutHandler(_: []const u8, writer: std.net.Stream.Writer) !void {
    const body = "API desenvolvida em Zig";
    try sendResponse(writer, 200, "OK", "text/plain", body);
}

fn notFoundHandler(_: []const u8, writer: std.net.Stream.Writer) !void {
    const body = "404 - Rota nao encontrada";
    try sendResponse(writer, 404, "Not Found", "text/plain", body);
}

const routes = [_]Route{
    .{ .method = "GET", .path = "/", .handler = homeHandler },
    .{ .method = "GET", .path = "/users", .handler = usersHandler },
    .{ .method = "GET", .path = "/about", .handler = aboutHandler },
};

fn sendResponse(
    writer: std.net.Stream.Writer,
    status_code: u16,
    status_text: []const u8,
    content_type: []const u8,
    body: []const u8,
) !void {
    const response = try std.fmt.allocPrint(
        std.heap.page_allocator,
        "HTTP/1.1 {d} {s}\r\n" ++
            "Content-Type: {s}\r\n" ++
            "Content-Length: {d}\r\n" ++
            "\r\n" ++
            "{s}",
        .{ status_code, status_text, content_type, body.len, body }
    );
    defer std.heap.page_allocator.free(response);

    try writer.writeAll(response);
}

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

    const address = try std.net.Address.parseIp4("127.0.0.1", 8080);
    var server = try address.listen(.{
        .reuse_address = true,
        .kernel_backlog = 128,
    });
    defer server.deinit();

    std.debug.print("API em http://127.0.0.1:8080\n", .{});
    std.debug.print("Rotas:\n", .{});
    for (routes) |route| {
        std.debug.print("  {s} {s}\n", .{ route.method, route.path });
    }

    var read_buffer: [1024]u8 = undefined;

    while (true) {
        const conn = try server.accept();
        defer conn.stream.close();

        const reader = conn.stream.reader();
        const writer = conn.stream.writer();

        // Ler requisição
        const request_line = try reader.readUntilDelimiterOrEof(
            &read_buffer,
            '\n'
        ) orelse continue;

        // Parse
        var parts = std.mem.split(u8, request_line, " ");
        const method = parts.next() orelse continue;
        const path = parts.next() orelse continue;

        // Ignorar headers (para este exemplo simples)
        while (true) {
            const line = try reader.readUntilDelimiterOrEof(
                &read_buffer,
                '\n'
            ) orelse break;
            if (line.len == 1 or (line.len == 2 and line[0] == '\r')) break;
        }

        // Roteamento
        var found = false;
        for (routes) |route| {
            if (std.mem.eql(u8, route.method, method) and
                std.mem.eql(u8, route.path, path))
            {
                try route.handler(path, writer);
                found = true;
                break;
            }
        }

        if (!found) {
            try notFoundHandler(path, writer);
        }
    }
}

API REST com JSON

Servidor CRUD Completo

Vamos criar uma API REST para gerenciar tarefas (todo list):

const std = @import("std");

// Modelo
const Task = struct {
    id: u32,
    title: []const u8,
    completed: bool,
};

// Estado global (simplificado — em produção use banco de dados)
var tasks = std.ArrayList(Task).init(std.heap.page_allocator);
var next_id: u32 = 1;

const Context = struct {
    allocator: std.mem.Allocator,
    reader: std.net.Stream.Reader,
    writer: std.net.Stream.Writer,
    method: []const u8,
    path: []const u8,
    body: ?[]const u8,
};

// Handler para GET /tasks
fn getTasks(ctx: Context) !void {
    // Serializar tasks para JSON
    var json = std.ArrayList(u8).init(ctx.allocator);
    defer json.deinit();

    try json.append('[');
    for (tasks.items, 0..) |task, i| {
        if (i > 0) try json.appendSlice(",");
        
        const task_json = try std.fmt.allocPrint(
            ctx.allocator,
            "{{\"id\":{d},\"title\":\"{s}\",\"completed\":{}}}",
            .{ task.id, task.title, task.completed }
        );
        defer ctx.allocator.free(task_json);
        
        try json.appendSlice(task_json);
    }
    try json.append(']');

    try sendJson(ctx.writer, 200, json.items);
}

// Handler para GET /tasks/:id
fn getTask(ctx: Context, id: u32) !void {
    for (tasks.items) |task| {
        if (task.id == id) {
            const json = try std.fmt.allocPrint(
                ctx.allocator,
                "{{\"id\":{d},\"title\":\"{s}\",\"completed\":{}}}",
                .{ task.id, task.title, task.completed }
            );
            defer ctx.allocator.free(json);
            
            try sendJson(ctx.writer, 200, json);
            return;
        }
    }
    
    try sendError(ctx.writer, 404, "Task not found");
}

// Handler para POST /tasks
fn createTask(ctx: Context) !void {
    const body = ctx.body orelse {
        try sendError(ctx.writer, 400, "Body required");
        return;
    };

    // Parse JSON simples (em produção use std.json.parse)
    const title = extractJsonString(body, "title") orelse {
        try sendError(ctx.writer, 400, "Title required");
        return;
    };

    const task = Task{
        .id = next_id,
        .title = try ctx.allocator.dupe(u8, title),
        .completed = false,
    };
    next_id += 1;

    try tasks.append(task);

    const json = try std.fmt.allocPrint(
        ctx.allocator,
        "{{\"id\":{d},\"title\":\"{s}\",\"completed\":false}}",
        .{ task.id, task.title }
    );
    defer ctx.allocator.free(json);

    try sendJson(ctx.writer, 201, json);
}

// Handler para PUT /tasks/:id
fn updateTask(ctx: Context, id: u32) !void {
    const body = ctx.body orelse {
        try sendError(ctx.writer, 400, "Body required");
        return;
    };

    for (tasks.items) |*task| {
        if (task.id == id) {
            if (extractJsonString(body, "title")) |new_title| {
                task.title = new_title;
            }
            if (extractJsonBool(body, "completed")) |new_completed| {
                task.completed = new_completed;
            }

            const json = try std.fmt.allocPrint(
                ctx.allocator,
                "{{\"id\":{d},\"title\":\"{s}\",\"completed\":{}}}",
                .{ task.id, task.title, task.completed }
            );
            defer ctx.allocator.free(json);

            try sendJson(ctx.writer, 200, json);
            return;
        }
    }

    try sendError(ctx.writer, 404, "Task not found");
}

// Handler para DELETE /tasks/:id
fn deleteTask(ctx: Context, id: u32) !void {
    for (tasks.items, 0..) |task, i| {
        if (task.id == id) {
            _ = tasks.orderedRemove(i);
            try sendJson(ctx.writer, 204, "");
            return;
        }
    }

    try sendError(ctx.writer, 404, "Task not found");
}

// Helpers
fn sendJson(writer: std.net.Stream.Writer, status: u16, body: []const u8) !void {
    const response = try std.fmt.allocPrint(
        std.heap.page_allocator,
        "HTTP/1.1 {d} {s}\r\n" ++
            "Content-Type: application/json\r\n" ++
            "Content-Length: {d}\r\n" ++
            "\r\n" ++
            "{s}",
        .{ status, if (status == 200) "OK" else if (status == 201) "Created" else "No Content", body.len, body }
    );
    defer std.heap.page_allocator.free(response);
    
    try writer.writeAll(response);
}

fn sendError(writer: std.net.Stream.Writer, status: u16, message: []const u8) !void {
    const json = try std.fmt.allocPrint(
        std.heap.page_allocator,
        "{{\"error\":\"{s}\"}}",
        .{message}
    );
    defer std.heap.page_allocator.free(json);

    const response = try std.fmt.allocPrint(
        std.heap.page_allocator,
        "HTTP/1.1 {d} Error\r\n" ++
            "Content-Type: application/json\r\n" ++
            "Content-Length: {d}\r\n" ++
            "\r\n" ++
            "{s}",
        .{ status, json.len, json }
    );
    defer std.heap.page_allocator.free(response);

    try writer.writeAll(response);
}

fn extractJsonString(json: []const u8, key: []const u8) ?[]const u8 {
    const pattern = try std.fmt.allocPrint(
        std.heap.page_allocator,
        "\"{s}\":\"",
        .{key}
    );
    defer std.heap.page_allocator.free(pattern);

    if (std.mem.indexOf(u8, json, pattern)) |start| {
        const value_start = start + pattern.len;
        if (std.mem.indexOfScalar(u8, json[value_start..], '"')) |end| {
            return json[value_start .. value_start + end];
        }
    }
    return null;
}

fn extractJsonBool(json: []const u8, key: []const u8) ?bool {
    const pattern = try std.fmt.allocPrint(
        std.heap.page_allocator,
        "\"{s}\":",
        .{key}
    );
    defer std.heap.page_allocator.free(pattern);

    if (std.mem.indexOf(u8, json, pattern)) |start| {
        const value_start = start + pattern.len;
        if (std.mem.startsWith(u8, json[value_start..], "true")) {
            return true;
        } else if (std.mem.startsWith(u8, json[value_start..], "false")) {
            return false;
        }
    }
    return null;
}

// Roteamento
fn handleRequest(ctx: Context) !void {
    if (std.mem.eql(u8, ctx.path, "/tasks")) {
        if (std.mem.eql(u8, ctx.method, "GET")) {
            try getTasks(ctx);
        } else if (std.mem.eql(u8, ctx.method, "POST")) {
            try createTask(ctx);
        } else {
            try sendError(ctx.writer, 405, "Method not allowed");
        }
    } else if (std.mem.startsWith(u8, ctx.path, "/tasks/")) {
        const id_str = ctx.path[7..];
        const id = std.fmt.parseInt(u32, id_str, 10) catch {
            try sendError(ctx.writer, 400, "Invalid ID");
            return;
        };

        if (std.mem.eql(u8, ctx.method, "GET")) {
            try getTask(ctx, id);
        } else if (std.mem.eql(u8, ctx.method, "PUT")) {
            try updateTask(ctx, id);
        } else if (std.mem.eql(u8, ctx.method, "DELETE")) {
            try deleteTask(ctx, id);
        } else {
            try sendError(ctx.writer, 405, "Method not allowed");
        }
    } else {
        try sendError(ctx.writer, 404, "Not found");
    }
}

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

    const address = try std.net.Address.parseIp4("127.0.0.1", 8080);
    var server = try address.listen(.{
        .reuse_address = true,
        .kernel_backlog = 128,
    });
    defer server.deinit();

    std.debug.print("API REST em http://127.0.0.1:8080\n", .{});
    std.debug.print("\nEndpoints:\n", .{});
    std.debug.print("  GET    /tasks       - Listar tarefas\n", .{});
    std.debug.print("  GET    /tasks/:id   - Obter tarefa\n", .{});
    std.debug.print("  POST   /tasks       - Criar tarefa\n", .{});
    std.debug.print("  PUT    /tasks/:id   - Atualizar tarefa\n", .{});
    std.debug.print("  DELETE /tasks/:id   - Remover tarefa\n", .{});

    var read_buffer: [4096]u8 = undefined;

    while (true) {
        const conn = try server.accept();

        // Ler requisição
        const reader = conn.stream.reader();
        const writer = conn.stream.writer();

        const request_line = try reader.readUntilDelimiterOrEof(
            &read_buffer,
            '\n'
        ) orelse {
            conn.stream.close();
            continue;
        };

        var parts = std.mem.split(u8, request_line, " ");
        const method = parts.next() orelse {
            conn.stream.close();
            continue;
        };
        const path = parts.next() orelse {
            conn.stream.close();
            continue;
        };

        // Ler headers e body
        var content_length: usize = 0;
        while (true) {
            const line = try reader.readUntilDelimiterOrEof(
                &read_buffer,
                '\n'
            ) orelse break;
            
            if (line.len == 1 or (line.len == 2 and line[0] == '\r')) break;

            if (std.mem.startsWith(u8, line, "Content-Length:")) {
                const val = std.mem.trim(u8, line[15..], " \r\n");
                content_length = std.fmt.parseInt(usize, val, 10) catch 0;
            }
        }

        // Ler body se houver
        var body: ?[]const u8 = null;
        if (content_length > 0) {
            const body_buf = try allocator.alloc(u8, content_length);
            _ = try reader.readAll(body_buf);
            body = body_buf;
        }

        const ctx = Context{
            .allocator = allocator,
            .reader = reader,
            .writer = writer,
            .method = method,
            .path = path,
            .body = body,
        };

        handleRequest(ctx) catch |err| {
            std.debug.print("Erro: {}\n", .{err});
        };

        if (body) |b| allocator.free(b);
        conn.stream.close();
    }
}

Testando a API

# Iniciar servidor
zig run api.zig

# Listar tarefas (vazio inicialmente)
curl http://localhost:8080/tasks
# Output: []

# Criar tarefa
curl -X POST http://localhost:8080/tasks \
  -H "Content-Type: application/json" \
  -d '{"title":"Aprender Zig"}'
# Output: {"id":1,"title":"Aprender Zig","completed":false}

# Criar outra
curl -X POST http://localhost:8080/tasks \
  -H "Content-Type: application/json" \
  -d '{"title":"Criar API"}'

# Listar todas
curl http://localhost:8080/tasks
# Output: [{"id":1,...},{"id":2,...}]

# Atualizar
curl -X PUT http://localhost:8080/tasks/1 \
  -H "Content-Type: application/json" \
  -d '{"completed":true}'

# Deletar
curl -X DELETE http://localhost:8080/tasks/2

Servindo Arquivos Estáticos

Static File Server

const std = @import("std");

const MIME_TYPES = std.StaticStringMap([]const u8).initComptime(.{
    .{ ".html", "text/html" },
    .{ ".css", "text/css" },
    .{ ".js", "application/javascript" },
    .{ ".json", "application/json" },
    .{ ".png", "image/png" },
    .{ ".jpg", "image/jpeg" },
    .{ ".gif", "image/gif" },
    .{ ".svg", "image/svg+xml" },
    .{ ".ico", "image/x-icon" },
});

fn serveFile(writer: std.net.Stream.Writer, path: []const u8) !void {
    // Sanitizar path
    if (std.mem.indexOf(u8, path, "..") != null) {
        try sendError(writer, 403, "Forbidden");
        return;
    }

    // Default para index.html
    const file_path = if (std.mem.eql(u8, path, "/")) 
        "public/index.html" 
    else 
        try std.fmt.allocPrint(
            std.heap.page_allocator,
            "public{s}",
            .{path}
        );

    const file = std.fs.cwd().openFile(file_path, .{}) catch {
        try sendError(writer, 404, "Not found");
        return;
    };
    defer file.close();

    const content = file.readToEndAlloc(std.heap.page_allocator, 10 * 1024 * 1024) catch {
        try sendError(writer, 500, "Internal error");
        return;
    };
    defer std.heap.page_allocator.free(content);

    // Detectar MIME type
    const ext = std.fs.path.extension(file_path);
    const mime = MIME_TYPES.get(ext) orelse "application/octet-stream";

    try sendResponse(writer, 200, "OK", mime, content);
}

fn sendResponse(
    writer: std.net.Stream.Writer,
    status: u16,
    status_text: []const u8,
    content_type: []const u8,
    body: []const u8,
) !void {
    const response = try std.fmt.allocPrint(
        std.heap.page_allocator,
        "HTTP/1.1 {d} {s}\r\n" ++
            "Content-Type: {s}\r\n" ++
            "Content-Length: {d}\r\n" ++
            "\r\n",
        .{ status, status_text, content_type, body.len }
    );
    defer std.heap.page_allocator.free(response);

    try writer.writeAll(response);
    try writer.writeAll(body);
}

fn sendError(writer: std.net.Stream.Writer, status: u16, message: []const u8) !void {
    try sendResponse(writer, status, message, "text/plain", message);
}

pub fn main() !void {
    const address = try std.net.Address.parseIp4("127.0.0.1", 8080);
    var server = try address.listen(.{ .reuse_address = true });
    defer server.deinit();

    std.debug.print("Static server em http://127.0.0.1:8080\n", .{});
    std.debug.print("Coloque arquivos em ./public/\n", .{});

    var read_buffer: [1024]u8 = undefined;

    while (true) {
        const conn = try server.accept();
        defer conn.stream.close();

        const reader = conn.stream.reader();
        const writer = conn.stream.writer();

        const request_line = try reader.readUntilDelimiterOrEof(
            &read_buffer,
            '\n'
        ) orelse continue;

        var parts = std.mem.split(u8, request_line, " ");
        const method = parts.next() orelse continue;
        const path = parts.next() orelse continue;

        // Ignorar headers
        while (true) {
            const line = try reader.readUntilDelimiterOrEof(
                &read_buffer,
                '\n'
            ) orelse break;
            if (line.len <= 2) break;
        }

        if (std.mem.eql(u8, method, "GET")) {
            try serveFile(writer, path);
        } else {
            try sendError(writer, 405, "Method not allowed");
        }
    }
}

Middleware e Logging

Adicionando Logging

fn logRequest(method: []const u8, path: []const u8, status: u16, duration_ms: u64) void {
    const timestamp = std.time.timestamp();
    const datetime = timestampToDateTime(timestamp);
    
    std.debug.print(
        "[{d:0>4}-{d:0>2}-{d:0>2} {d:0>2}:{d:0>2}:{d:0>2}] ",
        .{ datetime.year, datetime.month, datetime.day, datetime.hour, datetime.minute, datetime.second }
    );
    std.debug.print("{s} {s} - {d} ({d}ms)\n", .{ method, path, status, duration_ms });
}

fn timestampToDateTime(timestamp: i64) struct { year: u16, month: u8, day: u8, hour: u8, minute: u8, second: u8 } {
    // Simplificação — use biblioteca de data real em produção
    const seconds_per_day = 86400;
    const days = @divFloor(timestamp, seconds_per_day);
    const seconds_of_day = @mod(timestamp, seconds_per_day);
    
    const hour = @intCast(u8, @divFloor(seconds_of_day, 3600));
    const minute = @intCast(u8, @mod(@divFloor(seconds_of_day, 60), 60));
    const second = @intCast(u8, @mod(seconds_of_day, 60));
    
    // Aproximação simples (não considera anos bissextos)
    const year = 1970 + @intCast(u16, @divFloor(days, 365));
    const day_of_year = @mod(days, 365);
    const month = @intCast(u8, @divFloor(day_of_year, 30) + 1);
    const day = @intCast(u8, @mod(day_of_year, 30) + 1);
    
    return .{
        .year = year,
        .month = month,
        .day = day,
        .hour = hour,
        .minute = minute,
        .second = second,
    };
}

Melhores Práticas

1. Sempre Use defer para Cleanup

const conn = try server.accept();
defer conn.stream.close(); // Sempre fecha a conexão

2. Limite o Tamanho dos Buffers

// ✅ Bom: buffer limitado
var body_buf = try allocator.alloc(u8, content_length);
if (content_length > 10 * 1024 * 1024) {
    return error.BodyTooLarge;
}

// ❌ Ruim: aceitar qualquer tamanho

3. Sanitize Inputs

// ✅ Verificar path traversal
if (std.mem.indexOf(u8, path, "..") != null) {
    return error.Forbidden;
}

4. Use Allocators Apropriados

// ✅ GPA para debug, PageAllocator/FixedBuffer para produção
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
// Em produção: considere FixedBufferAllocator ou Arena

5. Trate Erros Gracefully

handleRequest(ctx) catch |err| {
    std.log.err("Erro na requisição: {}", .{err});
    sendError(writer, 500, "Internal Server Error") catch {};
};

Build e Deploy

build.zig para Servidor Web

const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const exe = b.addExecutable(.{
        .name = "zig-server",
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });

    b.installArtifact(exe);

    const run_cmd = b.addRunArtifact(exe);
    run_cmd.step.dependOn(b.getInstallStep());

    const run_step = b.step("run", "Run the server");
    run_step.dependOn(&run_cmd.step);
}

Docker para Deploy

# Dockerfile
FROM alpine:latest AS builder
RUN apk add --no-cache curl
WORKDIR /app
RUN curl -L https://ziglang.org/download/0.13.0/zig-linux-x86_64-0.13.0.tar.xz | tar xJ
ENV PATH="/app/zig-linux-x86_64-0.13.0:${PATH}"
COPY . .
RUN zig build -Doptimize=ReleaseSafe

FROM scratch
COPY --from=builder /app/zig-out/bin/zig-server /server
EXPOSE 8080
ENTRYPOINT ["/server"]

Cross-Compilation

# Linux x86_64
zig build -Dtarget=x86_64-linux-gnu -Doptimize=ReleaseSafe

# Linux ARM64 (Raspberry Pi, AWS Graviton)
zig build -Dtarget=aarch64-linux-gnu -Doptimize=ReleaseSafe

# Windows
zig build -Dtarget=x86_64-windows-gnu -Doptimize=ReleaseSafe

# macOS
zig build -Dtarget=x86_64-macos-none -Doptimize=ReleaseSafe
zig build -Dtarget=aarch64-macos-none -Doptimize=ReleaseSafe

Comparação com Outras Linguagens

AspectoZigNode.jsGoRust
Deps HTTP0 (stdlib)0 (built-in)0 (stdlib)1 (hyper/axum)
Binary size~100KB50MB+ (com runtime)~2MB~1MB
MemoryManualGCGCOwnership
Performance⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
ComplexidadeMédiaBaixaBaixaAlta

Próximos Passos

Agora que você sabe criar servidores HTTP em Zig:

  1. HTTP Client em Zig — Consuma outras APIs do seu servidor
  2. Parsing JSON em Zig — APIs REST geralmente usam JSON
  3. Zig e C: Interoperabilidade — Use bibliotecas C avançadas
  4. Async/Await em Zig — Servidores concorrentes

Construa APIs rápidas e seguras com Zig. Compartilhe seus projetos!

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.