Skip to content

With Loco ​

Loco is a Rust framework built on axum. moltn plugs into it without an adapter: moltn pages join Loco's router, Loco controllers stay as they are, and loaders read Loco's AppContext.

A complete example lives in the repository: examples/loco-vue.

Dependencies ​

On top of a moltn project's (moltn-pages, moltn-ssr, veer, ts-rs, rust-embed):

toml
[dependencies]
loco-rs = { version = "1.3", default-features = false }   # add the Loco features you need
async-trait = "0.1"

And the same build.rs as a moltn project (moltn_pages::build::scan_project()).

Wiring moltn into Hooks ​

rust
#[async_trait]
impl Hooks for App {
    // moltn pages become Loco's starting router.
    async fn before_routes(_ctx: &AppContext) -> Result<axum::Router<AppContext>> {
        moltn_pages::router::<AppContext>(moltn_pages::pages!())
            .map_err(|e| loco_rs::Error::string(&e.to_string()))
    }

    // Loco controllers, unchanged.
    fn routes(_ctx: &AppContext) -> AppRoutes {
        AppRoutes::with_default_routes().add_route(controllers::posts::routes())
    }

    // Inertia protocol and frontend assets, around every route.
    async fn after_routes(router: axum::Router, _ctx: &AppContext) -> Result<axum::Router> {
        let inertia = inertia_config()?;   // as in a moltn project's main.rs
        Ok(router
            .nest_service("/build", EmbeddedAssets::new(|p| Assets::get(p).map(|f| f.data)))
            .layer(InertiaLayer::new(inertia)))
    }

    // … app_name, boot, connect_workers, register_tasks as usual
}

Loaders that read Loco's state ​

rust
#[page("posts/[id]")]
async fn post_show(
    Path(id): Path<i32>,
    State(ctx): State<AppContext>,
) -> Result<PostProps, loco_rs::Error> {
    let post = posts::Entity::find_by_id(id)
        .one(&ctx.db)
        .await?
        .ok_or(loco_rs::Error::NotFound)?;
    Ok(PostProps { post: post.into() })
}

With moltn dev ​

moltn dev starts the application with cargo run and passes its port in the PORT variable. If you use Loco's CLI (cargo loco start), write a main that starts the server directly instead, as in the example:

rust
#[tokio::main]
async fn main() -> loco_rs::Result<()> {
    let environment = Environment::from(resolve_from_env());
    let config = App::load_config(&environment).await?;
    let port = std::env::var("PORT").ok().and_then(|p| p.parse().ok()).unwrap_or(config.server.port);
    let binding = config.server.binding.clone();
    let boot = App::boot(StartMode::ServerOnly, &environment, config).await?;
    loco_rs::boot::start::<App>(boot, ServeParams { port, binding }, true).await
}