Apidog

オールインワン協働API開発プラットフォーム

API設計

APIドキュメント

APIデバッグ

APIモック

API自動テスト

Zigを使ってREST APIを構築する方法

@apidog

@apidog

Updated on 4月 2, 2025

Zigは、堅牢で最適化され、メンテナンスしやすいことを目指して設計された現代的なプログラミング言語です。パフォーマンス、メモリ割り当てに対する明示的な制御、およびコンパイル時機能に焦点を当てているため、Zigは高パフォーマンスのアプリケーション、特に最小限のリソース使用で大きな負荷を処理する必要があるREST APIの構築に最適です。

このチュートリアルでは、Zigを活用して「驚異的に速い」REST APIを作成する方法を探ります。プロジェクトのセットアップ、コアAPI機能の実装、ピークパフォーマンスへの最適化を行います。最後には、Zigを使用して高パフォーマンスのWebサービスを構築するための堅実な基盤を得ることができます。

💡
多くの開発者がAPIテストツールとしてPostmanに慣れている一方で、完全なAPI開発プラットフォームを提供する強力な代替ツールであるApidogを紹介します。

Apidogは、APIのドキュメント作成、デザイン、テスト、およびデバッグを1つのシームレスなワークフローに統合しており、高パフォーマンスのZig APIをテストするのに最適です。

button

Zig開発環境の設定

コーディングを始める前に、Zigで開発するために必要なものがすべて揃っているか確認しましょう:

Zigをインストールziglang.orgから最新バージョンをダウンロードするか、パッケージマネージャーを使用します:

# macOSでHomebrewを使用する場合
brew install zig

# Linuxでaptを使用する場合
sudo apt install zig

インストールを確認

zig version

プロジェクト構造

クリーンなプロジェクト構造を作成しましょう:

mkdir zig-rest-api
cd zig-rest-api
zig init-exe

これにより、src/main.zigファイルを含む基本的なZigプロジェクトが作成されます。この構造を拡張していきます:

zig-rest-api/
├── src/
│   ├── main.zig
│   ├── server.zig
│   ├── router.zig
│   ├── handlers/
│   │   ├── users.zig
│   │   └── products.zig
│   └── models/
│       ├── user.zig
│       └── product.zig
├── build.zig
└── README.md

HTTPサーバーの実装

まず、基本的なHTTPサーバーを作成します。Zigの標準ライブラリはネットワークプライミティブを提供しますが、完全なHTTPサーバーは含まれていません。優れたzhttpパッケージを使用します。

この依存関係を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);
}

次に、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("サーバーはポート {d} でリスニング中", .{self.server.port});
        try self.server.start();
    }
};

ルーターとリクエスト処理の追加

src/router.zigで、シンプルなルーターを作成しましょう:

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("見つかりませんでした");
            return;
        };

        try handler(req, res);
    }
};

JSON処理

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, .{});
}

APIハンドラーの構築

次に、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,
};

// デモ用のインメモリストア
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 {
    // URLパスパラメータからIDを抽出
    const id_str = req.params.get("id") orelse {
        res.status = .bad_request;
        try res.send("IDパラメータがありません");
        return;
    };
    
    const id = try std.fmt.parseInt(usize, id_str, 10);
    
    // 一致する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("ユーザーが見つかりませんでした");
}

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);
    
    // 新しい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);
}

全体をまとめる

これで、すべてを統合するためにsrc/main.zigを更新しましょう:

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 {
    // アリーナアロケータを作成
    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();

    // ルートを登録
    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();

    // リクエストハンドラを設定
    server.server.setRequestHandler(handler);

    // サーバーを開始
    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);
}

パフォーマンスの最適化

Zigは、APIを驚異的に速くするいくつかの機能を提供しています:

  1. ゼロコスト抽象化:Zigのアプローチは、抽象がオーバーヘッドを追加しないことを保証します。
  2. コンパイル時メタプログラミング:ランタイムではなくコンパイル時に作業を行うことができます。
  3. 手動メモリ管理:割り当てを制御することにより、ガベージコレクションの中断を回避します。
  4. アリーナアロケータ:すぐに一度に解放できるリクエストスコープのメモリに最適です。
  5. インライン関数:重要な経路はインライン化され、関数呼び出しのオーバーヘッドを排除できます。

ルーターを最適化しましょう:

// 重要な関数にインライン属性を追加
pub inline fn handleRequest(self: *Router, req: zhttp.Request, res: *zhttp.Response) !void {
    // 既存のコード...
}

スレッドプールを使用してリクエストハンドラを最適化しましょう:

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 {
        // 受信リクエストを処理する
        while (true) {
            // キューからリクエストを取得して処理
        }
    }
};

結論

このチュートリアルでは、Zigのパフォーマンスに焦点を当てた機能を活用して高パフォーマンスなREST APIを構築する方法を探りました。Zigの手動メモリ管理、コンパイル時機能、およびゼロコスト抽象化を利用することで、堅牢で驚異的に速いAPIサーバーを作成しました。

次のトピックについて説明しました:

  • Zig開発環境の設定
  • 基本的なHTTPサーバーの構築
  • APIエンドポイントを処理するルーターの作成
  • JSONシリアル化とデシリアル化の実装
  • APIエンドポイントハンドラーの追加
  • パフォーマンスの最適化

この基盤により、他の多くの言語で書かれたものを上回る、Zigで本番用のAPIを構築するためのツールが提供されます。Zigでの開発を続ける中で、コンパイル時、エラーハンドリング、クロスコンパイルなど、さらに進んだ機能を探求してアプリケーションをさらに強化してください。