Build full-stack stateful web apps using Axum + server-rendered frontend patterns + Neon (PostgreSQL).
Stack: Axum + SQLx (Rust), Askama templates + HTMX + Alpine.js or DataStar + PicoCSS, Neon (PostgreSQL), Docker.
Workflow
Phase 1: Setup
Requirements before scaffolding:
- Rust toolchain with
cargoinstalled from https://rustup.rs. neonctl,jq, andsqlx-cliavailable onPATH.NEON_API_KEYandNEON_PROJECT_IDexported in the environment.
Install tool dependencies once:
npm i -g neonctl
brew install jq
cargo install sqlx-cli --features postgres,native-tls
Scaffold app ({skill_dir} = the directory containing this SKILL.md):
NEON_BRANCH_TTL=2h {skill_dir}/scripts/scaffold <app-name> .
Creates app files and a Neon branch ({app}-dev) with 2h expiration.
Phase 2: Data Modeling
- Define models in
src/models.rs - Write migration SQL in
migrations/001_init.sql - Use
SERIALfor i32,BIGSERIALfor i64
Reference: Models - SQLx patterns, struct definitions, type mapping
Phase 3: Backend Implementation
- Add route handlers in
src/main.rs - All handlers return
Result<T, AppError>- use?operator - NEVER use
.expect()or.unwrap()- causes server crashes - Route params use
{id}syntax (NOT:id)
Reference: Handlers - CRUD patterns, router setup, transactions
Phase 4: Frontend
- Update Askama templates in
templates/ - Delete unused template files (create.html, edit.html if not needed)
- Default to
HTMX + Alpine.jsfor simpler CRUD, forms, filters, and small local UI state - Use
DataStaronly when the Rust app needs a more advanced reactive surface:- SSE-first or realtime UI
- Progressive multi-event backend responses
- One feature needing both local reactive state and server-pushed updates
- If unsure, start with
HTMX + Alpine.js - Do not mix
HTMXandDataStaron the same interaction surface unless there is a concrete reason
Reference: Templates - Askama, HTMX, Alpine patterns Reference: Design - CSS components, layout patterns Reference: DataStar - when to prefer it in this Rust skill, template patterns, SSE patterns, and further reading
Phase 5: Validation
Validate (runs cargo check, clippy, tests, release build):
{skill_dir}/scripts/validate .
Fix all errors before completing.
When Things Fail
- Missing
NEON_API_KEY/NEON_PROJECT_ID: ask the user to export them, then rerun scaffold - it detects the partial run and redoes only the Neon step. - Migration checksum mismatch: never edit an already-applied migration - revert the edit and add a new migration file. Last resort (dev only, destroys data):
{skill_dir}/scripts/validate . --reset. - Clippy failures: fix the code, never silence with
#[allow(...)].
Template Structure
├── Cargo.toml # Dependencies
├── Dockerfile # Multi-stage Rust build
├── src/
│ ├── main.rs # Axum server, routes, templates
│ ├── db.rs # SQLx pool setup
│ └── models.rs # Data structs
├── templates/
│ ├── base.html # Base layout (PicoCSS/HTMX/Alpine CDN; DataStar opt-in)
│ ├── index.html # List view
│ ├── edit.html # Edit form
│ └── create.html # Create form
├── static/
│ └── styles.css # Custom CSS overrides
└── migrations/
└── 001_init.sql # Database schema
Critical Rules
- NEVER
.expect()or.unwrap()in handlers - use?operator - Route params:
{id}not:id- wrong syntax compiles but panics at runtime - All handlers return
Result<T, AppError> - Check
rows_affected()for DELETE/UPDATE to return 404 - Use compile-time SQLx macros (
query!,query_as!) - Ensure the app has enough logs for basic observability
Full list: Pitfalls
Constraints
- Neon (PostgreSQL) required - needs
DATABASE_URL - All routes at root level (/, /new, /{id}/edit)
- Strict clippy lints - must pass validation