Announcing Axum
July 30, 2021
Today we are happy to announce axum
: An easy to use, yet powerful, web framework
designed to take full advantage of the Tokio ecosystem.
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
andtower-http
ecosystem of middleware, services, and utilities.
In particular the last point is what sets axum
apart from existing 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
.
Usage examples
The "hello world" of axum
looks like this:
use axum::prelude::*;
use std::net::SocketAddr;
#[tokio::main]
async fn main() {
let app = route("/", get(root));
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
hyper::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
async fn root() -> &'static str {
"Hello, World!"
}
This will respond to GET /
with a 200 OK
response where the body is Hello, World!
. Any other requests will result in a 404 Not Found
response.
Extractors
Requests can be parsed declaratively using "extractors". An extractor is a type
that implements FromRequest
. Extractors can be used as arguments to handlers
and will run if the request URI matches.
For example, Json
is an extractor that consumes the request body and parses it
as JSON:
use axum::{prelude::*, extract::Json};
use serde::Deserialize;
#[derive(Deserialize)]
struct CreateUser {
username: String,
}
async fn create_user(Json(payload): Json<CreateUser>) {
// `payload` is a `CreateUser`
}
let app = route("/users", post(create_user));
axum
ships with many useful extractors such as:
Bytes
,String
,Body
, andBodyStream
for consuming the request body.Method
,HeaderMap
, andUri
for getting specific parts of the request.Form
,Query
,UrlParams
, andUrlParamsMap
for more high level request parsing.- [
Extension
] for sharing state across handlers. Request<hyper::Body>
if you want full control.Result<T, E>
andOption<T>
to make an extractor optional.
You can also define your own by implementing FromRequest
.
Building responses
Handlers can return anything that implements IntoResponse
and it will
automatically be converted into a response:
use http::StatusCode;
use axum::response::{Html, Json};
use serde_json::{json, Value};
// We've already seen returning &'static str
async fn text() -> &'static str {
"Hello, World!"
}
// String works too
async fn string() -> String {
"Hello, World!".to_string()
}
// Returning a tuple of `StatusCode` and another `IntoResponse` will
// change the status code
async fn not_found() -> (StatusCode, &'static str) {
(StatusCode::NOT_FOUND, "not found")
}
// `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 }))
}
This means in practice you rarely have to build your own Response
s. You can
also implement IntoResponse
to create your own domain specific responses.
Routing
Multiple routes can be combined using a simple DSL:
use axum::prelude::*;
let app = route("/", get(root))
.route("/users", get(list_users).post(create_user))
.route("/users/:id", get(show_user).delete(delete_user));
Middleware
axum
supports middleware from tower
and tower-http
:
use axum::prelude::*;
use tower_http::{compression::CompressionLayer, trace::TraceLayer};
use tower::ServiceBuilder;
use std::time::Duration;
let middleware_stack = ServiceBuilder::new()
// timeout all requests after 10 seconds
.timeout(Duration::from_secs(10))
// add high level tracing of requests and responses
.layer(TraceLayer::new_for_http())
// compression responses
.layer(CompressionLayer::new())
// convert the `ServiceBuilder` into a `tower::Layer`
.into_inner();
let app = route("/", get(|| async { "Hello, World!" }))
// wrap our application in the middleware stack
.layer(middleware_stack);
This feature is key as it allows us to write middleware once and share them
across applications. For example, axum
doesn't have to provide its own
tracing/logging middleware, TraceLayer
from tower-http
can be used
directly. That same middleware can be also be used for clients or servers made
with tonic
.
Routing to any tower::Service
axum
can also route requests to any tower
leaf service. Could be one you
write using service_fn
or something from another crate, such as
ServeFile
from tower-http
:
use axum::{service, prelude::*};
use http::Response;
use std::convert::Infallible;
use tower::{service_fn, BoxError};
use tower_http::services::ServeFile;
let app = route(
// Any request to `/` goes to a some `Service`
"/",
service::any(service_fn(|_: Request<Body>| async {
let res = Response::new(Body::from("Hi from `GET /`"));
Ok::<_, Infallible>(res)
}))
).route(
// GET `/static/Cargo.toml` goes to a service from tower-http
"/static/Cargo.toml",
service::get(ServeFile::new("Cargo.toml"))
);
Learn more
This is just a small sample of what axum
provides. Error handling, web
sockets, and parsing multipart/form-data
requests are some features not shown
here. See the docs for more details.
We also encourage you to check out the examples in the repo to see
some slightly larger applications written with axum
.
As always, if you have questions you can find us in the Tokio Discord server.