Project Structure

Understanding the structure of projects created by the TriStack CLI

Overview

TriStack scaffolds backend projects in Python, Go, and Rust. Each language follows its own native conventions, so a generated project looks like a real developer wrote it:

  • Python — pyproject.toml, sources under src/, migrations/
  • Go — go.mod, a root-level main.go, internal/ packages
  • Rust — Cargo.toml, sources under src/

The exact tree depends on your selections: base files are always present, then framework, ORM, migrations, database, and add-on files are layered on top. This page mirrors what the CLI actually writes.

How the tree is built

Files come from these layers, in order:

  1. Base — always included (README.md, tristack.jsonc, .env.example, .gitignore, plus the language manifest: pyproject.toml, go.mod, or Cargo.toml)
  2. Framework — the HTTP entrypoint per framework
  3. ORM — the data layer
  4. Migrations — migration files/tooling
  5. Frontend — the server-rendered UI layer (per selected frontend)
  6. Database — database choice drives configuration and env values
  7. Addons — one set of files per selected addon

tristack.jsonc records the stack and its reproducible command. See tristack.jsonc.

Python

Root layout (default stack)

The default stack (fastapi + sqlmodel + alembic + sqlite + uv + docker, ruff, pytest) looks like this:

README.md
tristack.jsonc
pyproject.toml
.env.example
.gitignore
.dockerignore
Dockerfile
docker-compose.yml
ruff.toml

Notes:

  • The import package is a fixed src/ directory — the project name never appears inside the tree. The distribution/project name in pyproject.toml is still your snake_case project name (my_tristack_app for my-tristack-app).
  • Docker/CI files exist only when the matching addon is selected; tests/ only with the pytest addon.

Framework variations

All Python frameworks share the base + import package, then differ in the entrypoint:

  • FastAPI — main.py with a FastAPI app (uv run uvicorn src.main:app --reload)
  • Litestar — main.py with a Litestar app (uv run uvicorn src.main:app --reload)
  • Flask — main.py with a Flask app (uv run flask --app src.main run --debug)
  • Django — no main.py; instead manage.py plus a config package (settings, asgi.py, wsgi.py, urls.py) and per-domain apps under apps/ (core, users)
manage.py

Django brings its own ORM and migrations, so it skips the import-package layout entirely — the project name is only used for the root folder, and code lives in config/ and apps/.

ORM variations

  • SQLModel — adds db.py (engine/session) and models.py (models) to src/
  • SQLAlchemy / Tortoise — adds the data-layer files to src/
  • None — no data-layer files

Migrations

With --migrations alembic, the CLI writes a migrations/ directory with alembic.ini, env.py, script.py.mako, and a versions/ folder for your migration files. Django uses its own bundled migrations.

Frontend

The frontend layer adds server-rendered pages served by the same backend (no separate UI server). With --frontend htmx, all Python frameworks except Django use the import-package layout:

  • web.py — the page routes (/ and a fragment route like /web/items) returning Jinja2 templates
  • templates/ — base.html (layout with the HTMX script), home.html, and the fragment (e.g. items.html)
  • static/css/style.css — the shared stylesheet
  • pyproject.toml installs jinja2 (or litestar[jinja] for Litestar)

Django instead gets a web app plus root templates:

config/urls.py includes apps.web.urls, apps.web is registered in INSTALLED_APPS, and STATICFILES_DIRS picks up the project static/ folder. Django ships its own template engine and static handling, so no extra dependencies are added.

Addons

AddonAdds
dockerDockerfile, docker-compose.yml, .dockerignore
ruffruff.toml
mypymypy.ini
pytesttests/test_health.py
github-actions.github/workflows/ci.yml

Go

Every Go project is a single module with its entrypoint at cmd/api/main.go and a layered package tree under internal/ (config, handler, service, repository, model, db). Framework and data imports are pinned in go.mod.

Root layout (default stack)

The default stack (gin + gorm + goose + sqlite + go + docker, air, golangci-lint) looks like this:

README.md
tristack.jsonc
go.mod
Makefile
.env.example
.gitignore
.dockerignore
Dockerfile
docker-compose.yml
air.toml
.golangci.yml

Notes:

  • The module path is the slugged project name (my_api for my-api) unless your project name is already a module path.
  • The server reads PORT (default 8000) and APP_NAME from the environment via internal/config.
  • The entrypoint wires the layers: config.Load() → db.Connect() → repository → service → handler, then registers GET /health, GET /items, and POST /items.
  • orm must not be none for the handler/service/repository/model layers to be generated.

Framework variations

All frameworks share the entrypoint at cmd/api/main.go and the same internal/handler interface (ListItems, CreateItem); only the router wiring differs:

  • Gin — gin.Default() with GET /health
  • Fiber — fiber.New() with GET /health
  • Echo — echo.New() with GET /health
  • Chi — chi.NewRouter() with GET /health
  • Go stdlib — http.NewServeMux() with method-pattern GET /health

ORM variations

  • GORM — internal/model/item.go (struct), internal/db/db.go (connection + AutoMigrate(&model.Item{})), internal/repository/item.go (gorm.DB)
  • sqlx — internal/model/item.go (db: tags), internal/db/db.go (connection), internal/repository/item.go (sqlx.DB, placeholders via Rebind)
  • sqlc — internal/model/item.go (plain JSON struct), internal/db/db.go (connection), internal/repository/item.go (wraps generated internal/db/sqlc queries), plus root schema/ + queries/ + sqlc.yaml; run sqlc generate via the generate Makefile target before building
  • None — no persistence layers (db, repository, model, service, handler are skipped; only /health is exposed)

The driver is chosen from the database value (sqlite, postgres, or mysql) and pinned in go.mod.

Migrations

  • goose — a migrations/ folder of .sql files (0001_create_items.sql)
  • golang-migrate — a db/migrations/ folder with paired .up.sql / .down.sql files

Frontend

With --frontend htmx, Go adds a web layer under internal/web that registers page routes alongside the API. The handlers — GET / and a fragment like GET /web/items — render html/template, so no extra dependency is added:

  • web.go — a package internal/web exposing a New constructor that returns a handler wiring the page routes (/ and a fragment like /web/items) to their templates
  • templates/ — base.html (layout with the HTMX script), index.html, and the fragment (e.g. items.html)
  • static/css/style.css — the shared stylesheet, mounted at /static/
  • cmd/api/main.go — mounts the web handler under the framework's router alongside the API routes

Addons

AddonAdds
dockerDockerfile, docker-compose.yml, .dockerignore
airair.toml (live reload)
golangci-lint.golangci.yml
github-actions.github/workflows/ci.yml

Rust

Every Rust project is a single cargo package with its sources under src/. The Cargo build target is the default binary target, so main.rs lives at src/main.rs.

Root layout (default stack)

The default stack (axum + seaorm + cargo + docker, cargo-watch, clippy) looks like this:

README.md
tristack.jsonc
Cargo.toml
.env.example
.gitignore
.dockerignore
Dockerfile
docker-compose.yml

Notes:

  • The package name is the slugged project name (my_api for my-api).
  • The web framework entrypoints read PORT (default 8000) and APP_NAME from the environment (main.rs) and expose GET /health.
  • Dependencies (serde, tokio, the framework, and the ORM) are pinned in Cargo.toml.

Framework variations

Every framework writes its entrypoint to src/main.rs. Each web framework exposes GET /health returning {"status":"ok"}:

  • Axum — axum::Router with a #[tokio::main] async runtime
  • Actix Web — the actix-web HttpServer/App
  • Rocket — rocket routes with the #[launch] (or #[rocket::main]) runtime
  • Warp — a warp filter pipeline on tokio
  • Salvo — a salvo Router on tokio
  • Loco — a minimal, Rails-like loco-rs entrypoint scaffold; generate the full app (controllers, initializers) with cargo loco new

ORM variations

The data layer always lands at src/db.rs and is wired to main.rs via mod db;:

  • SeaORM — an async connection helper (DatabaseConnection for postgres/mysql, the SQLite connection for sqlite)
  • Diesel — a synchronous connection helper (PgConnection / MysqlConnection / SqliteConnection)
  • sqlx — an async pool helper (PgPool / MySqlPool / SqlitePool)
  • None — no src/db.rs and no mod db;

The database driver is chosen from the database value and enabled via Cargo features in Cargo.toml.

Migrations

Rust has no standard migration tool in the option set yet, so --migrations is none. Run migrations with your ORM's own tooling (e.g. diesel migration run, sqlx migrate run).

Frontend

With --frontend htmx, Rust adds Askama templates under src/templates/. The page templates are ORM-agnostic — the IndexTemplate / NowTemplate render data computed in main.rs, so the htmx frontend works with any ORM (or none):

  • main.rs — registers GET / (rendering IndexTemplate) and GET /web/now (rendering the NowTemplate fragment with the current Unix time)
  • src/templates/ — index.html (layout with the HTMX script and inline CSS) and now.html (the fragment)
  • Cargo.toml — adds askama = "0.14"

Loco doesn't support htmx yet — the CLI rejects --framework loco --frontend htmx.

Addons

AddonAdds
dockerDockerfile, docker-compose.yml, .dockerignore
cargo-watchauto-reload dev server (run with cargo watch -x run)
clippystrict clippy lints in CI (cargo clippy -- -D warnings)
github-actions.github/workflows/ci.yml

Config file

tristack.jsonc at the project root captures the resolved stack and a reproducibleCommand that reproduces it, regardless of language. See tristack.jsonc.

Frontend roadmap

The frontend dimension is language-aware: the CLI only offers options that exist for the selected language. This table tracks the full roadmap as frontends land.

LanguageFrontendStatus
PythonhtmxAvailable
Pythonreflex / niceguiPlanned
GohtmxAvailable
GotemplPlanned
RusthtmxAvailable
RustleptosPlanned

On this page