Announcing axum 0.6.0

November 25, 2022

Back in August we announced axum 0.6.0-rc.1 and today I'm happy to report that the prelease period is over and axum 0.6.0 is out!

axum is an ergonomic and modular web framework built with tokio, tower, and hyper.

This also includes new major versions for axum-core, axum-extra, and axum-macros.

If you've already read the rc.1 announcement then some of these things will be familiar. However many details of the APIs have been fine tuned to be easier to use and more flexible.

Type safe State extractor

Previously the recommended way to share state with handlers was to use the Extension middleware and extractor:

use axum::{
    Router,
    Extension,
    routing::get,
};

#[derive(Clone)]
struct AppState {}

let state = AppState {};

let app = Router::new()
    .route("/", get(handler))
    // Add `Extension` as a middleware
    .layer(Extension(state));

async fn handler(
    // And extract our shared `AppState` using `Extension`
    Extension(state): Extension<AppState>,
) {}

However this wasn't type safe, so if you forgot .layer(Extension(...)) things would compile just fine but you'd get runtime errors when calling handler.

In 0.6 you can use the new State extractor which works similarly to Extension but is type safe:

use axum::{
    Router,
    extract::State,
    routing::get,
};

#[derive(Clone)]
struct AppState {}

let state = AppState {};

let app = Router::new()
    .route("/", get(handler))
    // Provide the state for the router
    .with_state(state);

async fn handler(
    // And extract our shared `AppState` using `State`
    //
    // This will only compile if the type passed to `Router::with_state`
    // matches what we're extracting here
    State(state): State<AppState>,
) {}

State also supports extracting "sub states":

use axum::{
    extract::{State, FromRef},
    routing::get,
    Router,
};

// Our top level state that contains an `HttpClient` and a `Database`
//
// `#[derive(FromRef)]` makes them sub states so they can be extracted
// independently
#[derive(Clone, FromRef)]
struct AppState {
    client: HttpClient,
    database: Database,
}

#[derive(Clone)]
struct HttpClient {}

#[derive(Clone)]
struct Database {}

let state = AppState {
    client: HttpClient {},
    database: Database {},
};

let app = Router::new()
    .route("/", get(handler))
    .with_state(state);

async fn handler(
    // We can extract both `State<HttpClient>` and `State<Database>`
    State(client): State<HttpClient>,
    State(database): State<Database>,
) {}

It is also possible to use different state types on merged and nested sub routers:

let app = Router::new()
    // A route on the outermost router that requires `OuterState` as the
    // state
    .route("/", get(|_: State<OuterState>| { ... }))
    // Nest a router under `/api` that requires an `ApiState`
    //
    // We have to provide the state when nesting it into another router
    // since it uses a different state type
    .nest("/api", api_router().with_state(ApiState {}))
    // Same goes for routers we merge
    .merge(some_other_routes().with_state(SomeOtherState {}))
    // Finally provide the `OuterState` needed by the first route we
    // added
    .with_state(OuterState {});

// We don't need to provide the state when constructing the sub routers
//
// We only need to do that when putting everything together. That means
// we don't need to pass the different states around to each function
// that builds a sub router
fn api_router() -> Router<ApiState> {
    Router::new()
        .route("/users", get(|_: State<ApiState>| { ... }))
}

fn some_other_state() -> Router<SomeOtherState> {
    Router::new()
        .route("/foo", get(|_: State<SomeOtherState>| { ... }))
}

#[derive(Clone)]
struct ApiState {};

#[derive(Clone)]
struct SomeOtherState {};

#[derive(Clone)]
struct OuterState {};

While Extension still works we recommend users migrate to State, both because it is more type safe but also because it is faster.

Type safe extractor ordering

Continuing the theme of type safety, axum now enforces that only one extractor consumes the request body. In 0.5 this would compile just fine but fail at runtime:

use axum::{
    Router,
    Json,
    routing::post,
    body::Body,
    http::Request,
};

let app = Router::new()
    .route("/", post(handler).get(other_handler));

async fn handler(
    // This would consume the request body
    json_body: Json<serde_json::Value>,
    // This would also attempt to consume the body but fail
    // since it is gone
    request: Request<Body>,
) {}

async fn other_handler(
    request: Request<Body>,
    // This would also fail at runtime, even if the `AppState` extension
    // was set since `Request<Body>` consumes all extensions
    state: Extension<AppState>,
) {}

The solution was to manually ensure that you only use one extractor that consumes the request and that it was the last extractor. axum 0.6 now enforces this at compile time:

use axum::{
    Router,
    Json,
    routing::post,
    body::Body,
    http::Request,
};

let app = Router::new()
    .route("/", post(handler).get(other_handler));

async fn handler(
    // We cannot extract both `Request` and `Json`, have to pick one
    json_body: Json<serde_json::Value>,
) {}

async fn other_handler(
    state: Extension<AppState>,
    // `Request` must be extracted last
    request: Request<Body>,
) {}

This was done by reworking the FromRequest trait and adding a new FromRequestParts trait.

This also means that if you have implementations of FromRequest that don't need the request body then you should implement FromRequestParts instead.

Run extractors from middleware::from_fn

middleware::from_fn makes it easy to write middleware using familiar async/await syntax. In 0.6 such middleware can also run extractors:

use axum::{
    Router,
    middleware::{self, Next},
    response::{Response, IntoResponse},
    http::Request,
    routing::get,
};
use axum_extra::extract::cookie::{CookieJar, Cookie};

async fn my_middleware<B>(
    // Run the `CookieJar` extractor as part of this middleware
    cookie_jar: CookieJar,
    request: Request<B>,
    next: Next<B>,
) -> Response {
    let response = next.run(request).await;

    // Add a cookie to the jar
    let updated_cookie_jar = cookie_jar.add(Cookie::new("foo", "bar"));

    // Add the new cookies to the response
    (updated_cookie_jar, response).into_response()
}

let app = Router::new()
    .route("/", get(|| async { "Hello, World!" }))
    .layer(middleware::from_fn(my_middleware));

There are also new map_request and map_response middleware functions that work similarly to middleware::from_fn, as well as from_fn_with_state, [map_request_with_state,] and map_response_with_state versions that support extracting State.

Fallback inheritance for nested routers

In axum 0.5, nested routers weren't allowed to have fallbacks and would cause a panic:

let api_router = Router::new()
    .route("/users", get(|| { ... }))
    .fallback(api_fallback);

let app = Router::new()
    // this would panic since `api_router` has a fallback
    .nest("/api", api_router);

However in 0.6 that now just works and requests that start with /api but aren't matched by api_router will go to api_fallback.

The outer router's fallback will still apply if a nested router doesn't have its own fallback:

// This time without a fallback
let api_router = Router::new().route("/users", get(|| { ... }));

let app = Router::new()
    .nest("/api", api_router)
    // `api_fallback` will inherit this fallback
    .fallback(app_fallback);

So generally you just put fallbacks where you need them and axum will do the right thing!

WebAssembly support

axum now supports being compiled to WebAssembly by disabling the tokio feature:

axum = { version = "0.6", default-features = false }

default-features = false will disable the tokio feature which is among the default features.

This will disable the parts of tokio, hyper, and axum that don't support WebAssembly.

Trailing slash redirects removed

Previously if you had a route for /foo but got a request to /foo/, axum would send a redirect response to /foo. However many found this behavior surprising and it had edge-case bugs when combined with services that did their own redirection, or used middleware, so in 0.6 we decided to remove this feature.

The recommended solution is to explicitly add the routes you want:

use axum::{
    Router,
    routing::get,
};

let app = Router::new()
    // Send `/foo` and `/foo/` to the same handler
    .route("/foo", get(foo))
    .route("/foo/", get(foo));

async fn foo() {}

If you want to opt into the old behavior you can use RouterExt::route_with_tsr from axum-extra.

Mix wildcard routes and regular routes

axum's Router now has better support for mixing wildcard routes and regular routes:

use axum::{Router, routing::get};

let app = Router::new()
    // In 0.5 these routes would be considered overlapping and not be
    // allowed but in 0.6 it just works
    .route("/foo/*rest", get(|| async {}))
    .route("/foo/bar", get(|| async {}));

See the changelog for more

I encourage you to read the changelog to see all the changes and for tips on how to update from 0.5 to 0.6.

Also, please ask questions in Discord or file issues if you have trouble updating or discover bugs.

ā€” David Pedersen (@davidpdrsn)