docs-site/content/docs/getting-started/axum-users.md
+++ title = "Axum vs Loco" description = "Shows how to move from Axum to Loco" date = 2023-12-01T19:30:00+00:00 updated = 2023-12-01T19:30:00+00:00 draft = false weight = 5 sort_by = "weight" template = "docs/page.html"
[extra] toc = true top = false flair =[] +++
<div class="infobox"> <b>NOTE: Loco is based on Axum, it is "Axum with batteries included"</b>, and is very easy to move your Axum code to Loco. </div>We will study realworld-axum-sqlx which is an Axum based app, that attempts to describe a real world project, using API, real database, and real world scenarios as well as real world operability requirements such as configuration and logging.
Picking realworld-axum-sqlx apart piece by piece we will show that by moving it from Axum to Loco, most of the code is already written for you, you get better best practices, better dev experience, integrated testing, code generation, and build apps faster.
You can use this breakdown to understand how to move your own Axum based app to Loco as well. For any questions, reach out in discussions or join our discord by clicking the green invite button
mainWhen working with Axum, you have to have your own main function which sets up every component of your app, gets your routers, adds middleware, sets context, and finally, eventually, goes and sets up a listen on a socket.
This is a lot of manual, error prone work.
In Loco you:
cargo loco start, no need for a main file at allyour_app which you runconfig/server:
middlewares:
limit_payload:
body_limit: 5mb
# .. more middleware below ..
config/server:
port: 5150
The realworld axum codebase uses dotenv, which needs explicit loading in main:
dotenv::dotenv().ok();
And a .env file to be available, maintained and loaded:
DATABASE_URL=postgresql://postgres:{password}@localhost/realworld_axum_sqlx
HMAC_KEY={random-string}
RUST_LOG=realworld_axum_sqlx=debug,tower_http=debug
This is a sample file which you get with the project, which you have to manually copy and edit, which is more often than not very error prone.
Loco: use your standard config/[stage].yaml configuration, and load specific values from environment using get_env
# config/development.yaml
# Web server configuration
server:
# Port on which the server will listen. the server binding is 0.0.0.0:{PORT}
port: {}
This configuration is strongly typed, contains most-used values like database URL, logger levels and filtering and more. No need to guess or reinvent the wheel.
Using Axum only, you typically have to set up your connection, pool, and set it up to be available for your routes, here's the code which you put in your main.rs typically:
let db = PgPoolOptions::new()
.max_connections(50)
.connect(&config.database_url)
.await
.context("could not connect to database_url")?;
Then you have to hand-wire this connection
.layer(AddExtensionLayer::new(ApiContext {
config: Arc::new(config),
db,
}))
In Loco you just set your values for the pool in your config/ folder. We already pick up best effort default values so you don't have to do it, but if you want to, this is how it looks like:
database:
enable_logging: false
connect_timeout: 500
idle_timeout: 500
min_connections: 1
max_connections: 1
All around your app, you'll have to manually code a logging story. Which do you pick? tracing or slog? Is it logging or tracing? What is better?
Here's what exists in the real-world-axum project. In serving:
// Enables logging. Use `RUST_LOG=tower_http=debug`
.layer(TraceLayer::new_for_http()),
And in main:
// Initialize the logger.
env_logger::init();
And ad-hoc logging in various points:
log::error!("SQLx error: {:?}", e);
In Loco, we've already answered these hard questions and provide multi-tier logging and tracing:
And we picked tracing so that any and every Rust library can "stream" into your log uniformly.
But we also made sure to create smart filters so you don't get bombarded with libraries you don't know, by default.
You can configure your logger in config/
logger:
enable: true
pretty_backtrace: true
level: debug
format: compact
Moving routes from Axum to Loco is actually drop-in. Loco uses the native Axum router.
If you want to have facilities like route listing and information, you can use the native Loco router, which translates to an Axum router, or you can use your own Axum router.
If you want 1:1 complete copy-paste experience, just copy your Axum routes, and plug your router in Loco's after_routes() hook:
async fn after_routes(router: AxumRouter, _ctx: &AppContext) -> Result<AxumRouter> {
// use AxumRouter to mount your routes and return an AxumRouter
}
If you want Loco to understand the metadata information about your routes (which can come in handy later), write your routes() function in each of your controllers in this way:
// this is what people usually do using Axum only
pub fn router() -> Router {
Router::new()
.route("/auth/register", post(create_user))
.route("/auth/login", post(login_user))
}
// this is how it looks like using Loco (notice we use `Routes` and `add`)
pub fn routes() -> Routes {
Routes::new()
.add("/auth/register", post(create_user))
.add("/auth/login", post(login_user))
}
Routes you get it for free, while all of the different signatures remain compatible with Axum router.