Axum and SQLx interoperability guide
Rullst builds on Axum, Tokio, Tower and SQLx and preserves direct access to their APIs. This reduces coupling for HTTP and database code; it does not make every optional framework subsystem free to remove.
Mount an existing Axum router
rullst::Router supports conversion to and from axum::Router:
#![allow(unused)]
fn main() {
use axum::{Router as AxumRouter, routing::get};
use rullst::Router;
async fn existing_handler() -> &'static str {
"existing Axum route"
}
let existing = AxumRouter::new().route("/existing", get(existing_handler));
let rullst_router: Router = existing.into();
let axum_router: AxumRouter = rullst_router.into();
let _ = axum_router;
}
The standard Axum extractor and response types remain available, including
through Rullst’s documented re-exports. Generated projects do not depend on
axum directly: add axum = "0.8" (the version Rullst uses) to run this
example as written, or import the same types through rullst::web::axum.
Keep raw SQLx where it is useful
Application-owned SQL can live beside generated ORM queries. Bind every dynamic value and review structural SQL separately:
#![allow(unused)]
fn main() {
use sqlx::PgPool;
async fn active_names(pool: &PgPool) -> Result<Vec<String>, sqlx::Error> {
sqlx::query_scalar("SELECT name FROM users WHERE active = $1")
.bind(true)
.fetch_all(pool)
.await
}
}
Generate an escape-hatch template
cargo rullst eject
The command writes a reviewable Axum/Tokio entry-point template to
src/ejected_main.rs (--output src/<file>.rs chooses another file below
src/) and leaves src/main.rs unchanged. The template is a placeholder route
behind the configured Rullst security baseline, compiled through the
rullst::web::axum re-export. It does not copy the application’s routes;
ORM models, migrations, authentication policy, Studio/Nexus integration and
other selected subsystems can still require deliberate migration work.
cargo rullst eject --force writes the template over src/main.rs instead and
keeps the original at src/main.rs.rullst-backup (it refuses to run while that
backup exists). Use it only with a committed or backed-up worktree, move the
module declarations and routes from the backup into application_routes(),
then run cargo check.
Practical migration sequence
- Keep domain types and handlers independent of framework globals.
- Convert router boundaries incrementally.
- Replace generated ORM calls with raw SQLx only where that trade-off helps.
- Inventory authentication, middleware order, jobs, cache and admin surfaces.
- Run application-specific integration and authorization tests before removing dependencies.
Interoperability is a maintained design goal. “Zero lock-in” or zero migration cost is not a framework guarantee.