Buildable example
Blog — ZigBase examples
A minimal app on ZigBase-as-a-library: a slugify hook and a Zigapagos frontend served from a runtime-selected directory.
A minimal application built on ZigBase as a library. It imports the zigbase module, then registers a single before_create record hook on the posts collection that derives a URL slug from the post title when one isn’t supplied. Everything else — HTTP API, SQLite storage, auth, CLI — comes straight from the framework.
What it proves
This example exists primarily as a packaging proof: building it demonstrates that the SQLite C sources and the zap dependency travel transitively through the published zigbase module into a downstream consumer package. If this compiles and runs, ZigBase works as a real dependency.
Pre-1.0: ZigBase is pre-1.0 — the hook-config shape and module API may change between releases.
The hook
const std = @import("std");
const zigbase = @import("zigbase");
fn slugify(ctx: *zigbase.Ctx, ev: *zigbase.RecordEvent) anyerror!void {
_ = ctx;
if (ev.record.* != .object) return;
if (ev.record.object.get("slug") != null) return;
const title = if (ev.record.object.get("title")) |t| switch (t) {
.string => |s| s,
else => return,
} else return;
// Record mutations MUST allocate with `ev.arena.a` (`ev.arena` is a typed
// request arena; `.a` is the allocator that owns `ev.record`).
const buf = try ev.arena.a.alloc(u8, title.len);
var len: usize = 0;
var in_run = false;
for (title) |ch| {
if (std.ascii.isAlphanumeric(ch)) {
buf[len] = std.ascii.toLower(ch);
len += 1;
in_run = true;
} else if (in_run) {
buf[len] = '-';
len += 1;
in_run = false;
}
}
if (len > 0 and buf[len - 1] == '-') len -= 1;
try ev.record.object.put(ev.arena.a, "slug", .{ .string = buf[0..len] }); // "Hello, World!" -> "hello-world"
}
pub fn main(init: std.process.Init) !void {
return zigbase.App(.{
.hooks = .{ .posts = .{ .beforeCreate = slugify } },
}).runCli(init);
}
Using ZigBase in your own project
Add the dependency:
zig fetch --save git+https://github.com/valthon/zigbase
Wire the module into your build.zig:
const zigbase = @import("zigbase");
const zb = b.dependency("zigbase", .{ .target = target, .optimize = optimize });
zigbase.addTo(zb, exe_mod);
addTo adds the import and sets .link_libc = true (SQLite needs libc), so the two can’t drift apart.
Frontend (Zigapagos frontend)
frontend/ is a Zigapagos site with targeted Preact islands: a public post list/detail and a login + “write a post” island that exercises the slugify hook. The example’s comptime .collections schema (users + posts) provisions itself on startup, so the whole thing works from a fresh data directory.
cd frontend && ./build.sh && cd ..
zig build
# --insecure-cookies: local dev is over plain HTTP, and auth cookies are Secure by default.
# A strong JWT secret is auto-generated and persisted under the data dir on first run.
./zig-out/bin/blog serve --insecure-cookies --data-dir ./data --serve-static frontend/dist
# open http://127.0.0.1:8090/ — same-origin frontend, so no --realtime-origins needed
This demonstrates ZigBase’s default static-files mode: the binary serves frontend/dist at the root path because you passed --serve-static. The other modes (comptime-hardcoded dir, fully embedded) are shown by the golfsim and plugins examples.
Building and running
This example needs Zig 0.16, which you can get via mise (mise exec zig@0.16.0 -- zig ...). From examples/blog/:
cd frontend && ./build.sh && cd ..
zig build # produces ./zig-out/bin/blog
./zig-out/bin/blog superuser create --email you@example.com --password <pw> --data-dir ./data
# --insecure-cookies: local dev is over plain HTTP, and auth cookies are Secure by default.
# A strong JWT secret is auto-generated and persisted under the data dir on first run.
./zig-out/bin/blog serve --insecure-cookies --data-dir ./data --serve-static frontend/dist
Then create a posts record without a slug and the hook fills it in from the title.