Creating a New Project
The zzz new command generates a ready-to-run project with a sensible directory structure, environment-based configuration, and optional database and Docker support.
zzz new <project_name> [options]Project names may contain letters, numbers, underscores, hyphens, and dots.
Options
Section titled “Options”| Flag | Default | Description |
|---|---|---|
--db=<engine> | none | Add database support. Values: sqlite, postgres (or pg), none |
--full | off | Scaffold a full-stack app with controllers, middleware, and static file serving |
--api | off | Scaffold an API-only app (no templates/ or public/ directories) |
--docker=false | true | Disable Docker file generation |
Getting started
Section titled “Getting started”-
Create the project:
Terminal window zzz new my_app -
Enter the project directory and run:
Terminal window cd my_appzig build run -
Open your browser at
http://127.0.0.1:4000.
Generated directory structure
Section titled “Generated directory structure”The default scaffold creates the following layout:
my_app/ build.zig build.zig.zon .gitignore .env .env.example Dockerfile .dockerignore docker-compose.yml config/ config.zig # Shared AppConfig struct dev.zig # Development defaults prod.zig # Production defaults staging.zig # Staging defaults src/ main.zig # Application entry point and router controllers/ templates/ public/ css/style.css js/app.jsVariations
Section titled “Variations”When you pass --api, the templates/ and public/ directories are omitted and the generated main.zig contains a JSON-only API skeleton.
When you pass --full, the scaffold includes pre-built controllers (src/controllers/home.zig and src/controllers/api.zig) and a full middleware stack with error handling, CORS, body parsing, sessions, CSRF protection, static file serving, and health checks.
Environment-based configuration
Section titled “Environment-based configuration”Every generated project uses a two-layer configuration system:
- Comptime defaults — defined in
config/dev.zig,config/prod.zig, orconfig/staging.zig, selected at build time with the-Denvflag. - Runtime overrides — loaded from
.envand system environment variables viazzz.mergeWithEnv.
Select the environment when building:
zig build run # uses config/dev.zig (default)zig build run -Denv=prod # uses config/prod.zigzig build run -Denv=staging # uses config/staging.zigThe shared AppConfig struct in config/config.zig defines the available fields:
pub const AppConfig = struct { host: []const u8 = "127.0.0.1", port: u16 = 4000, secret_key_base: []const u8 = "change-me-in-production",};When --db is set, a database_url field is added automatically.
Database support
Section titled “Database support”SQLite
Section titled “SQLite”zzz new my_app --db=sqliteSets DATABASE_URL=sqlite:my_app.db in the generated .env file and adds database_url to the config struct.
PostgreSQL
Section titled “PostgreSQL”zzz new my_app --db=postgresSets DATABASE_URL=postgres://zzz:zzz@localhost:5432/zzz_dev and generates a docker-compose.yml that includes PostgreSQL 17 and Adminer services.
Docker support
Section titled “Docker support”By default, every new project includes:
- Dockerfile — Multi-stage build that compiles with
ReleaseSafeand produces a minimal Debian-based image. - .dockerignore — Excludes
.envfiles, Zig caches, and Git metadata. - docker-compose.yml — Runs the application on port 4000 with a health check. When using
--db=postgres, the compose file adds PostgreSQL and Adminer containers.
To skip Docker files entirely:
zzz new my_app --docker=falseGenerated main.zig
Section titled “Generated main.zig”The default main.zig sets up a minimal router with a single route and request logging:
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);}Next steps
Section titled “Next steps”- Generate controllers, models, and channels for your new project
- Run database migrations if you used
--db - Start the development server with auto-reload