A Rust web application built with Axum, SQLite, and Askama.
- Rust — install via rustup:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
- Bun — used to build and test the dashboard graph bundle (
ui-graph/) intopublic/js/app.js. Install via bun.sh:curl -fsSL https://bun.sh/install | bash - SQLite3 — pre-installed on macOS. On Linux:
sudo apt install sqlite3 libsqlite3-dev - Caddy — install via Homebrew:
brew install caddy
make setupThis installs:
- sqlx-cli — database migration runner
- cargo-watch — auto-recompile on file changes
Add boardtask.local to your hosts file:
make hostsThis adds 127.0.0.1 boardtask.local to /etc/hosts (requires sudo password).
Create a .env file in the project root (already created with defaults), or copy .env.example for the full template (including optional GitHub App variables and registration notes):
DATABASE_URL=sqlite:boardtask.db
make migrateThis creates the SQLite file and runs all migrations in migrations/.
You need two terminals (three if you edit ui-graph/ often):
Terminal 1 — Start Caddy (HTTPS reverse proxy + static files):
make caddyTerminal 2 — Start Axum (the Rust app):
make devThe server starts at https://boardtask.local and automatically restarts when you edit Rust or Askama sources.
Optional — Terminal 3 — When you change files under ui-graph/src/, rebuild the JS bundle (or use watch mode):
make graph-build
# or: make graph-watchThe bundle at public/js/app.js is generated and not tracked in git; run make graph-build after clone or when you change ui-graph/.
For a single run without watching:
make runmake buildThe optimised binary is at target/release/boardtask. Run it with:
DATABASE_URL=sqlite:boardtask.db ./target/release/boardtaskThe app is built to reduce the risk of database corruption in production:
- Graceful shutdown — On SIGTERM/SIGINT the server stops accepting new requests, finishes in-flight ones, then closes the SQLite connection pool so the DB and WAL are left in a consistent state. Always stop the process with
kill <pid>or Ctrl+C (notkill -9) when possible. - Durability —
PRAGMA synchronous=NORMALwith WAL gives a good balance of safety and performance; use FULL only if you need strict durability (e.g. finance). - Single writer — The app enforces one process per database file. If another instance (or the seed binary) is already using the same file, the new process exits at startup with a clear error. Do not point multiple processes at the same
boardtask.db. - Local disk — Store the database on local SSD. Avoid NFS, network filesystems, or shared volumes that can cause I/O errors or lock issues.
- Backups — Take regular backups (e.g.
sqlite3 boardtask.db ".backup backup.db"or copy the file while the app is stopped). If the DB is ever corrupted, restore from backup and run migrations if needed; do not delete WAL/shm on a live process.
boardtask/
├── Cargo.toml # Dependencies and project metadata
├── askama.toml # Askama template configuration
├── Caddyfile # Caddy reverse proxy config
├── Makefile # Dev workflow commands
├── .env # Environment variables (not committed)
├── migrations/ # SQL migration files (run in order)
│ └── 20260207000000_initial.sql
├── ui-graph/ # Bun package: builds dashboard JS → ../public/js/app.js
├── public/ # Static assets (served by Caddy or Axum /js)
│ ├── css/
│ │ └── app.css
│ └── js/
│ └── app.js # Built from ui-graph (run `make graph-build`)
└── src/
├── main.rs # Entry point — boots the server
├── app/ # Router + shared AppState
│ ├── mod.rs
│ ├── db/ # DB models, queries
│ ├── domain/ # Domain types
│ ├── error.rs # Error handling
│ └── features/ # Feature slices
│ └── auth/ # Authentication
│ ├── mod.rs
│ ├── service.rs # Auth service layer
│ ├── signup.rs # Signup form + handlers + routes
│ ├── signup.html
│ ├── login.rs # Login form + handlers + routes
│ └── login.html
└── site/ # Marketing website
├── mod.rs
└── home.rs # Root route (redirects to /app or /login)
This project uses vertical slice architecture with colocated Askama templates. For detailed conventions (how to add features, auth layering, template paths), see .cursor/rules/.
| Command | What it does |
|---|---|
make setup |
Install sqlx-cli and cargo-watch |
make hosts |
Add boardtask.local to /etc/hosts |
make dev |
Run Axum with auto-reload |
make caddy |
Run Caddy reverse proxy (separate terminal) |
make run |
Run Axum |
make build |
Build release binary |
make test |
Rust tests only (RUSTFLAGS=-D warnings) |
make test-all |
make test plus make graph-test (Rust + ui-graph unit tests) |
make graph-install |
bun install in ui-graph/ (after clone) |
make graph-build |
Build public/js/app.js from ui-graph/src |
make graph-test |
Run ui-graph unit tests (Bun) |
make graph-watch |
Rebuild app.js on save (use with make dev) |
make migrate |
Create DB + run migrations |
make db-reset-wal |
Clear WAL/shm only (fixes I/O 522; stop app first) |
make db-reset |
Remove DB file so next run recreates it (data lost; stop app first) |
make clean |
Remove build artifacts and local database |
make test stays Rust-only so backend work does not require Bun. Use make test-all before a full-stack change, and run make graph-build when you touch ui-graph/ so public/js/app.js exists locally (CI builds the bundle to ensure it compiles).
- Browser requests
https://boardtask.local - Caddy checks if the request matches a file in
public/(CSS, JS, images) - If yes → Caddy serves it directly
- If no → Caddy reverse proxies to Axum on
localhost:3000 - Axum handles the request, renders Askama templates, queries SQLite
Caddy automatically provisions a local root CA and TLS certificate for boardtask.local on first run. It may prompt for your system password to trust the CA. After that, https://boardtask.local works without any certificate warnings.
CSS and the built public/js/app.js are served from public/ (Caddy in the recommended setup; Axum also serves /js/* in development). The graph UI source lives in ui-graph/; see make graph-build above.
"Failed to bind to port 3000" — Another process is using port 3000. Kill it or change the port in src/main.rs.
"DATABASE_URL must be set" — Make sure .env exists and contains DATABASE_URL=sqlite:boardtask.db.
"boardtask.local not found" — Run make hosts to add it to /etc/hosts.
Caddy SSL errors — On first run, Caddy may need your password to install its root CA. Enter it when prompted.
SQLite "disk I/O error" (522) or "database disk image is malformed" (11) — Stop the app, then run make db-reset-wal. If the DB is corrupted, run make db-reset (this deletes the database; next start will recreate it via migrations). See "Production: avoiding SQLite corruption" above for prevention.