Kerniq Server

Axum and Tower interoperability

Kerniq is not a closed ecosystem. Every Kerniq app is an Axum router, every Kerniq handler is an Axum handler, and every Kerniq middleware is Tower middleware. Kerniq re-exports axum, tower and http at the versions it uses (kerniq_server::axum, ...), so the types always match.

#What converts, mounts and composes

You have You want Use
App<S> axum::Router app.into_axum_router()
Kerniq Router<S> axum::Router<S> router.into_axum() / .into()
axum::Router<S> inside an App<S> / Router<S> .mount_axum(path, router) ("/" merges)
axum::Router<S> Kerniq Router<S> Router::from(axum_router)
Axum handler Kerniq route pass it to .get(..) etc.
Axum extractor (axum::Json, axum::extract::Path, axum-extra types, ...) in a Kerniq handler use it as an argument
axum::routing::MethodRouter Kerniq route .route(path, method_router)
Tower Layer app- or router-wide middleware .layer(layer)
Axum from_fn middleware middleware .layer(axum::middleware::from_fn(f))
Kerniq middleware in a plain Axum router or ServiceBuilder .into_layer() on built-ins

into_axum_router() includes routes, state, fallbacks and all middleware. Lifecycle features (graceful shutdown, background tasks, connection limits) belong to App::run and are not part of a router.

#Mounting an existing Axum router

rust
use kerniq_server::prelude::*;
use kerniq_server::axum;

#[derive(Clone)]
struct AdminState { name: &'static str }

let existing_router = axum::Router::new()
    .route("/whoami", axum::routing::get(|axum::extract::State(s): axum::extract::State<AdminState>| async move { s.name }))
    .with_state(AdminState { name: "admin" });

let app = App::new()
    .get("/", || async { "kerniq" })
    .mount_axum("/admin", existing_router);

The mounted router keeps its own state, layers and handlers.

#Running Kerniq inside an Axum application

rust
use kerniq_server::prelude::*;
use kerniq_server::axum;

#[tokio::main]
async fn main() -> Result<()> {
    let kerniq = App::new().get("/hello", || async { "from kerniq" });

    let app = axum::Router::new()
        .route("/status", axum::routing::get(|| async { "ok" }))
        .nest("/kerniq", kerniq.into_axum_router());

    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await?;
    axum::serve(listener, app).await?;
    Ok(())
}

#Native Axum handlers and extractors

rust
use kerniq_server::prelude::*;
use kerniq_server::axum;

async fn axum_handler(axum::extract::Path(id): axum::extract::Path<u32>) -> axum::Json<u32> {
    axum::Json(id)
}

let app = App::new()
    .get("/items/{id}", axum_handler)
    .get("/ext", |axum::Extension(v): axum::Extension<&'static str>| async move { v })
    .layer(axum::Extension("injected"));

#Per-route Tower layers

rust
use std::time::Duration;
use kerniq_server::prelude::*;
use kerniq_server::routing::methods;
use kerniq_server::tower::ServiceBuilder;

let app = App::new().route(
    "/reports",
    methods::get(|| async { "slow report" }).layer(
        ServiceBuilder::new()
            .layer(kerniq_server::axum::error_handling::HandleErrorLayer::new(
                |_: kerniq_server::tower::BoxError| async { StatusCode::GATEWAY_TIMEOUT },
            ))
            .timeout(Duration::from_secs(60)),
    ),
);

#Testing with Tower

Because the app becomes a tower::Service, tests can call it in-process without a socket:

rust
use kerniq_server::prelude::*;
use kerniq_server::axum::body::Body;
use kerniq_server::http::Request;
use tower::ServiceExt;

let router = App::new().get("/", || async { "ok" }).into_axum_router();
let response = router
    .oneshot(Request::get("/").body(Body::empty()).unwrap())
    .await
    .unwrap();
assert_eq!(response.status(), StatusCode::OK);

The integration tests in crates/kerniq-server/tests/interop.rs verify each of these paths.

Edit this page on GitHub

Kerniq Server is licensed under MIT or Apache-2.0.