Crate axum[][src]

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::prelude::*;
use hyper::Server;
use std::net::SocketAddr;
use tower::make::Shared;

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

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

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 custom domain logic lives and axum applications are built by routing between handlers.

Some examples of handlers:

use axum::prelude::*;
use bytes::Bytes;
use http::StatusCode;

// Handler that immediately returns an empty `200 OK` response.
async fn unit_handler() {}

// Handler that immediately returns an empty `200 OK` response with a plain
// text body.
async fn string_handler() -> String {
    "Hello, World!".to_string()
}

// Handler that buffers the request body and returns it.
async fn echo(body: Bytes) -> Result<String, StatusCode> {
    if let Ok(string) = String::from_utf8(body.to_vec()) {
        Ok(string)
    } else {
        Err(StatusCode::BAD_REQUEST)
    }
}

Routing

Routing between handlers looks like this:

use axum::prelude::*;

let app = route("/", get(get_slash).post(post_slash))
    .route("/foo", get(get_foo));

async fn get_slash() {
    // `GET /` called
}

async fn post_slash() {
    // `POST /` called
}

async fn get_foo() {
    // `GET /foo` called
}

Routes can also be dynamic like /users/:id.

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.

For example, extract::Json is an extractor that consumes the request body and deserializes it as JSON into some target type:

use axum::prelude::*;
use serde::Deserialize;

let app = route("/users", post(create_user));

#[derive(Deserialize)]
struct CreateUser {
    email: String,
    password: String,
}

async fn create_user(payload: extract::Json<CreateUser>) {
    let payload: CreateUser = payload.0;

    // ...
}

extract::UrlParams can be used to extract params from a dynamic URL. It is compatible with any type that implements std::str::FromStr, such as Uuid:

use axum::prelude::*;
use uuid::Uuid;

let app = route("/users/:id", post(create_user));

async fn create_user(params: extract::UrlParams<(Uuid,)>) {
    let user_id: Uuid = (params.0).0;

    // ...
}

There is also UrlParamsMap which provide a map like API for extracting URL params.

You can also apply multiple extractors:

use axum::prelude::*;
use uuid::Uuid;
use serde::Deserialize;

let app = route("/users/:id/things", get(get_user_things));

#[derive(Deserialize)]
struct Pagination {
    page: usize,
    per_page: usize,
}

impl Default for Pagination {
    fn default() -> Self {
        Self { page: 1, per_page: 30 }
    }
}

async fn get_user_things(
    params: extract::UrlParams<(Uuid,)>,
    pagination: Option<extract::Query<Pagination>>,
) {
    let user_id: Uuid = (params.0).0;
    let pagination: Pagination = pagination.unwrap_or_default().0;

    // ...
}

Additionally Request<Body> is itself an extractor:

use axum::prelude::*;

let app = route("/users/:id", post(handler));

async fn handler(req: Request<Body>) {
    // ...
}

However it cannot be combined with other extractors since it consumes the entire request.

See the extract module for more details.

Building responses

Anything that implements IntoResponse can be returned from a handler:

use axum::{body::Body, response::{Html, Json}, prelude::*};
use http::{StatusCode, Response, Uri};
use serde_json::{Value, json};

// We've already seen returning &'static str
async fn plain_text() -> &'static str {
    "foo"
}

// String works too and will get a `text/plain` content-type
async fn plain_text_string(uri: Uri) -> String {
    format!("Hi from {}", uri.path())
}

// Bytes will get a `application/octet-stream` content-type
async fn bytes() -> Vec<u8> {
    vec![1, 2, 3, 4]
}

// `()` gives an empty response
async fn empty() {}

// `StatusCode` gives an empty response with that status code
async fn empty_with_status() -> StatusCode {
    StatusCode::NOT_FOUND
}

// A tuple of `StatusCode` and something that implements `IntoResponse` can
// be used to override the status code
async fn with_status() -> (StatusCode, &'static str) {
    (StatusCode::INTERNAL_SERVER_ERROR, "Something went wrong")
}

// `Html` gives a content-type of `text/html`
async fn html() -> Html<&'static str> {
    Html("<h1>Hello, World!</h1>")
}

// `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 }))
}

// `Result<T, E>` where `T` and `E` implement `IntoResponse` is useful for
// returning errors
async fn result() -> Result<&'static str, StatusCode> {
    Ok("all good")
}

// `Response` gives full control
async fn response() -> Response<Body> {
    Response::builder().body(Body::empty()).unwrap()
}

let app = route("/plain_text", get(plain_text))
    .route("/plain_text_string", get(plain_text_string))
    .route("/bytes", get(bytes))
    .route("/empty", get(empty))
    .route("/empty_with_status", get(empty_with_status))
    .route("/with_status", get(with_status))
    .route("/html", get(html))
    .route("/json", get(json))
    .route("/result", get(result))
    .route("/response", get(response));

Applying middleware

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

To individual handlers

A middleware can be applied to a single handler like so:

use axum::prelude::*;
use tower::limit::ConcurrencyLimitLayer;

let app = route(
    "/",
    get(handler.layer(ConcurrencyLimitLayer::new(100))),
);

async fn handler() {}

To groups of routes

Middleware can also be applied to a group of routes like so:

use axum::prelude::*;
use tower::limit::ConcurrencyLimitLayer;

let app = route("/", get(get_slash))
    .route("/foo", post(post_foo))
    .layer(ConcurrencyLimitLayer::new(100));

async fn get_slash() {}

async fn post_foo() {}

Error handling

Handlers created from async functions must always produce a response, even when returning a Result<T, E> the error type must implement IntoResponse. In practice this makes error handling very perdictable and easier to reason about.

However when applying middleware, or embedding other tower services, errors might happen. For example Timeout will return an error if the timeout elapses. By default these errors will be propagated all the way up to hyper where the connection will be closed. If that isn’t desireable you can call handle_error to handle errors from adding a middleware to a handler:

use axum::prelude::*;
use tower::{
    BoxError, timeout::{TimeoutLayer, error::Elapsed},
};
use std::{borrow::Cow, time::Duration, convert::Infallible};
use http::StatusCode;

let app = route(
    "/",
    get(handle
        .layer(TimeoutLayer::new(Duration::from_secs(30)))
        // `Timeout` uses `BoxError` as the error type
        .handle_error(|error: BoxError| {
            // Check if the actual error type is `Elapsed` which
            // `Timeout` returns
            if error.is::<Elapsed>() {
                return Ok::<_, Infallible>((
                    StatusCode::REQUEST_TIMEOUT,
                    "Request took too long".into(),
                ));
            }

            // If we encounter some error we don't handle return a generic
            // error
            return Ok::<_, Infallible>((
                StatusCode::INTERNAL_SERVER_ERROR,
                // `Cow` lets us return either `&str` or `String`
                Cow::from(format!("Unhandled internal error: {}", error)),
            ));
        })),
);

async fn handle() {}

The closure passed to handle_error must return Result<T, E> where T implements IntoResponse.

See routing::Layered::handle_error fo more details.

Applying multiple middleware

tower::ServiceBuilder can be used to combine multiple middleware:

use axum::prelude::*;
use tower::ServiceBuilder;
use tower_http::compression::CompressionLayer;
use std::{borrow::Cow, time::Duration};

let middleware_stack = ServiceBuilder::new()
    // Return an error after 30 seconds
    .timeout(Duration::from_secs(30))
    // Shed load if we're receiving too many requests
    .load_shed()
    // Process at most 100 requests concurrently
    .concurrency_limit(100)
    // Compress response bodies
    .layer(CompressionLayer::new())
    .into_inner();

let app = route("/", get(|_: Request<Body>| async { /* ... */ }))
    .layer(middleware_stack);

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. That can be done using the AddExtension middleware (applied with AddExtensionLayer) and the extract::Extension extractor:

use axum::{AddExtensionLayer, prelude::*};
use std::sync::Arc;

struct State {
    // ...
}

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

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

async fn handler(
    state: extract::Extension<Arc<State>>,
) {
    let state: Arc<State> = state.0;

    // ...
}

Routing to any Service

axum also supports routing to general Services:

use axum::{service, prelude::*};
use tower_http::services::ServeFile;
use http::Response;
use std::convert::Infallible;
use tower::{service_fn, BoxError};

let app = route(
    // Any request to `/` goes to a service
    "/",
    service::any(service_fn(|_: Request<Body>| async {
        let res = Response::new(Body::from("Hi from `GET /`"));
        Ok(res)
    }))
).route(
    // GET `/static/Cargo.toml` goes to a service from tower-http
    "/static/Cargo.toml",
    service::get(ServeFile::new("Cargo.toml"))
);

Routing to arbitrary services in this way has complications for backpressure (Service::poll_ready). See the service module for more details.

Nesting applications

Applications can be nested by calling nest:

use axum::{prelude::*, routing::BoxRoute, body::{Body, BoxBody}};
use tower_http::services::ServeFile;
use http::Response;

fn api_routes() -> BoxRoute<Body> {
    route("/users", get(|_: Request<Body>| async { /* ... */ })).boxed()
}

let app = route("/", get(|_: Request<Body>| async { /* ... */ }))
    .nest("/api", api_routes());

Examples

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

Feature flags

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

The following optional features are available:

Modules

HTTP body utilities.

Types and traits for extracting data from requests.

Async functions that can be used to handle requests.

Re-exports of important traits, types, and functions used with axum. Meant to be glob imported.

Types and traits for generating responses.

Routing between Services.

Use Tower Services to handl requests.

wsws

Handle websocket connections.

Structs

Middleware for adding some shareable value to request extensions.

Layer for adding some shareable value to request extensions.

Functions

Create a route.

Attribute Macros