FastHTML Plus is a layered web application architecture experiment built around literal HTML, small amounts of JavaScript, HTMX fragment swaps, a FastHTML backend, and auth offload via Traefik plus Authelia.
The main detailed write-up is the accompanying blog post: https://weisser-zwerg.dev/posts/software-engineering-fasthtml-plus/
This repository is meant to stand on its own as a runnable prototype. You can work from the inside out:
- Open static HTML directly from
file://. - Add a FastHTML backend that serves the same pages plus HTMX fragments and JSON APIs.
- Put Traefik and Authelia in front for local auth and authorization checks.
- Deploy the full stack into an Incus-based integration environment and then onto a VPS.
- Keep markup literal. The same HTML files can be opened from disk and later served by the backend.
- Keep JavaScript small. TypeScript handles local behavior and progressive enhancement instead of owning the whole UI.
- Treat HTML fragments as first-class responses. HTMX endpoints return HTML, not just JSON.
- Keep JSON APIs explicit. Canonical API routes still exist under
/api/public/*and/api/private/*. - Move login outward. Traefik plus Authelia handle authentication so the backend can focus on authorization and trust boundaries.
- Let each layer be useful on its own. The inner layers stay productive even before the outer layers are running.
.
├── 010-dev/
│ ├── 0010-systems/ # Local Traefik + Authelia stack
│ └── 0100-app/
│ ├── 010-web/ # Static HTML + TypeScript frontend
│ └── 020-fasthtml/ # FastHTML backend
├── 020-ops/ # Rendering and deployment tooling
├── 050-integration-tests/ # HTTPS integration tests against Incus/VPS deployments
└── AUTHENTICATION-AND-AUTHORIZATION.md
nvmwith Nodev22.12.0available for the frontenduvfor Python environments- Docker + Docker Compose
mkcertpluslibnss3-toolsfor trusted local TLStmuxfor the optional systems workflowincusfor the full integration-test layer
cd 010-dev/0100-app/010-web
nvm install
nvm use
npm ci
npm run buildThen open:
file://.../index.htmlfile://.../public.htmlfile://.../private.html
This validates the innermost layer: plain HTML, CSS, and TypeScript, with HTMX attributes present but inert until a backend exists.
cd 010-dev/0100-app/020-fasthtml
uv sync
uv run pytest
make run-localOpen:
In this mode RUN_LOCAL=1 keeps auth friction low so you can work on pages, fragments, and APIs quickly.
In one shell:
cd 010-dev/0010-systems
make certs
make render-config
make check-sync
make upIn a second shell:
cd 010-dev/0100-app/020-fasthtml
make run-systemsThen visit:
- https://app.localhost:8080/public
- https://app.localhost:8080/private
- https://auth.app.localhost:8080
This mode exercises the proxy trust model, ForwardAuth flow, and private-route authorization behavior.
- Incus integration runbook:
050-integration-tests/README.md - Deployment runbook:
020-ops/DEPLOY.md - Auth model and trust boundaries:
AUTHENTICATION-AND-AUTHORIZATION.md
- Static HTML pages that work directly from
file:// - Vite build emits both
dist/main.jsanddist/main.file.js - HTMX is vendored into
dist/vendor/htmx.min.jsfor offline use
- Serves the same frontend files from
../010-web - Returns literal HTML fragments for HTMX interactions
- Exposes JSON APIs for public and private routes
- Supports two backend modes:
- functional mode for fast iteration
- access/authz mode for trusted-proxy validation
- Local HTTPS entrypoints via Traefik
- Login and policy evaluation via Authelia
- Forwarded identity headers plus proxy-secret proof into the backend
- Incus-backed end-to-end environment
- mkcert for local integration TLS, Let's Encrypt for production
- Redis-backed sessions and scaled
fasthtmlcontainers in outer layers
When changing a layer, run that layer's checks:
- Frontend:
npm run build - Backend:
uv syncthenuv run pytest - Systems:
make render-config,make check-sync,make up - Ops:
uv sync,make check-sync - Integration:
make testand optionallymake test-scale
010-dev/0010-systems/contains generated local config outputs. The canonical templates live in020-ops/templates/.010-dev/0100-app/010-web/dist/vendor/htmx.min.jsis generated and intentionally not committed.- The backend defaults to the safer access/authz mode unless you use
make run-local.