← All posts
RUSTPublished · April 8, 2026

Why housing_rs picked Axum over Actix

tower ecosystem vs actor model, sqlx compile-time checks vs Diesel ORM — practical Rust framework selection notes for a mid-size project.

3 min read

The first technical call on housing_rs was "which Rust web framework". I PoC'd Actix-web, Axum, and Rocket. Axum won. Here are the trade-offs.

What each smells like

Actix-web: actor-model based, perennial benchmark winner. But:

  • Actor model is a learning curve for new team members
  • Docs lag behind axum
  • Past unsafe controversy hurt community confidence

Rocket: elegant API, macro-driven. But:

  • async / await landed late (stable in 0.5+)
  • middleware ecosystem is weaker than tower's
  • heavy macros are unfriendly to IDE tooling

Axum: from the tokio family, built on tower. Pros:

  • reusable tower middleware
  • elegant type-driven extractor design
  • dense docs, fast-growing community

The deciding factor was the tower ecosystem.

tower: Rust's web middleware standard

tower abstracts a Service trait — Request -> Future<Response> — that all middleware speaks. housing_rs uses:

src/main.rs
use axum::Router;
use tower_http::{
    compression::CompressionLayer,
    cors::CorsLayer,
    trace::TraceLayer,
    timeout::TimeoutLayer,
};
use std::time::Duration;
 
let app = Router::new()
    .route("/api/housing/estimate", post(estimate_handler))
    .with_state(state)
    // Order: trace → timeout → compression → cors
    // Trace first so it covers the entire request lifetime
    .layer(TraceLayer::new_for_http())
    .layer(TimeoutLayer::new(Duration::from_secs(10)))
    .layer(CompressionLayer::new())
    .layer(cors);

In Actix you'd hunt for an actix-equivalent crate per piece (some exist, some you write yourself), and APIs differ.

sqlx vs Diesel: why sqlx

ORM choices:

  • Diesel: mature, type-safe, but macro-heavy, async is third-party
  • SeaORM: active-record style, async native, but heavier abstraction
  • sqlx: raw SQL + compile-time query check, async native

Picked sqlx because compile-time check is a maintenance superpower for mid-size projects:

src/repo/housing.rs
use sqlx::PgPool;
 
pub async fn nearby_pois(
    pool: &PgPool,
    lat: f64,
    lng: f64,
    radius_m: f64,
) -> sqlx::Result<Vec<Poi>> {
    // sqlx::query_as! validates the SQL against the live DB at cargo check!
    // Forget to update a model after a schema change → compile fails.
    sqlx::query_as!(
        Poi,
        r#"
        SELECT category, name, ST_Distance(geom, ST_GeogFromText($1)) as distance_m, weight
        FROM poi_points
        WHERE ST_DWithin(geom, ST_GeogFromText($1), $2)
        ORDER BY distance_m
        "#,
        format!("POINT({lng} {lat})"),
        radius_m,
    )
    .fetch_all(pool)
    .await
}

Diesel's table! macro requires you to hand-write schema; after migrations you regen with diesel migration, which is easy to miss. With sqlx, SQL is the source of truth. Schema changed? Run cargo sqlx prepare to refresh the cache.

Note: sqlx macros need a reachable DATABASE_URL to compile. In CI, run cargo sqlx prepare first to generate the .sqlx/ cache, then set SQLX_OFFLINE=true.

PostGIS coordinate woes in Rust

sqlx doesn't natively support geography(Point, 4326). housing_rs workaround:

src/repo/poi.rs
// Cast geom to text in SQL, receive as String in Rust
sqlx::query_as!(
    PoiRaw,
    r#"
    SELECT
        id,
        category,
        name,
        ST_AsText(geom::geometry) as "geom_wkt!",
        weight
    FROM poi_points
    "#
)
.fetch_all(pool)
.await

Or pull in sqlx-postgres-types for proper geography support. housing's traffic is small enough that String parsing overhead is negligible.

A year later

After a year on Axum + sqlx in housing_rs, three reflections:

  1. tower middleware saved real time: log / cors / trace / rate limit — never had to write any
  2. sqlx compile-time checks catch bugs precisely: a schema refactor has never blown up SQL in prod
  3. Axum docs keep maturing: post-0.7 release, Discord questions usually get answered same-day

Same call again, given the chance.

When it's the wrong choice

  • Ultra-high-throughput / low-latency trading: Actix-web still wins at the nanosecond level
  • Solo prototype: what Express / Fastify ships in 30 minutes takes Rust 2 days
  • Team without Rust experience: learning cost is real — assess ROI first

For something like housing_rs (mid-size, long-term, multi-collaborator backend), Axum is the sweet spot.

Found it useful?

Share with someone who might benefit.