Cómo usar Zig para construir una API REST

En este tutorial, exploraremos cómo usar Zig para crear una API REST funcional y "ultrarrápida".

Daniel Costa

Daniel Costa

15 April 2025

Cómo usar Zig para construir una API REST

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.

💡
Si bien muchos desarrolladores están familiarizados con Postman como la herramienta de prueba de API, me gustaría presentarles Apidog, una alternativa poderosa que ofrece una plataforma completa de desarrollo de API.

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.

button

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:

  1. Abstracciones de coste cero: El enfoque de Zig asegura que las abstracciones no añadan sobrecarga.
  2. Metaprogramación en tiempo de compilación: Podemos realizar el trabajo en tiempo de compilación en lugar de en tiempo de ejecución.
  3. Gestión manual de la memoria: Al controlar las asignaciones, evitamos las pausas de recolección de basura.
  4. Asignadores de arena: Perfectos para la memoria de ámbito de solicitud que se puede liberar rápidamente de una vez.
  5. 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:

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.

Explore more

Cómo usar Ollama: Guía Completa para Principiantes sobre LLMs Locales con Ollama

Cómo usar Ollama: Guía Completa para Principiantes sobre LLMs Locales con Ollama

El panorama de la inteligencia artificial evoluciona constantemente, y los Grandes Modelos de Lenguaje (LLM) se vuelven cada vez más potentes y accesibles. Aunque muchos interactúan con estos modelos a través de servicios basados en la nube, existe un movimiento creciente enfocado en ejecutarlos directamente en computadoras personales. Aquí es donde entra Ollama. Ollama es una herramienta potente pero fácil de usar, diseñada para simplificar drásticamente el complejo proceso de descargar, config

28 April 2025

¿Dónde Descargar Swagger UI en Español Gratis?

¿Dónde Descargar Swagger UI en Español Gratis?

¿Necesitas Swagger UI en español? Este artículo explica por qué no existe una descarga oficial gratuita y cómo habilitar la traducción. Explora las características de Swagger y por qué Apidog es la alternativa superior para diseño, pruebas y documentación API integrados.

23 April 2025

¿Dónde Descargar Postman en Español Gratis?

¿Dónde Descargar Postman en Español Gratis?

¿Puedes descargar Postman en español gratis? Aunque Postman carece de soporte nativo en español, existen soluciones. Explóralas y descubre Apidog, una potente alternativa unificada a Postman diseñada para optimizar todo tu flujo de trabajo de API, sin importar el idioma.

22 April 2025

Practica el diseño de API en Apidog

Descubre una forma más fácil de construir y usar APIs