Spider is a web framework for Zig. This guide gets you from zero to a running HTTP server in under 5 minutes.
---
There are two ways to start a Spider project:
curl and use spider new to scaffoldzig fetchBoth produce the same result. Choose the one that fits your workflow.
---
curl -fsSL https://spiderme.org/install.sh | bash
This installs the spider command to ~/.local/bin/spider. Verify it works:
spider help
spider new myapp
cd myapp
This generates a complete project with:
build.zig + build.zig.zon — build configurationspider.config.zig — runtime configurationsrc/main.zig — entry point with route setupsrc/shared/templates/ — layout, nav, sidebar, toast componentssrc/features/home/ — sample controller + viewDockerfile + docker-compose.yml — PostgreSQL and deployment.env.example — environment variables templatepublic/ — static assets (CSS, JS, images)bin/ — Tailwind CSS CLI (auto-downloaded)cp .env.example .env
docker compose up -d # starts PostgreSQL
spider g feature todo
This creates src/features/todo/ with controller, view, and module registration.
zig build run
Spider server starting on port 3000...
Server listening on http://127.0.0.1:3000
Starting 12 worker threads
Visit http://localhost:3000.
---
mkdir myapp && cd myapp
zig init
zig fetch --save git+https://github.com/llllOllOOll/spider
Replace build.zig with:
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const spider_dep = b.dependency("spider", .{
.target = target,
});
const exe = b.addExecutable(.{
.name = "myapp",
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
.imports = &.{
.{ .name = "spider", .module = spider_dep.module("spider") },
},
}),
});
const gen = b.addRunArtifact(spider_dep.artifact("generate-templates"));
gen.addArg("src/");
gen.addArg("src/embedded_templates.zig");
exe.step.dependOn(&gen.step);
b.installArtifact(exe);
const run_step = b.step("run", "Run the app");
const run_cmd = b.addRunArtifact(exe);
run_step.dependOn(&run_cmd.step);
run_cmd.step.dependOn(b.getInstallStep());
}
src/main.zig:
const std = @import("std");
const spider = @import("spider");
pub fn main() void {
var server = spider.app();
defer server.deinit();
server
.get("/", homeHandler)
.listen(.{ .port = 3000 }) catch {};
}
fn homeHandler(c: *spider.Ctx) !spider.Response {
return c.text("Hello, Spider!", .{});
}
Create spider.config.zig:
const spider = @import("spider");
pub const config = spider.Config{
.port = 3000,
.env = .development,
};
Create docker-compose.yml:
services:
pg:
image: postgres:16-alpine
environment:
POSTGRES_USER: spider
POSTGRES_PASSWORD: spider
POSTGRES_DB: myapp
ports:
- "5432:5432"
Create .env:
PG_HOST=localhost
PG_PORT=5432
PG_USER=spider
PG_PASSWORD=spider
PG_DB=myapp
docker compose up -d
zig build run
Spider server starting on port 3000...
Server listening on http://127.0.0.1:3000
Starting 12 worker threads
---
Both flows produce a running Spider app. From here you can:
Create src/index.html:
extends "layout"
<h1>Hello, { name }!</h1>
<p>Today is { date }.</p>
Update src/main.zig to use c.view():
const templates = @import("embedded_templates.zig").EmbeddedTemplates;
pub const spider_templates = templates;
fn home(c: *spider.Ctx) !spider.Response {
return c.view("index", .{
.title = "Home",
.name = "Seven",
.date = "2026-05-04",
}, .{});
}
spider g feature products
spider migrate
.listen(.{ .port = 3000 }) // override port only
.listen(.{ .host = "0.0.0.0" }) // override host only
.listen(.{ .port = 3000, .host = "0.0.0.0" }) // override both
.listen(.{}) // use config values
---