2026-05-04
Webdev With Zig
How this site is put together.
When working on backends for games, much of the infrastructure is custom-built for the game itself. The client, the servers, all the way down to the network protocol.
Just for this site, I tried my hand at using Zig to serve it. It's not the usual language chosen for web development, but it's lightweight, has a good build system, and it's easy to reason about the runtime memory usage.
The web server uses http.zig. Creating custom routes with it is about as easy as many other popular libraries, like FastAPI for Python.
var server = try httpz.Server(void).init(
allocator,
.{
.port = 8080,
.address = "0.0.0.0",
.request = .{ .max_form_count = 4 }
},
{},
);
defer {
server.stop();
server.deinit();
}
var router = try server.router(.{});
router.get("/", getHomepage, .{});
router.get("/posts/:post_id", getPost, .{});
Then the handlers simply take a request and response, which can be read/written to using Zig's new Reader/Writer:
fn getHomepage(req: *httpz.Req, res: *httpz.Res) !void {
_ = req;
// Open the index.html file
const cwd = std.fs.cwd();
const index_file = try cwd.openFile("index.html", .{});
defer index_file.close();
var rb: [256]u8 = undefined;
var reader = index_file.reader(&rb);
// Stream the index into the response:
res.content_type = .HTML;
res.clearWriter();
const writer = res.writer();
_ = try reader.interface.stream(writer, .unlimited);
try writer.flush();
}
Static Content
Posts are written in markdown, converted to HTML, and then injected into the response. They also have a template file, which looks like this:
<div class="content">
<header href="/">
<h1>{ voxygen }</h1>
<p class="subtitle">gamedev • voxels • procedural generation • code</p>
</header>
<main>
<a href="/" class="back-link">← Back to all posts</a>
<article>
<div class="post-content">
VX_POST_CONTENT
</div>
</article>
The
VX_POST_CONTENTstring is detected when reading the file, and the corresponding markdown file is read, converted, and sent in its place.
For this, I created a markdown to HTML converter. It supports headings, thematic breaks, nested lists, block quotes, code blocks, and embedded HTML in under 1K lines of code. Some features of markdown vastly complicate the parsing logic, so I left those out. For example, setext headings require a lookahead because the current line could be a heading if the next line contains underlines. The features it supports are good enough for my use case.
To create a post, I just make a .md file in the posts/ directory of the web server.
Dynamic Content
Comments are handled with a small JavaScript file for the client interactivity. Clients submit a form to a REST endpoint, and the server stores it on disk. There's no fancy database here; the server just stores them in flatfiles for each post.
There are no accounts either. It's the same as old IRC where anyone can impersonate anyone else. I don't think this really matters because I'm not building a social media site here.
Summary
Overall, I like how simple this system is. The attack surface is low, because the server doesn't do much. There is no potential for SQL injection, because it doesn't use SQL. All of the changes are stored in version control too, so the whole history of the website is backed up. The cost to run the server is only $2.50 USD per month using a tiny Vultr VPS. I think this is pretty good, since people are paying over $10 a month for Squarespace subscriptions.
Comments
Set a username to leave comments:
Commenting as: