# Project Structure (/docs/project-structure)



## Overview [#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 [#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`](/docs/tristack-config).

## Python [#python]

### Root layout (default stack) [#root-layout-default-stack]

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

<Files>
  <Folder name="/">
    <File name="README.md" />

    <File name="tristack.jsonc" />

    <File name="pyproject.toml" />

    <File name=".env.example" />

    <File name=".gitignore" />

    <File name=".dockerignore" />

    <File name="Dockerfile" />

    <File name="docker-compose.yml" />

    <File name="ruff.toml" />

    <Folder name=".github">
      <Folder name="workflows">
        <File name="ci.yml" />
      </Folder>
    </Folder>

    <Folder name="src">
      <File name="__init__.py" />

      <File name="config.py" />

      <File name="main.py" />

      <File name="db.py" />

      <File name="models.py" />
    </Folder>

    <Folder name="migrations">
      <File name="alembic.ini" />

      <File name="env.py" />

      <File name="script.py.mako" />

      <Folder name="versions">
        <File name="README.md" />
      </Folder>
    </Folder>

    <Folder name="tests">
      <File name="test_health.py" />
    </Folder>
  </Folder>
</Files>

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 [#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`)

<Files>
  <Folder name="django-project/">
    <File name="manage.py" />

    <Folder name="config">
      <File name="__init__.py" />

      <File name="asgi.py" />

      <File name="urls.py" />

      <File name="wsgi.py" />

      <Folder name="settings">
        <File name="__init__.py" />

        <File name="base.py" />

        <File name="development.py" />

        <File name="production.py" />
      </Folder>
    </Folder>

    <Folder name="apps">
      <File name="__init__.py" />

      <Folder name="core">
        <File name="__init__.py" />

        <File name="apps.py" />

        <File name="models.py" />

        <File name="urls.py" />

        <File name="views.py" />
      </Folder>

      <Folder name="users">
        <File name="__init__.py" />

        <File name="apps.py" />

        <File name="models.py" />

        <File name="selectors.py" />

        <File name="serializers.py" />

        <File name="services.py" />

        <File name="urls.py" />

        <File name="views.py" />
      </Folder>
    </Folder>

    <Folder name="templates">
      <File name="base.html" />
    </Folder>
  </Folder>
</Files>

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 [#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 [#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 [#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:

<Files>
  <Folder name="htmx-project/">
    <Folder name="src">
      <File name="web.py" />

      <Folder name="templates">
        <File name="base.html" />

        <File name="home.html" />

        <File name="items.html" />
      </Folder>
    </Folder>

    <Folder name="static">
      <Folder name="css">
        <File name="style.css" />
      </Folder>
    </Folder>
  </Folder>
</Files>

* `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:

<Files>
  <Folder name="django-htmx-project/">
    <Folder name="apps">
      <Folder name="web">
        <File name="__init__.py" />

        <File name="urls.py" />

        <File name="views.py" />
      </Folder>
    </Folder>

    <Folder name="templates">
      <File name="base.html" />

      <File name="home.html" />

      <File name="users_fragment.html" />
    </Folder>

    <Folder name="static">
      <Folder name="css">
        <File name="style.css" />
      </Folder>
    </Folder>
  </Folder>
</Files>

`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 [#addons]

| Addon            | Adds                                                |
| ---------------- | --------------------------------------------------- |
| `docker`         | `Dockerfile`, `docker-compose.yml`, `.dockerignore` |
| `ruff`           | `ruff.toml`                                         |
| `mypy`           | `mypy.ini`                                          |
| `pytest`         | `tests/test_health.py`                              |
| `github-actions` | `.github/workflows/ci.yml`                          |

## Go [#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) [#root-layout-default-stack-1]

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

<Files>
  <Folder name="/">
    <File name="README.md" />

    <File name="tristack.jsonc" />

    <File name="go.mod" />

    <File name="Makefile" />

    <File name=".env.example" />

    <File name=".gitignore" />

    <File name=".dockerignore" />

    <File name="Dockerfile" />

    <File name="docker-compose.yml" />

    <File name="air.toml" />

    <File name=".golangci.yml" />

    <Folder name=".github">
      <Folder name="workflows">
        <File name="ci.yml" />
      </Folder>
    </Folder>

    <Folder name="cmd">
      <Folder name="api">
        <File name="main.go" />
      </Folder>
    </Folder>

    <Folder name="internal">
      <Folder name="config">
        <File name="config.go" />
      </Folder>

      <Folder name="handler">
        <File name="handler.go" />
      </Folder>

      <Folder name="service">
        <File name="item_service.go" />
      </Folder>

      <Folder name="repository">
        <File name="item.go" />
      </Folder>

      <Folder name="model">
        <File name="item.go" />
      </Folder>

      <Folder name="db">
        <File name="db.go" />
      </Folder>
    </Folder>

    <Folder name="migrations">
      <File name="0001_create_items.sql" />
    </Folder>
  </Folder>
</Files>

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 [#framework-variations-1]

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 [#orm-variations-1]

* **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 [#migrations-1]

* **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 [#frontend-1]

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:

<Files>
  <Folder name="go-htmx-project/">
    <Folder name="internal">
      <Folder name="web">
        <File name="web.go" />

        <Folder name="templates">
          <File name="base.html" />

          <File name="index.html" />

          <File name="items.html" />
        </Folder>

        <Folder name="static">
          <Folder name="css">
            <File name="style.css" />
          </Folder>
        </Folder>
      </Folder>
    </Folder>
  </Folder>
</Files>

* `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 [#addons-1]

| Addon            | Adds                                                |
| ---------------- | --------------------------------------------------- |
| `docker`         | `Dockerfile`, `docker-compose.yml`, `.dockerignore` |
| `air`            | `air.toml` (live reload)                            |
| `golangci-lint`  | `.golangci.yml`                                     |
| `github-actions` | `.github/workflows/ci.yml`                          |

## Rust [#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) [#root-layout-default-stack-2]

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

<Files>
  <Folder name="/">
    <File name="README.md" />

    <File name="tristack.jsonc" />

    <File name="Cargo.toml" />

    <File name=".env.example" />

    <File name=".gitignore" />

    <File name=".dockerignore" />

    <File name="Dockerfile" />

    <File name="docker-compose.yml" />

    <Folder name=".github">
      <Folder name="workflows">
        <File name="ci.yml" />
      </Folder>
    </Folder>

    <Folder name="src">
      <File name="main.rs" />

      <File name="db.rs" />
    </Folder>
  </Folder>
</Files>

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 [#framework-variations-2]

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 [#orm-variations-2]

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 [#migrations-2]

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 [#frontend-2]

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):

<Files>
  <Folder name="rust-htmx-project/">
    <Folder name="src">
      <File name="main.rs" />

      <Folder name="templates">
        <File name="index.html" />

        <File name="now.html" />
      </Folder>
    </Folder>
  </Folder>
</Files>

* `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 [#addons-2]

| Addon            | Adds                                                      |
| ---------------- | --------------------------------------------------------- |
| `docker`         | `Dockerfile`, `docker-compose.yml`, `.dockerignore`       |
| `cargo-watch`    | auto-reload dev server (run with `cargo watch -x run`)    |
| `clippy`         | strict clippy lints in CI (`cargo clippy -- -D warnings`) |
| `github-actions` | `.github/workflows/ci.yml`                                |

## Config file [#config-file]

`tristack.jsonc` at the project root captures the resolved stack and a `reproducibleCommand` that reproduces it, regardless of language. See [`tristack.jsonc`](/docs/tristack-config).

## Frontend roadmap [#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.

| Language | Frontend             | Status    |
| -------- | -------------------- | --------- |
| Python   | `htmx`               | Available |
| Python   | `reflex` / `nicegui` | Planned   |
| Go       | `htmx`               | Available |
| Go       | `templ`              | Planned   |
| Rust     | `htmx`               | Available |
| Rust     | `leptos`             | Planned   |
