# Rust + Axum + htmx + Askama (Server-Rendered Web App) — AI Agent Guidelines & Architecture Rules

> Server-rendered Rust web app rules: Axum 0.8, compile-time Askama templates, htmx partials, sqlx 0.9 + PostgreSQL, and a cargo check loop for AI agents.
> Technologies: Rust, Axum, HTMX, Askama, PostgreSQL, sqlx, Tokio, Tailwind CSS

---

## AGENTS.md
```markdown
# Project Architecture & Guidelines (Rust + Axum + htmx + Askama)

## 1. System Architecture
- **Language**: Rust 2024 edition, stable toolchain pinned in `rust-toolchain.toml`.
- **Runtime & HTTP**: Tokio + Axum 0.8, middleware from `tower-http` (trace, compression, timeouts, static files).
- **Rendering**: server-rendered HTML with Askama templates (Jinja-like syntax, compiled and type-checked at build time). No SPA, no client-side router.
- **Interactivity**: htmx attributes (`hx-get`, `hx-post`, `hx-target`, `hx-swap`) that request HTML fragments from the same handlers. Small client behaviour in plain JS or Alpine.js only when htmx cannot express it.
- **Database**: PostgreSQL through `sqlx` 0.9 (`runtime-tokio`, `tls-rustls`, `postgres`, `macros`, `migrate`, `uuid`, `chrono`).
- **Styling**: Tailwind CSS standalone CLI building `assets/app.css`; no Node toolchain required.

## 2. Project Layout
- `src/main.rs`: config, tracing, pool, migrations, router, graceful shutdown.
- `src/routes/<feature>.rs`: one module per feature; each exports `fn router() -> Router<AppState>`.
- `src/views/<feature>.rs`: Askama template structs for that feature (`#[derive(Template)]`).
- `templates/`: `base.html` layout, `<feature>/index.html`, and partials in `<feature>/_row.html`.
- `src/db/<feature>.rs`: queries only (`sqlx::query_as!`), no HTTP types.
- `src/error.rs`: `AppError` enum implementing `IntoResponse`.
- `migrations/`: timestamped `.sql` files created with `sqlx migrate add`.

## 3. Handlers, Templates and htmx
- Handlers return `Result<impl IntoResponse, AppError>`. Render with `Html(template.render()?)`; map `askama::Error` into `AppError` so a template error is a 500 with a log line, never a panic.
- Detect htmx requests with the `HX-Request` header. Return the full page for normal navigation and only the fragment for htmx requests, from the same handler.
- Render fragments with Askama's `block` attribute (`#[template(path = "todos/index.html", block = "list")]`) instead of duplicating markup in a second file.
- Forms post with `hx-post` and return the updated fragment. On validation errors, return `422` with the form fragment and inline messages; configure htmx to swap 422 responses.
- Use `HX-Redirect` or `HX-Location` response headers for redirects after htmx requests; a plain `303 See Other` for non-htmx posts.
- Never build HTML with `format!`. Askama escapes by default; use `|safe` only on content you sanitised yourself.

## 4. State, Auth and Security
- `AppState { db: PgPool, config: Arc<Config> }` passed with `.with_state()`; no globals or `lazy_static` mutable state.
- Sessions: `tower-sessions` with a Postgres store; passwords hashed with `argon2`.
- CSRF: require a token on every state-changing request; send it as a header via `hx-headers` on `<body>`.
- Set `Content-Security-Policy`, `X-Content-Type-Options` and `Referrer-Policy` in one tower layer.

## 5. Database
- `sqlx::query!` / `query_as!` macros only, so SQL is checked against the schema at compile time.
- Commit the `.sqlx/` offline cache (`cargo sqlx prepare`) and build CI with `SQLX_OFFLINE=true`.
- Pool: `PgPoolOptions::new().max_connections(10).acquire_timeout(Duration::from_secs(3))`.
- Wrap multi-statement writes in `pool.begin()` transactions.

## 6. Agent Loop (run after every change)
1. `cargo check` until clean; read the full compiler message before editing.
2. `cargo clippy --all-targets -- -D warnings`.
3. `cargo test`.
4. For template changes, load the page and the htmx fragment once (`curl -H 'HX-Request: true'`).
- Do not silence the borrow checker with `.clone()` everywhere, `unsafe`, or `Rc<RefCell<_>>`; restructure ownership instead.
- No `unwrap()`, `expect()` or `panic!()` in request paths. `?` into `AppError`.
- Do not add a crate without saying why in the PR; prefer the ones already in `Cargo.toml`.

## 7. Testing
- Handler tests call the router with `tower::ServiceExt::oneshot` and assert status plus a fragment of the HTML.
- `#[sqlx::test]` for database tests (it creates and migrates a throwaway database per test).
- Test both the full-page and the `HX-Request` response for every htmx endpoint.

## 8. Git and CI
- Conventional Commits scoped to the feature module (`feat(todos): inline edit row`).
- CI: `cargo fmt --check`, `cargo clippy -- -D warnings`, `cargo test`, `SQLX_OFFLINE=true cargo build --release`.
- Ship as a single binary in a distroless or `debian:slim` image with `templates/` compiled in and `assets/` embedded or served by `tower-http::services::ServeDir`.
```

---

## CLAUDE.md
```markdown
# CLAUDE.md — Rust + Axum + htmx + Askama

@AGENTS.md

## Commands
- `cargo watch -x run` (or `bacon run`) - dev server with rebuild on save
- `cargo check` - fastest feedback; run after every edit
- `cargo clippy --all-targets -- -D warnings` - lint, warnings are errors
- `cargo test` - unit, handler and `#[sqlx::test]` database tests
- `sqlx migrate add <name>` / `sqlx migrate run` - migrations
- `cargo sqlx prepare` - refresh the `.sqlx/` offline cache after query changes
- `tailwindcss -i assets/input.css -o assets/app.css --watch` - CSS

## Non-negotiables
- HTML comes from Askama templates, never `format!`.
- Every htmx endpoint returns a fragment for `HX-Request` and a full page otherwise.
- No `unwrap()` in handlers; errors go through `AppError`.
- Commit `.sqlx/` with any query change.
```

---

## .cursor/rules/stack.mdc
```markdown
---
description: Rust + Axum + htmx + Askama server-rendered web app rules
globs: ["**/*.rs", "templates/**/*.html", "migrations/**/*.sql"]
alwaysApply: false
---

# Rust + Axum + htmx + Askama

- Axum 0.8 on Tokio; one `router()` per feature module in `src/routes/`.
- HTML only from Askama templates (`#[derive(Template)]`); fragments via `block = "..."`, not duplicate files.
- Same handler serves full page and htmx fragment; branch on the `HX-Request` header.
- Return `Result<impl IntoResponse, AppError>`; `Html(template.render()?)`.
- `sqlx::query!`/`query_as!` only; commit `.sqlx/`; build CI with `SQLX_OFFLINE=true`.
- No `unwrap()`/`panic!()` in request paths; no `format!` HTML; `|safe` only on sanitised content.
- After each change: `cargo check`, `cargo clippy -- -D warnings`, `cargo test`.
```

---

## Architecture Overview & Best Practices
## Architecture Overview

A **server-rendered Rust web app**: Axum handlers render Askama templates, htmx swaps HTML fragments for interactivity, and sqlx talks to PostgreSQL with queries checked at compile time. It is the Rust take on Rails + Hotwire or Laravel + Livewire, and of the four Rust web stacks on STACK IT FAST it has the fewest moving parts.

### Why it suits AI coding agents

- **Two compilers check the agent's work.** rustc checks the handlers, and Askama compiles templates into Rust, so a typo in `{{ user.emial }}` fails `cargo check` instead of a page view.
- **One rendering model.** There is no client state, hydration or API contract to keep in sync, so agents change a handler and a template and are done.
- **Matches what open source uses.** Axum is the web framework in 13 of the 18 open-source Rust apps in the directory that use one. [Kanidm](/project/kanidm) renders its whole self-service UI with Axum, Askama (`askama_web`) and `axum-htmx`, and [authentik](/project/authentik) pairs Axum with Askama in its Rust server.

### When to pick something else

Choose [Leptos full-stack](/rules/rust-leptos-fullstack) when the UI needs rich client-side state, [Loco](/rules/rust-loco-saas) when you want generators, mailers and workers out of the box, and [Rust + Axum + PostgreSQL](/rules/rust-axum-postgres) for a JSON API with a separate frontend. Background reading: [Building web apps in Rust in 2026](/insights/rust-web-apps-ai-agents-2026).