Zig es un lenguaje de programación moderno diseñado para ser robusto, óptimo y mantenible. Con su enfoque en el rendimiento, el control explícito sobre la asignación de memoria y las características de tiempo de compilación, Zig es una excelente opción para construir aplicaciones de alto rendimiento, incluyendo las API REST que necesitan manejar cargas significativas con un uso mínimo de recursos.
En este tutorial, exploraremos cómo aprovechar Zig para crear una API REST que no solo sea funcional sino "increíblemente rápida". Veremos cómo configurar un proyecto, implementar la funcionalidad principal de la API y optimizarla para obtener el máximo rendimiento. Al final, tendrás una base sólida para construir servicios web de alto rendimiento utilizando Zig.

Apidog combina la documentación, el diseño, las pruebas y la depuración de la API en un flujo de trabajo perfecto, lo que lo hace perfecto para probar nuestra API Zig de alto rendimiento.

Configurando tu Entorno de Desarrollo Zig
Antes de empezar a programar, asegurémonos de que tienes todo lo necesario para desarrollar con Zig:
Instalar Zig: Descarga la última versión de ziglang.org o utiliza un gestor de paquetes:
# On macOS with Homebrew
brew install zig
# On Linux with apt
sudo apt install zig
Verificar la instalación:
zig version
Estructura del Proyecto
Creemos una estructura de proyecto limpia:
mkdir zig-rest-api
cd zig-rest-api
zig init-exe
Esto crea un proyecto Zig básico con un archivo src/main.zig
. Ampliaremos esta estructura:
zig-rest-api/
├── src/
│ ├── main.zig
│ ├── server.zig
│ ├── router.zig
│ ├── handlers/
│ │ ├── users.zig
│ │ └── products.zig
│ └── models/
│ ├── user.zig
│ └── product.zig
├── build.zig
└── README.md
Implementación del Servidor HTTP
Primero, creemos un servidor HTTP básico. La biblioteca estándar de Zig proporciona primitivas de red, pero no incluye un servidor HTTP completo. Utilizaremos el excelente paquete zhttp
.
Añade esta dependencia a tu build.zig
:
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const zhttp = b.dependency("zhttp", .{});
const exe = b.addExecutable(.{
.name = "zig-rest-api",
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = optimize,
});
exe.addModule("zhttp", zhttp.module("zhttp"));
b.installArtifact(exe);
}
Ahora implementemos nuestro servidor en src/server.zig
:
const std = @import("std");
const zhttp = @import("zhttp");
const Allocator = std.mem.Allocator;
pub const Server = struct {
server: zhttp.Server,
allocator: Allocator,
pub fn init(allocator: Allocator, port: u16) !Server {
return Server{
.server = try zhttp.Server.init(allocator, .{
.address = "0.0.0.0",
.port = port,
}),
.allocator = allocator,
};
}
pub fn deinit(self: *Server) void {
self.server.deinit();
}
pub fn start(self: *Server) !void {
std.log.info("Servidor escuchando en el puerto {d}", .{self.server.port});
try self.server.start();
}
};
Añadiendo el Enrutador y el Manejo de Peticiones
En src/router.zig
, creemos un enrutador simple:
const std = @import("std");
const zhttp = @import("zhttp");
const Allocator = std.mem.Allocator;
pub const Router = struct {
routes: std.StringHashMap(HandlerFn),
allocator: Allocator,
pub const HandlerFn = fn(zhttp.Request, *zhttp.Response) anyerror!void;
pub fn init(allocator: Allocator) Router {
return Router{
.routes = std.StringHashMap(HandlerFn).init(allocator),
.allocator = allocator,
};
}
pub fn deinit(self: *Router) void {
self.routes.deinit();
}
pub fn addRoute(self: *Router, path: []const u8, handler: HandlerFn) !void {
try self.routes.put(try self.allocator.dupe(u8, path), handler);
}
pub fn handleRequest(self: *Router, req: zhttp.Request, res: *zhttp.Response) !void {
const handler = self.routes.get(req.path) orelse {
res.status = .not_found;
try res.send("No encontrado");
return;
};
try handler(req, res);
}
};
Manejo de JSON
Creemos funciones de utilidad para trabajar con JSON:
// src/json_utils.zig
const std = @import("std");
pub fn parseJson(comptime T: type, json_str: []const u8, allocator: std.mem.Allocator) !T {
var tokens = std.json.TokenStream.init(json_str);
return try std.json.parse(T, &tokens, .{
.allocator = allocator,
.ignore_unknown_fields = true,
});
}
pub fn stringify(value: anytype, allocator: std.mem.Allocator) ![]const u8 {
return std.json.stringifyAlloc(allocator, value, .{});
}
Construyendo los Controladores de la API
Ahora creemos un controlador para los recursos de usuario en src/handlers/users.zig
:
const std = @import("std");
const zhttp = @import("zhttp");
const json_utils = @import("../json_utils.zig");
const User = struct {
id: usize,
name: []const u8,
email: []const u8,
};
// In-memory store for demonstration
var users = std.ArrayList(User).init(std.heap.page_allocator);
pub fn getUsers(req: zhttp.Request, res: *zhttp.Response) !void {
const json = try json_utils.stringify(users.items, req.allocator);
res.headers.put("Content-Type", "application/json") catch unreachable;
try res.send(json);
}
pub fn getUserById(req: zhttp.Request, res: *zhttp.Response) !void {
// Extract ID from URL path parameters
const id_str = req.params.get("id") orelse {
res.status = .bad_request;
try res.send("Falta el parámetro ID");
return;
};
const id = try std.fmt.parseInt(usize, id_str, 10);
// Find user with matching ID
for (users.items) |user| {
if (user.id == id) {
const json = try json_utils.stringify(user, req.allocator);
res.headers.put("Content-Type", "application/json") catch unreachable;
try res.send(json);
return;
}
}
res.status = .not_found;
try res.send("Usuario no encontrado");
}
pub fn createUser(req: zhttp.Request, res: *zhttp.Response) !void {
const body = try req.body();
var user = try json_utils.parseJson(User, body, req.allocator);
// Generate a new ID
user.id = users.items.len + 1;
try users.append(user);
res.status = .created;
res.headers.put("Content-Type", "application/json") catch unreachable;
const json = try json_utils.stringify(user, req.allocator);
try res.send(json);
}
Juntando Todo
Ahora, actualicemos nuestro src/main.zig
para integrar todo:
const std = @import("std");
const Server = @import("server.zig").Server;
const Router = @import("router.zig").Router;
const users = @import("handlers/users.zig");
pub fn main() !void {
// Create an arena allocator
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();
var router = Router.init(allocator);
defer router.deinit();
// Register routes
try router.addRoute("/api/users", users.getUsers);
try router.addRoute("/api/users/{id}", users.getUserById);
try router.addRoute("/api/users", users.createUser);
var server = try Server.init(allocator, 8080);
defer server.deinit();
// Set up the request handler
server.server.setRequestHandler(handler);
// Start the server
try server.start();
}
fn handler(req: zhttp.Request, res: *zhttp.Response) !void {
std.log.info("{s} {s}", .{@tagName(req.method), req.path});
try router.handleRequest(req, res);
}
Optimizaciones de Rendimiento
Zig proporciona varias características que hacen que nuestra API sea increíblemente rápida:
- Abstracciones de coste cero: El enfoque de Zig asegura que las abstracciones no añadan sobrecarga.
- Metaprogramación en tiempo de compilación: Podemos realizar el trabajo en tiempo de compilación en lugar de en tiempo de ejecución.
- Gestión manual de la memoria: Al controlar las asignaciones, evitamos las pausas de recolección de basura.
- Asignadores de arena: Perfectos para la memoria de ámbito de solicitud que se puede liberar rápidamente de una vez.
- Funciones en línea: Las rutas críticas se pueden poner en línea para eliminar la sobrecarga de la llamada a la función.
Optimicemos nuestro enrutador:
// Add inline attribute to critical functions
pub inline fn handleRequest(self: *Router, req: zhttp.Request, res: *zhttp.Response) !void {
// Existing code...
}
Y optimicemos nuestro controlador de peticiones con un pool de hilos:
const ThreadPool = struct {
threads: []std.Thread,
pub fn init(thread_count: usize) !ThreadPool {
var threads = try std.heap.page_allocator.alloc(std.Thread, thread_count);
for (threads) |*thread, i| {
thread.* = try std.Thread.spawn(.{}, workerFn, .{i});
}
return ThreadPool{ .threads = threads };
}
fn workerFn(id: usize) void {
// Process incoming requests
while (true) {
// Get request from queue and process
}
}
};
Conclusión
En este tutorial, hemos explorado cómo aprovechar las características centradas en el rendimiento de Zig para construir una API REST de alto rendimiento. Al aprovechar la gestión manual de la memoria, las características de tiempo de compilación y las abstracciones de coste cero de Zig, hemos creado un servidor de API que es robusto e increíblemente rápido.
Cubrimos:
- Configuración de un entorno de desarrollo Zig
- Construcción de un servidor HTTP básico
- Creación de un enrutador para manejar los puntos finales de la API
- Implementación de la serialización y deserialización de JSON
- Añadir controladores de puntos finales de la API
- Optimización para el rendimiento
Esta base te proporciona las herramientas para construir APIs listas para producción en Zig que superan a las escritas en muchos otros lenguajes. A medida que continúes desarrollando con Zig, explora características más avanzadas como comptime, el manejo de errores y la compilación cruzada para mejorar aún más tus aplicaciones.