Skip to content
STACK IT FAST

Rust + Axum + htmx + Askama (Server-Rendered Web App)

Curated rule SaaS / Web App · Internal Admin Tool · Content & Directory Updated Oct 2026 Which file does my tool read?
rust-axum-htmx-askama.md

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.

Formats
4 files
AGENTS.md
57 lines
CLAUDE.md
18 lines
Languages
Rust
Updated
Oct 2026
Used by
4 projects
Install

Writes .claude/skills/rust-axum-htmx-askama/SKILL.md

$ curl -s --create-dirs -o .claude/skills/rust-axum-htmx-askama/SKILL.md https://stackitfast.com/rules/rust-axum-htmx-askama/SKILL.md

Rule files

AGENTS.md· 57 lines · 4.4 KB
1# Project Architecture & Guidelines (Rust + Axum + htmx + Askama)
2
3## 1. System Architecture
4- **Language**: Rust 2024 edition, stable toolchain pinned in `rust-toolchain.toml`.
5- **Runtime & HTTP**: Tokio + Axum 0.8, middleware from `tower-http` (trace, compression, timeouts, static files).
6- **Rendering**: server-rendered HTML with Askama templates (Jinja-like syntax, compiled and type-checked at build time). No SPA, no client-side router.
7- **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.
8- **Database**: PostgreSQL through `sqlx` 0.9 (`runtime-tokio`, `tls-rustls`, `postgres`, `macros`, `migrate`, `uuid`, `chrono`).
9- **Styling**: Tailwind CSS standalone CLI building `assets/app.css`; no Node toolchain required.
10
11## 2. Project Layout
12- `src/main.rs`: config, tracing, pool, migrations, router, graceful shutdown.
13- `src/routes/<feature>.rs`: one module per feature; each exports `fn router() -> Router<AppState>`.
14- `src/views/<feature>.rs`: Askama template structs for that feature (`#[derive(Template)]`).
15- `templates/`: `base.html` layout, `<feature>/index.html`, and partials in `<feature>/_row.html`.
16- `src/db/<feature>.rs`: queries only (`sqlx::query_as!`), no HTTP types.
17- `src/error.rs`: `AppError` enum implementing `IntoResponse`.
18- `migrations/`: timestamped `.sql` files created with `sqlx migrate add`.
19
20## 3. Handlers, Templates and htmx
21- 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.
22- 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.
23- Render fragments with Askama's `block` attribute (`#[template(path = "todos/index.html", block = "list")]`) instead of duplicating markup in a second file.
24- 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.
25- Use `HX-Redirect` or `HX-Location` response headers for redirects after htmx requests; a plain `303 See Other` for non-htmx posts.
26- Never build HTML with `format!`. Askama escapes by default; use `|safe` only on content you sanitised yourself.
27
28## 4. State, Auth and Security
29- `AppState { db: PgPool, config: Arc<Config> }` passed with `.with_state()`; no globals or `lazy_static` mutable state.
30- Sessions: `tower-sessions` with a Postgres store; passwords hashed with `argon2`.
31- CSRF: require a token on every state-changing request; send it as a header via `hx-headers` on `<body>`.
32- Set `Content-Security-Policy`, `X-Content-Type-Options` and `Referrer-Policy` in one tower layer.
33
34## 5. Database
35- `sqlx::query!` / `query_as!` macros only, so SQL is checked against the schema at compile time.
36- Commit the `.sqlx/` offline cache (`cargo sqlx prepare`) and build CI with `SQLX_OFFLINE=true`.
37- Pool: `PgPoolOptions::new().max_connections(10).acquire_timeout(Duration::from_secs(3))`.
38- Wrap multi-statement writes in `pool.begin()` transactions.
39
40## 6. Agent Loop (run after every change)
411. `cargo check` until clean; read the full compiler message before editing.
422. `cargo clippy --all-targets -- -D warnings`.
433. `cargo test`.
444. For template changes, load the page and the htmx fragment once (`curl -H 'HX-Request: true'`).
45- Do not silence the borrow checker with `.clone()` everywhere, `unsafe`, or `Rc<RefCell<_>>`; restructure ownership instead.
46- No `unwrap()`, `expect()` or `panic!()` in request paths. `?` into `AppError`.
47- Do not add a crate without saying why in the PR; prefer the ones already in `Cargo.toml`.
48
49## 7. Testing
50- Handler tests call the router with `tower::ServiceExt::oneshot` and assert status plus a fragment of the HTML.
51- `#[sqlx::test]` for database tests (it creates and migrates a throwaway database per test).
52- Test both the full-page and the `HX-Request` response for every htmx endpoint.
53
54## 8. Git and CI
55- Conventional Commits scoped to the feature module (`feat(todos): inline edit row`).
56- CI: `cargo fmt --check`, `cargo clippy -- -D warnings`, `cargo test`, `SQLX_OFFLINE=true cargo build --release`.
57- 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`.

Works with Claude Code · Cursor · Windsurf · AGY

Architecture notes

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 renders its whole self-service UI with Axum, Askama (askama_web) and axum-htmx, and authentik pairs Axum with Askama in its Rust server.

When to pick something else

Choose Leptos full-stack when the UI needs rich client-side state, Loco when you want generators, mailers and workers out of the box, and Rust + Axum + PostgreSQL for a JSON API with a separate frontend. Background reading: Building web apps in Rust in 2026.

Frequently asked questions

Is Axum + htmx a good Rust alternative to Rails + Hotwire?

It is the closest match in spirit: the server renders HTML, htmx swaps fragments in place like Turbo Frames, and there is no client-side app to keep in sync. What you give up is Rails generators and conventions; this rule supplies the conventions (feature modules, template partials, one handler per page and fragment) so an AI agent applies them consistently.

Why Askama instead of Tera or MiniJinja?

Askama compiles templates into Rust at build time, so a missing field or a wrong type in a template is a compile error that cargo check reports, which is exactly the feedback loop coding agents need. Tera and MiniJinja load templates at runtime and only fail when the page is rendered.

How do I render only part of a page for htmx?

Use Askama block fragments: define the list inside a {% block list %} in the page template and add a second struct with #[template(path = "todos/index.html", block = "list")]. The handler checks the HX-Request header and renders the block for htmx requests and the full page otherwise.

Does this stack need Node.js?

No. Tailwind CSS ships a standalone CLI binary and htmx is a single script file you vendor into assets/. The whole app builds with cargo and deploys as one binary plus static assets.

Used in production

Explore all stacks