A minimal, opinionated template that mirrors the architecture introduced in the PyCon Italy 2026 talk Simplicity Scales - Rewriting to a Django Monolith and a Monorepo. Slides | Video recording (coming soon)
.
├── backend/ # Python code: one folder per deployable unit
│ ├── django/ # Django monolith — uv workspace
│ │ ├── apps/ # Django apps (notifications, ...)
│ │ ├── config/ # Django settings, Celery, ASGI, URLs
│ │ ├── compose.yaml # postgres + redis + celery worker
│ │ ├── Dockerfile
│ │ ├── justfile # setup / run / migrate / type-gen
│ │ └── pyproject.toml
│ ├── ml/ # ML workspace — uv workspace
│ │ ├── algorithm/ # Toy optimisation algorithm package
│ │ ├── evaluation/ # Evaluation pipeline package
│ │ ├── Dockerfile # Demonstrates packaging the algorithm as a container
│ │ ├── compose.yml
│ │ └── justfile
│ ├── util/ # Shared Python utilities (logging, env, ...)
│ ├── typings/ # Centralised type stubs for third-party libs
│ ├── pyproject.toml # Shared ruff + basedpyright configuration
│ └── justfile # Cross-workspace lint / type / test
├── frontend/ # Vite + React + TypeScript
├── infrastructure/ # OpenTofu / Terraform stub
├── generated/ # OpenAPI / AsyncAPI schemas (gitignored — regenerated by `just generate_types`)
├── .github/
│ └── CODEOWNERS
├── justfile # Repo-wide entrypoint
└── README.md
Requirements:
just setup # set everything up (backend + frontend + pre-commit)
just backend run # start Django + Celery worker + Postgres + Redis
just run # start the frontend (Vite dev server on :5173)The included demo: POST /api/notifications/ simulates an outgoing email.
The request returns immediately; a Celery task picks the row up, sleeps a
couple of seconds (where you'd call a real asynchronous task), and flips the status to
SENT. The frontend polls every second so you can watch the state transition.
To run every quality gate across the monorepo:
just backend lint
just backend type
just backend testEach command is also available inside an individual workspace
(e.g. cd backend/django && just lint).
See the talk for the long version. In short:
- One folder per language:
backend/for Python,frontend/for TypeScript,infrastructure/for IaC,generated/for cross-language artefacts. - One uv workspace per deployable unit (
django,ml). The two never share a virtual environment, which keeps heavy ML dependencies out of the Django image. justas the single entrypoint — versions forruff,pyright, andpytestare pinned once inbackend/justfileand inherited by every sub-project.- Local-first development: a single
just setupboots Postgres, Redis and the Celery worker, so a single command brings up everything needed to exercise the async path end to end. - Generated artefacts are derived state, not source —
generated/and the frontend'ssrc/generated/are gitignored. Bothjust setupandjust generate_typesregenerate them, so a fresh clone always rebuilds from the live Django schema rather than a stale committed snapshot.
MIT — feel free to use this as a starting point for your own project.