Architecture Overview¶
rpodder is built as a Rust workspace with four crates, designed around a clean separation between domain logic and infrastructure.
Design principles¶
-
Repo trait pattern — domain logic depends on abstract traits, not concrete database implementations. This is how we support both PostgreSQL and SQLite with the same business logic.
-
No compile-time database macros — we use
sqlx::query_as::<_, Row>()withFromRowat runtime, notsqlx::query!(). This means you don't need aDATABASE_URLat compile time. -
Single binary — the web UI (Svelte SPA) is embedded in the Rust binary via
rust-embed. One file to deploy. -
Feature flags —
cargo build --no-default-featuresproduces an API-only binary without the web UI. Theweb-uifeature controls embedding.
Request flow¶
Client (podcast app / browser)
│
▼
axum Router
│
├─ Public routes (no auth)
│ ├─ /health, /metrics
│ ├─ /search.json, /toplist, /api/2/trending
│ ├─ /api/2/register, /api/2/password-reset
│ └─ /auth/sso/*
│
├─ Authenticated routes (Basic Auth or session cookie)
│ ├─ /api/2/subscriptions/*, /api/2/episodes/*
│ ├─ /api/2/devices/*, /api/2/me
│ └─ /subscriptions/*
│
├─ Admin routes (auth + is_admin check)
│ └─ /api/admin/*
│
└─ Fallback (web UI SPA)
└─ Serves index.html for client-side routing
Background tasks¶
- Feed updater — spawned via
tokio::spawnat startup. Runs every 30 minutes. Respects adaptive intervals and retries.
State management¶
struct AppState {
db: Arc<Db>, // Database connection (PG or SQLite)
config: Arc<AppConfig>, // All configuration
}
AppState is passed to all handlers via axum's State extractor. It's Clone and Send + Sync.
Authentication middleware¶
Two middleware layers, applied in order:
require_auth_layer— extracts session cookie or HTTP Basic Auth, resolves user, insertsAuthUserinto request extensionsrequire_admin_layer— checksAuthUser.is_admin, returns 403 if not
The admin layer is applied on top of the auth layer. In axum, route layers run inner-first, so:
Database abstraction¶
The Db enum dispatches to the correct pool. A with_repo! macro simplifies calling trait methods: