Skip to content

1. Set up

Zig and the CLI

kzig needs Zig 0.17.0. Get it from ziglang.org/download and check it's the one on your path:

$ zig version
0.17.0

The kzig CLI scaffolds apps, generates modules and writes migrations. Download it for Linux or macOS from the releases (on macOS, a browser download needs xattr -d com.apple.quarantine kzig), or build it:

$ git clone https://github.com/forinda/kzig && cd kzig
$ zig build            # zig-out/bin/kzig; put it on your PATH

A new app

$ kzig new bookmarks
  create bookmarks/build.zig
  create bookmarks/build.zig.zon
  …
  create bookmarks/src/modules/hello/hello.test.zig
$ cd bookmarks
$ zig build test

kzig new fetched kzig into build.zig.zon and wrote an app with one module:

bookmarks/
  build.zig, build.zig.zon     # `zig build`, `zig build run`, `zig build test`
  src/main.zig                 # the App: modules, config, middleware, plugins
  src/config.zig               # PORT, KZIG_ENV, LOG_LEVEL from the environment
  src/modules.zig              # every mounted module; the generators edit it
  src/test_app.zig             # boots a fresh app per test
  src/modules/hello/           # a module: controller, service, test

src/main.zig is the whole application:

src/main.zig
const std = @import("std");
const kzig = @import("kzig");
const Config = @import("config.zig").Config;
const modules = @import("modules.zig").modules;

pub const App = kzig.App(.{
    .modules = modules,
    .config = Config,
    .middleware = .{
        kzig.middleware.requestId(),
        kzig.middleware.requestLogger(.{ .skip = &.{"/health"} }),
        kzig.middleware.helmet(.{}),
    },
    // /openapi.json, /docs, /redoc — development and test only.
    .plugins = &.{kzig.swagger.Plugin(.{
        .info = .{ .title = "bookmarks", .version = "0.0.1" },
        .disable_in_prod = true,
    })},
});

pub fn main(init: std.process.Init) !void {
    var app: App = undefined;
    try app.init(init.io, init.gpa, init.environ_map);
    defer app.deinit();
    // Until SIGINT/SIGTERM, then drains in-flight requests.
    try kzig.runtime.std.serve(App, &app, init.io, init.gpa, .{ .port = app.get(Config).port });
}

test {
    _ = @import("modules/hello/hello.test.zig");
    // kzig:tests
}

Run it:

$ KZIG_ENV=development zig build run
info: listening on http://127.0.0.1:3000
$ curl localhost:3000/api/v1/hello
{"message":"Hello World from kzig!"}
$ curl localhost:3000/health/ready
{"status":"ready","checks":[]}

Open http://localhost:3000/docs for Swagger UI. Every route mounts at /api/v1/<module path><route path>; /health/live and /health/ready are built in.

Rebuild on save

kzig dev builds, runs, and restarts the app whenever src/ changes. A build that fails leaves the running app up.

Next: a first module.