Run An Axum Web Server With Live Reload From Tauri

January 2024

This is how I'm embedding Axum server with hot reload in a Tauri app:

Cargo.toml
[package]
name = "tauri_with_axum"
version = "0.0.1"
description = "Tauri With Axum Embedded"
license = ""
repository = ""
edition = "2021"

[build-dependencies]
tauri-build = { version = "1.5", features = [] }

[dependencies]
tauri = { version = "1.5", features = ["shell-open"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
axum = "0.7.4"
tower-http = { version = "0.5.1", features = ["fs"] }
tower-livereload = "0.9.1"
tokio = { version = "1.35.1"}
notify = "6.1.1"
notify-debouncer-mini = "0.4.1"

[features]
# this feature is used for production builds or when ``devPath`` points to the filesystem
# DO NOT REMOVE!!
custom-protocol = ["tauri/custom-protocol"]
src/main.rs
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

use axum::Router;
use notify::Watcher;
use std::path::Path;
use tower_http::services::ServeDir;
use tower_livereload::LiveReloadLayer;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    tauri::Builder::default()
        .setup(|_app| {
            tauri::async_runtime::spawn(run_web_server());
            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");

    Ok(())
}

async fn run_web_server() {
    println!("- Starting web server");
    let livereload = LiveReloadLayer::new();
    let reloader = livereload.reloader();
    let app = Router::new()
        .nest_service(
            "/",
            ServeDir::new(Path::new("/Users/alan/workshop/neopoligen/_site")),
        )
        .layer(livereload);
    if let Ok(mut watcher) = notify::recommended_watcher(move |_| reloader.reload()) {
        if let Ok(_) = watcher.watch(
            Path::new("/Users/alan/workshop/neopoligen/_site"),
            notify::RecursiveMode::Recursive,
        ) {};

        if let Ok(listener) = tokio::net::TcpListener::bind("localhost:3131").await {
            if let Ok(_) = axum::serve(listener, app).await {
                ()
            } else {
                ()
            }
        } else {
            ()
        }
    }
}

Notes

  • The examples for axum and tower/tower_http have a lot of Result<(), Box<dyn std::error::Error>> in them with ? at the end of the await statments. I couldn't figure out how to get that to work so I fell back to if let
  • There's probaby other refinements that could be made, but this is working for me.
end of line

References

This is what I'm using for the Neopoligen control panel

The web server that's embedded in Tauri with this process

The glue between Axum and tower_livereload

The middleware that doest the hot reload of the web pages when they change on disk