let d = import "adr-defaults.ncl" in d.make_adr { id = "adr-003", title = "Axum as the Sole HTTP Framework for vapora-backend", status = 'Accepted, date = "2024-11-01", context = "vapora-backend exposes 40+ REST endpoints plus WebSocket connections for real-time updates. The framework choice determines the async model, middleware composition, and extractor ergonomics for the entire API surface. As of 2026-03-27, axum 0.8.8 is in use (the markdown ADR references 0.8.6, which is stale). The Tower ecosystem is the chosen middleware stack.", decision = "Axum is the only HTTP framework allowed in vapora-backend. No Actix-Web, Rocket, or Warp. All HTTP handlers use Axum extractors and Router composition. Middleware uses the Tower layer model. WebSocket upgrades use axum::extract::ws.", rationale = [ { claim = "Axum is Tokio-native with zero abstraction over the async runtime", detail = "vapora runs a Tokio multi-threaded runtime. Axum's Handler trait is directly implemented over Tokio futures — no bridging layer, no actor model overhead. This eliminates the async runtime impedance mismatch that Actix-Web introduces.", }, { claim = "Tower middleware composes predictably with all Axum routes", detail = "CorsLayer, TraceLayer, CompressionLayer, and authentication middleware are composed via ServiceBuilder, not framework-specific macros. This means middleware ordering is explicit, testable, and portable to other Tower-based services.", }, { claim = "Type-safe extractors eliminate runtime deserialization panics", detail = "Json, Path, State extractors fail at compile time if the handler signature doesn't match — not at runtime. This catches API contract violations before deployment.", }, ], consequences = { positive = [ "All API surface is tested via axum-test's TestClient without a real TCP socket", "Tower middleware applies uniformly to all routes, including WebSocket upgrade paths", "Adding new endpoints requires only adding a handler fn and a route entry — no boilerplate registration", "IntoResponse impl on VaporaError provides consistent error serialization across all handlers", ], negative = [ "Axum's 0.8.x API introduced breaking changes from 0.7 (extractor signatures, Router typing) — upgrades require wholesale migration", "Axum lacks built-in request body size limiting — must be added via RequestBodyLimitLayer", ], }, alternatives_considered = [ { option = "Actix-Web", why_rejected = "Actor model adds coordination overhead not needed for stateless API handlers. Different async patterns from Tokio primitives make integration with NATS JetStream and SurrealDB clients awkward.", }, { option = "Rocket", why_rejected = "Synchronous-first design. async support was added as an afterthought, leading to executor boundary issues in Tokio-native code.", }, ], constraints = [ { id = "axum-only-http-framework", claim = "vapora-backend must not import actix-web, rocket, or warp", scope = "vapora-backend", severity = 'Hard, check = { tag = 'Cargo, crate = "vapora-backend", forbidden_deps = ["actix-web", "rocket", "warp"] }, rationale = "Multiple HTTP frameworks in one binary create conflicting async executor registrations, duplicated middleware chains, and inconsistent error serialization.", }, { id = "all-handlers-via-axum-router", claim = "All HTTP endpoints must be registered via axum::Router — no raw hyper service registration", scope = "vapora-backend/src/main.rs, vapora-backend/src/api/", severity = 'Hard, check = { tag = 'Grep, pattern = "axum::Router", paths = ["crates/vapora-backend/src/main.rs"], must_be_empty = false }, rationale = "Bypassing the Axum router skips middleware layers (tracing, CORS, auth) applied at the Router level.", }, ], related_adrs = ["adr-002"], ontology_check = { decision_string = "axum 0.8.x is the sole HTTP framework in vapora-backend; Tower middleware stack; no actix-web/rocket/warp", invariants_at_risk = [], verdict = 'Safe, }, }