Project Structure
This page explains the standard directory structure generated by zzz new, where each file lives, and how the workspace model works when you use multiple zzz packages together.
Default layout
Section titled “Default layout”Running zzz new my_app creates the following structure:
my_app/ build.zig build.zig.zon .gitignore .env .env.example Dockerfile docker-compose.yml .dockerignore src/ main.zig controllers/ config/ config.zig dev.zig prod.zig staging.zig templates/ public/ css/ style.css js/ app.jsWhat each file and folder does
Section titled “What each file and folder does”Build files
Section titled “Build files”| File | Purpose |
|---|---|
build.zig | Zig build script. Wires the zzz dependency, loads the config module selected by -Denv, and creates the executable. |
build.zig.zon | Package manifest. Declares your project name, version, minimum Zig version, and dependencies (including the path to zzz.zig). |
Source code
Section titled “Source code”| Path | Purpose |
|---|---|
src/main.zig | Application entry point. Defines the router, mounts middleware, and starts the server. |
src/controllers/ | Route handler modules. The --full scaffold places home.zig and api.zig here. |
Configuration
Section titled “Configuration”| Path | Purpose |
|---|---|
config/config.zig | Defines the AppConfig struct shared across all environments. This is the single source of truth for your configuration shape. |
config/dev.zig | Development defaults (host 127.0.0.1, port 4000). Selected by default when you run zig build run. |
config/prod.zig | Production defaults (host 0.0.0.0, port 8080). Selected with zig build run -Denv=prod. |
config/staging.zig | Staging defaults. Production-like settings with the same bind address and port as prod. |
.env | Runtime environment variable overrides. Loaded automatically at startup and merged on top of the comptime config. |
.env.example | Documented template of all supported environment variables. Committed to version control (.env itself is gitignored). |
Static assets and templates
Section titled “Static assets and templates”| Path | Purpose |
|---|---|
public/ | Static files served by the staticFiles middleware. Contains css/ and js/ subdirectories by default. |
templates/ | Template files for server-side rendering (used with the zzz_template package). |
Docker
Section titled “Docker”| File | Purpose |
|---|---|
Dockerfile | Multi-stage build that installs Zig, compiles a release binary, and produces a minimal Debian image. |
docker-compose.yml | Runs the app container with health checks. When --db=postgres is used, it also includes PostgreSQL and Adminer services. |
.dockerignore | Prevents .env, build caches, and .git/ from being copied into the Docker image. |
Pass --docker=false to zzz new to skip generating Docker files entirely.
Scaffold variants
Section titled “Scaffold variants”The zzz new command accepts flags that change which files are generated:
| Flag | Effect |
|---|---|
--full | Adds controller files (src/controllers/home.zig, src/controllers/api.zig) and uses a main.zig with session, CSRF, CORS, and static file middleware pre-configured. |
--api | Generates a JSON-only project. Skips the templates/ and public/ directories. Uses a minimal main.zig with no static file serving. |
--db=sqlite | Adds a database_url field to AppConfig and sets up .env with a SQLite connection string. |
--db=postgres | Same as sqlite but with a PostgreSQL connection URL, plus a docker-compose.yml that includes a Postgres service. |
--docker=false | Omits Dockerfile, docker-compose.yml, and .dockerignore. |
Example:
zzz new my_api --api --db=postgres --docker=falseThe entry point
Section titled “The entry point”Every zzz application starts in src/main.zig. The scaffolded entry point follows a standard pattern:
const std = @import("std");const zzz = @import("zzz");const app_config = @import("app_config");
const Router = zzz.Router;const Context = zzz.Context;
fn index(ctx: *Context) !void { ctx.html(.ok, \\<!DOCTYPE html> \\<html> \\<head><title>Welcome to Zzz</title></head> \\<body> \\ <h1>Welcome to Zzz!</h1> \\ <p>Your new project is ready.</p> \\</body> \\</html> );}
const App = Router.define(.{ .middleware = &.{ zzz.logger, zzz.healthCheck(.{}), }, .routes = &.{ Router.get("/", index), },});
pub fn main(init: std.process.Init) !void { const allocator = init.gpa; const io = init.io;
var env = try zzz.Env.init(allocator, .{}); defer env.deinit();
const config = zzz.mergeWithEnv(@TypeOf(app_config.config), app_config.config, &env);
var server = zzz.Server.init(allocator, .{ .host = config.host, .port = config.port, }, App.handler);
try server.listen(io);}The key steps are:
- Define routes and middleware using
Router.defineat comptime. - Load environment variables with
Env.init, which reads.envand system env vars. - Merge config with
mergeWithEnv, overlaying runtime env vars on top of the comptime defaults fromconfig/dev.zig(or whichever environment was selected at build time). - Create and start the server with
Server.initandserver.listen.
How the config module system works
Section titled “How the config module system works”The build script uses -Denv to select which config file is compiled in:
const env_name = b.option([]const u8, "env", "Environment: dev (default), prod, staging") orelse "dev";
var config_path_buf: [64]u8 = undefined;const config_path = std.fmt.bufPrint(&config_path_buf, "config/{s}.zig", .{env_name}) catch "config/dev.zig";This means zig build run loads config/dev.zig, while zig build run -Denv=prod loads config/prod.zig. Each environment file imports the shared AppConfig struct from config/config.zig and provides its own defaults:
const AppConfig = @import("config").AppConfig;
pub const config: AppConfig = .{ .host = "127.0.0.1", .port = 4000, .secret_key_base = "dev-secret-not-for-production",};The selected config is exposed to your application code as the app_config import.
Workspace model
Section titled “Workspace model”When using multiple zzz packages (database, jobs, mailer, templates), the recommended layout is a workspace directory containing sibling packages:
my_workspace/ zzz.zig/ # Core framework zzz_db/ # Database package zzz_jobs/ # Background jobs zzz_mailer/ # Email sending zzz_template/ # Template engine my_app/ # Your application build.zig build.zig.zon src/ config/ ...Your application’s build.zig.zon references sibling packages by relative path:
.dependencies = .{ .zzz = .{ .path = "../zzz.zig", },},This approach means all packages share the same source tree, making it straightforward to develop against local changes. Consumer packages like zzz_jobs and zzz_mailer do not need their own linkSystemLibrary calls — symbols propagate through module dependencies automatically.
Next steps
Section titled “Next steps”- Learn how to configure the server and tune timeouts, TLS, and worker threads
- Understand Routing to add more endpoints
- Add Middleware for logging, auth, and more