Zig est un langage de programmation moderne conçu pour être robuste, optimal et maintenable. Avec son accent sur la performance, le contrôle explicite de l'allocation de mémoire et les fonctionnalités de compilation, Zig est un excellent choix pour la création d'applications hautes performances, y compris les API REST qui doivent gérer des charges importantes avec une utilisation minimale des ressources.
Dans ce tutoriel, nous allons explorer comment utiliser Zig pour créer une API REST qui n'est pas seulement fonctionnelle, mais "extrêmement rapide". Nous allons voir comment configurer un projet, implémenter les fonctionnalités principales de l'API et l'optimiser pour des performances optimales. À la fin, vous aurez une base solide pour créer des services web hautes performances en utilisant Zig.

Apidog combine la documentation, la conception, les tests et le débogage d'API en un seul flux de travail transparent, ce qui le rend parfait pour tester notre API Zig hautes performances.

Configuration de votre environnement de développement Zig
Avant de commencer à coder, assurons-nous que vous avez tout ce qu'il faut pour développer avec Zig :
Installer Zig : Téléchargez la dernière version depuis ziglang.org ou utilisez un gestionnaire de paquets :
# On macOS with Homebrew
brew install zig
# On Linux with apt
sudo apt install zig
Vérifier l'installation :
zig version
Structure du projet
Créons une structure de projet propre :
mkdir zig-rest-api
cd zig-rest-api
zig init-exe
Cela crée un projet Zig de base avec un fichier src/main.zig. Nous allons développer cette structure :
zig-rest-api/
├── src/
│ ├── main.zig
│ ├── server.zig
│ ├── router.zig
│ ├── handlers/
│ │ ├── users.zig
│ │ └── products.zig
│ └── models/
│ ├── user.zig
│ └── product.zig
├── build.zig
└── README.md
Implémentation du serveur HTTP
Tout d'abord, créons un serveur HTTP de base. La bibliothèque standard de Zig fournit des primitives de mise en réseau, mais elle n'inclut pas un serveur HTTP complet. Nous allons utiliser l'excellent package zhttp.
Ajoutez cette dépendance à votre 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);
}
Implémentons maintenant notre serveur dans 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("Server listening on port {d}", .{self.server.port});
try self.server.start();
}
};
Ajout du routeur et de la gestion des requêtes
Dans src/router.zig, créons un routeur 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("Not Found");
return;
};
try handler(req, res);
}
};
Gestion JSON
Créons des fonctions utilitaires pour travailler avec 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, .{});
}
Création de gestionnaires d'API
Créons maintenant un gestionnaire pour les ressources utilisateur dans 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("Missing ID parameter");
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("User not found");
}
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);
}
Mettre le tout ensemble
Maintenant, mettons à jour notre src/main.zig pour tout intégrer :
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);
}
Optimisations de performance
Zig offre plusieurs fonctionnalités qui rendent notre API extrêmement rapide :
- Abstractions sans coût : L'approche de Zig garantit que les abstractions n'ajoutent pas de surcharge.
- Méta-programmation au moment de la compilation : Nous pouvons effectuer un travail au moment de la compilation au lieu du moment de l'exécution.
- Gestion manuelle de la mémoire : En contrôlant les allocations, nous évitons les pauses de garbage collection.
- Allocateurs d'arène : Parfait pour la mémoire limitée aux requêtes qui peut être rapidement libérée en une seule fois.
- Fonctions en ligne : Les chemins critiques peuvent être intégrés pour éliminer la surcharge des appels de fonction.
Optimisons notre routeur :
// Add inline attribute to critical functions
pub inline fn handleRequest(self: *Router, req: zhttp.Request, res: *zhttp.Response) !void {
// Existing code...
}
Et optimisons notre gestionnaire de requêtes avec un pool de threads :
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
}
}
};
Conclusion
Dans ce tutoriel, nous avons exploré comment utiliser les fonctionnalités axées sur la performance de Zig pour créer une API REST hautes performances. En tirant parti de la gestion manuelle de la mémoire de Zig, des fonctionnalités de compilation et des abstractions sans coût, nous avons créé un serveur d'API à la fois robuste et extrêmement rapide.
Nous avons couvert :
- Configuration d'un environnement de développement Zig
- Création d'un serveur HTTP de base
- Création d'un routeur pour la gestion des points de terminaison d'API
- Implémentation de la sérialisation et de la désérialisation JSON
- Ajout de gestionnaires de points de terminaison d'API
- Optimisation des performances
Cette base vous donne les outils nécessaires pour créer des API prêtes pour la production en Zig qui surpassent celles écrites dans de nombreux autres langages. Au fur et à mesure que vous continuez à développer avec Zig, explorez des fonctionnalités plus avancées comme comptime, la gestion des erreurs et la compilation croisée pour améliorer davantage vos applications.



