- Rust 100%
| .github/workflows | ||
| crates/axum-app-wrapper-macros | ||
| examples | ||
| src | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| README.md | ||
axum-app-wrapper
A small plugin layer for axum applications, inspired by fastify from the Node/JS ecosystem and the rocket framework.
Plugins can:
- add startup state before the final axum state type exists
- add routes, services, and middleware after state is initialized
- run graceful shutdown work in reverse registration order
This crate is intentionally thin. It does not replace axum's router, extractors, or middleware. Rather, it organizes your server setup and teardown into plugins.
Lifecycle
App::init() runs plugins in this order:
on_initin registration order, passing aTypeMapto build state.S::try_from(TypeMap)to build the final typed app state.on_setupin registration order, passingRouter<S>and&S.InitializedApp::shutdown()runson_shutdownconsecutively in reverse registration order: the last registered plugin shuts down first, and each hook finishes before the next one starts.
Example
See the examples folder for full examples.
App::init() returns an initialized app handle. Use router() to get a ready-to-serve Router<()>, state() to inspect the finalized state, and shutdown() from your graceful shutdown path:
let app = App::<AppState>::new()
.store(config)
.route("/health", axum::routing::get(health))
.register(metrics_plugin)
.init()
.await?;
let router = app.router();
let service_name = &app.state().config.service_name;
axum::serve(listener, router)
.with_graceful_shutdown(async move {
tokio::signal::ctrl_c().await.expect("failed to listen for ctrl-c");
app.shutdown().await.expect("failed to shut down");
})
.await?;
For on_setup closures that access typed state, construct the plugin as
AdHocPlugin::<AppState>::new(). That gives Rust enough context to infer the state parameter:
let plugin = AdHocPlugin::<AppState>::new()
.on_setup(|router, state| {
let config = Arc::clone(&state.config);
Ok(router.layer(axum::Extension(config)))
});
Reusable Plugins
Implement AppPlugin<S> directly when setup should be reusable across apps:
use axum::Router;
use axum_app_wrapper::{AppPlugin, InitFuture, Result, TypeMap};
use futures::FutureExt;
struct ConfigPlugin {
config: Config,
}
impl AppPlugin<AppState> for ConfigPlugin {
fn on_init(&mut self, mut state: TypeMap) -> InitFuture {
let config = self.config.clone();
async move {
state.insert(config);
Ok(state)
}
.boxed()
}
fn on_setup(
&mut self,
router: Router<AppState>,
_state: &AppState,
) -> Result<Router<AppState>> {
Ok(router.route("/health", axum::routing::get(health)))
}
}