Crate axum

source · []
Expand description

axum is a web application framework that focuses on ergonomics and modularity.

Table of contents

High level features

  • Route requests to handlers with a macro free API.
  • Declaratively parse requests using extractors.
  • Simple and predictable error handling model.
  • Generate responses with minimal boilerplate.
  • Take full advantage of the tower and tower-http ecosystem of middleware, services, and utilities.

In particular the last point is what sets axum apart from other frameworks. axum doesn’t have its own middleware system but instead uses tower::Service. This means axum gets timeouts, tracing, compression, authorization, and more, for free. It also enables you to share middleware with applications written using hyper or tonic.

Compatibility

axum is designed to work with tokio and hyper. Runtime and transport layer independence is not a goal, at least for the time being.

Example

The “Hello, World!” of axum is:

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

#[tokio::main]
async fn main() {
    // build our application with a single route
    let app = Router::new().route("/", get(|| async { "Hello, World!" }));

    // run it with hyper on localhost:3000
    axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
        .serve(app.into_make_service())
        .await
        .unwrap();
}

Routing

Router is used to setup which paths goes to which services:

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

// our router
let app = Router::new()
    .route("/", get(root))
    .route("/foo", get(get_foo).post(post_foo))
    .route("/foo/bar", get(foo_bar));

// which calls one of these handlers
async fn root() {}
async fn get_foo() {}
async fn post_foo() {}
async fn foo_bar() {}

See Router for more details on routing.

Handlers

In axum a “handler” is an async function that accepts zero or more “extractors” as arguments and returns something that can be converted into a response.

Handlers is where your application logic lives and axum applications are built by routing between handlers.

See handler for more details on handlers.

Extractors

An extractor is a type that implements FromRequest. Extractors is how you pick apart the incoming request to get the parts your handler needs.

use axum::extract::{Path, Query, Json};
use std::collections::HashMap;

// `Path` gives you the path parameters and deserializes them.
async fn path(Path(user_id): Path<u32>) {}

// `Query` gives you the query parameters and deserializes them.
async fn query(Query(params): Query<HashMap<String, String>>) {}

// Buffer the request body and deserialize it as JSON into a
// `serde_json::Value`. `Json` supports any type that implements
// `serde::Deserialize`.
async fn json(Json(payload): Json<serde_json::Value>) {}

See extract for more details on extractors.

Responses

Anything that implements IntoResponse can be returned from handlers.

use axum::{
    body::Body,
    routing::get,
    response::Json,
    Router,
};
use serde_json::{Value, json};

// `&'static str` becomes a `200 OK` with `content-type: text/plain; charset=utf-8`
async fn plain_text() -> &'static str {
    "foo"
}

// `Json` gives a content-type of `application/json` and works with any type
// that implements `serde::Serialize`
async fn json() -> Json<Value> {
    Json(json!({ "data": 42 }))
}

let app = Router::new()
    .route("/plain_text", get(plain_text))
    .route("/json", get(json));

See response for more details on building responses.

Error handling

axum aims to have a simple and predictable error handling model. That means it is simple to convert errors into responses and you are guaranteed that all errors are handled.

See error_handling for more details on axum’s error handling model and how to handle errors gracefully.

Middleware

axum is designed to take full advantage of the tower and tower-http ecosystem of middleware.

If you’re new to tower we recommend you read its guides for a general introduction to tower and its concepts.

axum supports adding middleware to both individual handlers and entire routers. For more details on that see

Applying multiple middleware

It’s recommended to use tower::ServiceBuilder to apply multiple middleware at once, instead of calling Router::layer repeatedly:

use axum::{
    routing::get,
    AddExtensionLayer,
    Router,
};
use tower_http::{trace::TraceLayer};
use tower::{ServiceBuilder, limit::ConcurrencyLimitLayer};

async fn handler() {}

#[derive(Clone)]
struct State {}

let app = Router::new()
    .route("/", get(handler))
    .layer(
        ServiceBuilder::new()
            .layer(TraceLayer::new_for_http())
            .layer(ConcurrencyLimitLayer::new(64))
            .layer(AddExtensionLayer::new(State {}))
    );

Middleware and errors

If you’re applying middleware that produces errors you have to handle the errors so they’re converted into responses. You can learn more about doing that here.

Commonly used middleware

tower and tower_http have a large collection of middleware that are compatible with axum. Some commonly used middleware are:

use axum::{
	response::Response,
    Router,
    body::{Body, BoxBody},
    error_handling::HandleErrorLayer,
    http::Request,
    routing::get,
};
use tower::{
    filter::AsyncFilterLayer,
    util::AndThenLayer,
    ServiceBuilder,
};
use std::convert::Infallible;
use tower_http::trace::TraceLayer;

let middleware_stack = ServiceBuilder::new()
    // Handle errors from middleware
    //
    // This middleware most be added above any fallible
    // ones if you're using `ServiceBuilder`, due to how ordering works
    .layer(HandleErrorLayer::new(handle_error))
    // `TraceLayer` adds high level tracing and logging
    .layer(TraceLayer::new_for_http())
    // `AsyncFilterLayer` lets you asynchronously transform the request
    .layer(AsyncFilterLayer::new(map_request))
    // `AndThenLayer` lets you asynchronously transform the response
    .layer(AndThenLayer::new(map_response));

async fn map_request(req: Request<Body>) -> Result<Request<Body>, Infallible> {
    Ok(req)
}

async fn map_response(res: Response) -> Result<Response, Infallible> {
    Ok(res)
}

let app = Router::new()
    .route("/", get(|| async { /* ... */ }))
    .layer(middleware_stack);

Additionally axum provides extract::extractor_middleware() for converting any extractor into a middleware. See extract::extractor_middleware() for more details.

Writing your own middleware with axum_extra::middleware::from_fn

The easiest way to write a custom middleware is using axum_extra::middleware::from_fn. See that function for more details.

Writing your own middleware with tower::Service

For maximum control (and a more low level API) you can write you own middleware by implementing tower::Service:

use axum::{
    response::Response,
    Router,
    body::{Body, BoxBody},
    http::Request,
    routing::get,
};
use futures::future::BoxFuture;
use tower::{Service, layer::layer_fn};
use std::task::{Context, Poll};

#[derive(Clone)]
struct MyMiddleware<S> {
    inner: S,
}

impl<S> Service<Request<Body>> for MyMiddleware<S>
where
    S: Service<Request<Body>, Response = Response> + Clone + Send + 'static,
    S::Future: Send + 'static,
{
    type Response = S::Response;
    type Error = S::Error;
    type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;

    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        self.inner.poll_ready(cx)
    }

    fn call(&mut self, mut req: Request<Body>) -> Self::Future {
        println!("`MyMiddleware` called!");

        // best practice is to clone the inner service like this
        // see https://github.com/tower-rs/tower/issues/547 for details
        let clone = self.inner.clone();
        let mut inner = std::mem::replace(&mut self.inner, clone);

        Box::pin(async move {
            let res: Response = inner.call(req).await?;

            println!("`MyMiddleware` received the response");

            Ok(res)
        })
    }
}

let app = Router::new()
    .route("/", get(|| async { /* ... */ }))
    .layer(layer_fn(|inner| MyMiddleware { inner }));

Routing to services and backpressure

Generally routing to one of multiple services and backpressure doesn’t mix well. Ideally you would want ensure a service is ready to receive a request before calling it. However, in order to know which service to call, you need the request…

One approach is to not consider the router service itself ready until all destination services are ready. That is the approach used by tower::steer::Steer.

Another approach is to always consider all services ready (always return Poll::Ready(Ok(()))) from Service::poll_ready and then actually drive readiness inside the response future returned by Service::call. This works well when your services don’t care about backpressure and are always ready anyway.

axum expects that all services used in your app wont care about backpressure and so it uses the latter strategy. However that means you should avoid routing to a service (or using a middleware) that does care about backpressure. At the very least you should load shed so requests are dropped quickly and don’t keep piling up.

It also means that if poll_ready returns an error then that error will be returned in the response future from call and not from poll_ready. In that case, the underlying service will not be discarded and will continue to be used for future requests. Services that expect to be discarded if poll_ready fails should not be used with axum.

One possible approach is to only apply backpressure sensitive middleware around your entire app. This is possible because axum applications are themselves services:

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

async fn handler() { /* ... */ }

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

let app = ServiceBuilder::new()
    .layer(some_backpressure_sensitive_middleware)
    .service(app);

However when applying middleware around your whole application in this way you have to take care that errors are still being handled with appropriately.

Also note that handlers created from async functions don’t care about backpressure and are always ready. So if you’re not using any Tower middleware you don’t have to worry about any of this.

Sharing state with handlers

It is common to share some state between handlers for example to share a pool of database connections or clients to other services.

The two most common ways of doing that is:

  • Using request extensions
  • Using closure captures

Using request extensions

The easiest way to extract state in handlers is using AddExtension middleware (applied with AddExtensionLayer) and the Extension extractor:

use axum::{
    AddExtensionLayer,
    extract::Extension,
    routing::get,
    Router,
};
use std::sync::Arc;

struct State {
    // ...
}

let shared_state = Arc::new(State { /* ... */ });

let app = Router::new()
    .route("/", get(handler))
    .layer(AddExtensionLayer::new(shared_state));

async fn handler(
    Extension(state): Extension<Arc<State>>,
) {
    // ...
}

The downside to this approach is that you’ll get runtime errors (specifically a 500 Internal Server Error response) if you try and extract an extension that doesn’t exist, perhaps because you forgot add the middleware or because you’re extracting the wrong type.

Using closure captures

State can also be passed directly to handlers using closure captures:

use axum::{
    AddExtensionLayer,
    Json,
    extract::{Extension, Path},
    routing::{get, post},
    Router,
};
use std::sync::Arc;
use serde::Deserialize;

struct State {
    // ...
}

let shared_state = Arc::new(State { /* ... */ });

let app = Router::new()
    .route(
        "/users",
        post({
            let shared_state = Arc::clone(&shared_state);
            move |body| create_user(body, Arc::clone(&shared_state))
        }),
    )
    .route(
        "/users/:id",
        get({
            let shared_state = Arc::clone(&shared_state);
            move |path| get_user(path, Arc::clone(&shared_state))
        }),
    );

async fn get_user(Path(user_id): Path<String>, state: Arc<State>) {
    // ...
}

async fn create_user(Json(payload): Json<CreateUserPayload>, state: Arc<State>) {
    // ...
}

#[derive(Deserialize)]
struct CreateUserPayload {
    // ...
}

The downside to this approach is that its a little more verbose than using extensions.

Building integrations for axum

Libraries authors that want to provide FromRequest or IntoResponse implementations should depend on the axum-core crate, instead of axum if possible. axum-core contains core types and traits and is less likely to receive breaking changes.

Required dependencies

To use axum there are a few dependencies you have pull in as well:

[dependencies]
axum = "<latest-version>"
hyper = { version = "<latest-version>", features = ["full"] }
tokio = { version = "<latest-version>", features = ["full"] }
tower = "<latest-version>"

The "full" feature for hyper and tokio isn’t strictly necessary but it’s the easiest way to get started.

Note that hyper::Server is re-exported by axum so if thats all you need then you don’t have to explicitly depend on hyper.

Tower isn’t strictly necessary either but helpful for testing. See the testing example in the repo to learn more about testing axum apps.

Examples

The axum repo contains a number of examples that show how to put all the pieces together.

Feature flags

axum uses a set of feature flags to reduce the amount of compiled and optional dependencies.

The following optional features are available:

NameDescriptionDefault?
headersEnables extracting typed headers via TypedHeaderNo
http1Enables hyper’s http1 featureYes
http2Enables hyper’s http2 featureNo
jsonEnables the Json type and some similar convenience functionalityYes
multipartEnables parsing multipart/form-data requests with MultipartNo
tower-logEnables tower’s log featureYes
wsEnables WebSockets support via extract::wsNo

Re-exports

pub use async_trait::async_trait;
pub use headers;
pub use http;
pub use hyper::Server;

Modules

HTTP body utilities.

Error handling model and utilities

Types and traits for extracting data from requests.

Async functions that can be used to handle requests.

Types and traits for generating responses.

Routing between Services and handlers.

Structs

Middleware for adding some shareable value to request extensions.

Layer for adding some shareable value to request extensions.

Errors that can happen when using axum.

Jsonjson

JSON Extractor / Response.

The router type for composing handlers and services.

Type Definitions

Alias for a type-erased error type.