Construindo uma API REST Completa com Zig

Construindo uma API REST Completa com Zig

Neste artigo, construímos uma API REST funcional com Zig do zero: roteamento de URLs, serialização/desserialização JSON, middleware de logging e validação, tratamento de erros estruturado e exemplos de integração com banco de dados.

Estrutura do Projeto

minha-api/
├── build.zig
├── build.zig.zon
└── src/
    ├── main.zig
    ├── router.zig
    ├── handlers/
    │   ├── usuarios.zig
    │   └── health.zig
    └── models/
        └── usuario.zig

Modelo de Dados

// src/models/usuario.zig
const std = @import("std");

pub const Usuario = struct {
    id: u64,
    nome: []const u8,
    email: []const u8,
    ativo: bool,

    pub fn toJson(self: Usuario, allocator: std.mem.Allocator) ![]const u8 {
        var buf = std.ArrayList(u8).init(allocator);
        const writer = buf.writer();

        try writer.print(
            \\{{"id":{d},"nome":"{s}","email":"{s}","ativo":{s}}}
        , .{ self.id, self.nome, self.email, if (self.ativo) "true" else "false" });

        return buf.toOwnedSlice();
    }
};

Roteador

// src/router.zig
const std = @import("std");
const http = std.http;

pub const Handler = *const fn (*http.Server.Response, std.mem.Allocator) anyerror!void;

pub const Router = struct {
    rotas: std.StringHashMap(Handler),

    pub fn init(allocator: std.mem.Allocator) Router {
        return .{ .rotas = std.StringHashMap(Handler).init(allocator) };
    }

    pub fn registrar(self: *Router, path: []const u8, handler: Handler) !void {
        try self.rotas.put(path, handler);
    }

    pub fn despachar(self: *Router, response: *http.Server.Response, allocator: std.mem.Allocator) !void {
        const path = response.request.target;

        if (self.rotas.get(path)) |handler| {
            try handler(response, allocator);
        } else {
            response.status = .not_found;
            try response.do();
            try response.writeAll("{\"erro\": \"rota não encontrada\"}");
            try response.finish();
        }
    }
};

Handler de Usuários

// src/handlers/usuarios.zig
const std = @import("std");
const http = std.http;
const Usuario = @import("../models/usuario.zig").Usuario;

// Simulação de banco de dados em memória
var proximo_id: u64 = 1;

pub fn listar(response: *http.Server.Response, allocator: std.mem.Allocator) !void {
    _ = allocator;
    response.status = .ok;
    try response.do();
    try response.writeAll("[{\"id\":1,\"nome\":\"Ana\",\"email\":\"ana@exemplo.com\",\"ativo\":true}]");
    try response.finish();
}

pub fn criar(response: *http.Server.Response, allocator: std.mem.Allocator) !void {
    // Ler body da requisição
    var buf: [4096]u8 = undefined;
    const body_len = try response.readAll(&buf);
    const body = buf[0..body_len];

    // Parse JSON
    const parsed = std.json.parseFromSlice(
        struct { nome: []const u8, email: []const u8 },
        allocator,
        body,
        .{},
    ) catch {
        response.status = .bad_request;
        try response.do();
        try response.writeAll("{\"erro\": \"JSON inválido\"}");
        try response.finish();
        return;
    };
    defer parsed.deinit();

    const usuario = Usuario{
        .id = proximo_id,
        .nome = parsed.value.nome,
        .email = parsed.value.email,
        .ativo = true,
    };
    proximo_id += 1;

    const json = try usuario.toJson(allocator);
    defer allocator.free(json);

    response.status = .created;
    try response.do();
    try response.writeAll(json);
    try response.finish();
}

Servidor Principal

// src/main.zig
const std = @import("std");
const Router = @import("router.zig").Router;
const usuarios = @import("handlers/usuarios.zig");

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

    var router = Router.init(allocator);
    try router.registrar("/api/v1/usuarios", usuarios.listar);
    try router.registrar("/health", healthCheck);

    const endereco = std.net.Address.initIp4(.{ 0, 0, 0, 0 }, 8080);
    var server = std.http.Server.init(allocator, .{});
    try server.listen(endereco);

    std.log.info("API rodando em http://localhost:8080", .{});

    while (true) {
        var response = try server.accept();
        defer response.deinit();
        try router.despachar(&response, allocator);
    }
}

fn healthCheck(response: *std.http.Server.Response, _: std.mem.Allocator) !void {
    response.status = .ok;
    try response.do();
    try response.writeAll("{\"status\":\"healthy\"}");
    try response.finish();
}

Conclusão

Construir uma API REST em Zig exige mais código boilerplate do que em Go ou Node.js, mas o resultado é um servidor extremamente performático, com binário estático de poucos MB, startup instantâneo e uso mínimo de memória. Para APIs de alta performance, Zig é uma excelente escolha.

Conteúdo Relacionado

Continue aprendendo Zig

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