Criar servidores HTTP é essencial para desenvolvimento backend moderno. Com Zig, você pode construir APIs pequenas, previsíveis e fáceis de distribuir como binários únicos, sem carregar um runtime pesado para cada deploy.
Este guia é a rota prática para quem pesquisa por zig server, zig HTTP server ou std.http.Server e quer sair com um projeto executável. Vamos criar desde um servidor simples até uma API REST com roteamento, JSON, build, Docker e checklist de produção para VPS, containers e ambientes de empresas brasileiras.
Resposta rápida: como criar uma API REST em Zig
| Objetivo | Caminho recomendado | Link interno |
|---|---|---|
| Criar um servidor HTTP local | std.net.Address.listen, server.accept() e uma resposta HTTP mínima para entender o fluxo | Use este guia como ponto de partida |
| Usar a biblioteca padrão para APIs | std.http.Server, buffer de headers, handler por rota e respostas com status explícito | Combine com Parsing JSON em Zig |
| Consumir APIs externas no mesmo projeto | std.http.Client para GET, POST, JSON, autenticação e limites de resposta | Zig HTTP Client: GET, POST, JSON e APIs REST em Zig |
| Enviar payload JSON para outra API | std.http.Client.open(.POST, ...), Content-Type: application/json e content_length definido | Como Fazer Requisições HTTP POST em Zig |
| Colocar o servidor no ar | Reverse proxy, limite de body, logs, health check, shutdown e deploy reversível | Zig HTTP Server em Produção |
Se você quer apenas aprender a API, comece com std.http.Server. Se a meta é um produto público, trate o servidor como parte de uma pilha maior: proxy na frente, binário Zig atrás, observabilidade mínima, versão do Zig fixada e rollback claro. Para projetos que precisam de roteamento mais ergonômico, veja também o panorama de clientes e bibliotecas HTTP no ecossistema Zig antes de adicionar dependências.
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
Quando usar std.http.Server, httpz ou zap?
A demanda por servidores HTTP em Zig aparece em três cenários diferentes. Escolher a ferramenta certa evita transformar um estudo simples em arquitetura grande demais.
| Cenário | Melhor ponto de partida | Por quê |
|---|---|---|
| Aprender HTTP, requests e respostas | std.http.Server | Mostra o fluxo real da conexão sem esconder alocação, buffers e erros |
| API interna pequena ou ferramenta de automação | std.http.Server ou httpz | A biblioteca padrão basta para poucos endpoints; httpz reduz boilerplate quando o projeto cresce |
| Serviço público com alta concorrência no Linux | zap, httpz ou arquitetura própria | Frameworks ajudam com roteamento, middleware e performance, mas adicionam dependências |
Para portfólio, entrevista e aprendizado, comece com std.http.Server. Para produto em produção, valide requisitos como TLS, logs, timeouts, limites de body, shutdown gracioso e observabilidade antes de escolher a pilha final.
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
Checklist para colocar no ar no Brasil
Um servidor HTTP em Zig normalmente nasce como ferramenta interna, webhook, API para dashboard ou serviço de alta performance. Antes de expor em produção, feche esta lista mínima:
- Versão do Zig fixada: use a mesma versão no ambiente local, CI e Dockerfile. Zig ainda evolui rápido, então builds reproduzíveis importam.
- Timeouts e limites: defina limite de tamanho para body, cabeçalhos e tempo de leitura para evitar conexões presas.
- Logs estruturados: registre método, path, status, duração e erro sem gravar tokens ou dados pessoais.
- Health check: mantenha uma rota como
/healthzpara Kubernetes, systemd, Fly.io, Render ou VPS com monitoramento. - TLS fora do binário: para a maioria dos projetos brasileiros, coloque Caddy, Nginx, Cloudflare Tunnel ou load balancer na frente e deixe Zig falar HTTP interno.
- Deploy reversível: publique binário versionado, mantenha a versão anterior e valide rollback antes de trocar tráfego.
Essa disciplina é o que separa um tutorial de um serviço útil para uma empresa. Zig facilita o binário pequeno; a confiabilidade vem do processo ao redor.
std.http.Server, httpz ou zap: decisão rápida
Quem chega pesquisando por zig server nem sempre quer a mesma coisa. Algumas pessoas querem aprender a biblioteca padrão, outras querem publicar uma API REST pequena e outras estão comparando Zig com Go, Rust ou Node para tráfego real. Use este mapa antes de escolher a stack:
| Situação | Use primeiro | O que validar antes de crescer |
|---|---|---|
| Tutorial, entrevista ou protótipo de API | std.http.Server | Roteamento, parsing JSON, status HTTP e testes locais com curl |
| Ferramenta interna com poucos endpoints | std.http.Server com helpers pequenos | Limite de body, logs, health check e deploy reversível |
| API REST pública com rotas e middleware | httpz ou framework similar | Maturidade da versão, exemplos recentes, tratamento de erro e compatibilidade com sua versão do Zig |
| Serviço de alta concorrência no Linux | zap, arquitetura própria ou proxy agressivo na frente | Benchmarks no hardware real, timeouts, observabilidade e rollback |
Não escolha framework apenas porque ele parece mais completo. Em Zig, dependência também significa acompanhar mudanças de compilador, alocadores, API de rede e build system. Para a maioria dos leitores brasileiros começando em backend com Zig, o melhor caminho é: criar um servidor mínimo aqui, consumir uma API externa com Zig HTTP Client, enviar JSON com a receita de HTTP POST em Zig e só então avaliar bibliotecas HTTP do ecossistema.
Se o projeto virar produto, trate a escolha como decisão operacional. Escreva um health check, rode atrás de Caddy ou Nginx, defina versão fixa do Zig, mantenha um binário anterior para rollback e documente como medir erro 5xx, latência e uso de memória. Esses pontos são mais importantes para crescimento real do que trocar std.http.Server por um framework antes de existir tráfego.
Comparação com Outras Linguagens
| Aspecto | Zig | Node.js | Go | Rust |
|---|---|---|---|---|
| Deps HTTP | 0 (stdlib) | 0 (built-in) | 0 (stdlib) | 1 (hyper/axum) |
| Binary size | ~100KB | 50MB+ (com runtime) | ~2MB | ~1MB |
| Memory | Manual | GC | GC | Ownership |
| Performance | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Complexidade | Média | Baixa | Baixa | Alta |
Próximos Passos
Agora que você sabe criar servidores HTTP em Zig:
- HTTP Client em Zig — Consuma outras APIs do seu servidor
- Parsing JSON em Zig — APIs REST geralmente usam JSON
- Zig e C: Interoperabilidade — Use bibliotecas C avançadas
- Async/Await em Zig — Servidores concorrentes
Construa APIs rápidas e seguras com Zig. Compartilhe seus projetos!