axum is a web application framework that focuses on ergonomics and modularity.
tower andtower-http ecosystem ofmiddleware, services, and utilities.In particular the last point is what setsaxum apart from other frameworks.axum doesn’t have its own middleware system but instead usestower::Service. This meansaxum gets timeouts, tracing, compression,authorization, and more, for free. It also enables you to share middleware withapplications written usinghyper ortonic.
axum is designed to work withtokio andhyper. Runtime andtransport layer independence is not a goal, at least for the time being.
The “Hello, World!” of axum is:
useaxum::{handler::get,Router,};#[tokio::main]asyncfnmain() {// build our application with a single routeletapp=Router::new().route("/",get(||async {"Hello, World!" }));// run it with hyper on localhost:3000axum::Server::bind(&"0.0.0.0:3000".parse().unwrap()) .serve(app.into_make_service()) .await .unwrap();}In axum a “handler” is an async function that accepts zero or more“extractors” as arguments and returns something thatcan be convertedinto a response.
Handlers is where your custom domain logic lives and axum applications arebuilt by routing between handlers.
Some examples of handlers:
usebytes::Bytes;usehttp::StatusCode;// Handler that immediately returns an empty `200 OK` response.asyncfnunit_handler() {}// Handler that immediately returns an empty `200 OK` response with a plain// text body.asyncfnstring_handler()->String {"Hello, World!".to_string()}// Handler that buffers the request body and returns it.asyncfnecho(body:Bytes)->Result<String,StatusCode> {ifletOk(string)=String::from_utf8(body.to_vec()) {Ok(string) }else {Err(StatusCode::BAD_REQUEST) }}Routing between handlers looks like this:
useaxum::{handler::get,Router,};letapp=Router::new() .route("/",get(get_slash).post(post_slash)) .route("/foo",get(get_foo));asyncfnget_slash() {// `GET /` called}asyncfnpost_slash() {// `POST /` called}asyncfnget_foo() {// `GET /foo` called}Routes can also be dynamic like/users/:id. Seeextractorsfor more details.
You can also define routes separately and merge them withRouter::or.
Note that routes are matchedbottom to top so routes that should havehigher precedence should be addedafter routes with lower precedence:
useaxum::{body::{Body,BoxBody},handler::get,http::Request,Router,};usetower::{Service,ServiceExt};usehttp::{Method,Response,StatusCode};usestd::convert::Infallible;// `/foo` also matches `/:key` so adding the routes in this order means `/foo`// will be inaccessible.letmutapp=Router::new() .route("/foo",get(||async {"/foo called" })) .route("/:key",get(||async {"/:key called" }));// Even though we use `/foo` as the request URI, `/:key` takes precedence// since its defined last.let (status,body)=call_service(&mutapp,Method::GET,"/foo").await;assert_eq!(status,StatusCode::OK);assert_eq!(body,"/:key called");// We have to add `/foo` after `/:key` since routes are matched bottom to// top.letmutnew_app=Router::new() .route("/:key",get(||async {"/:key called" })) .route("/foo",get(||async {"/foo called" }));// Now it workslet (status,body)=call_service(&mutnew_app,Method::GET,"/foo").await;assert_eq!(status,StatusCode::OK);assert_eq!(body,"/foo called");// And the other route works as welllet (status,body)=call_service(&mutnew_app,Method::GET,"/bar").await;assert_eq!(status,StatusCode::OK);assert_eq!(body,"/:key called");// Little helper function to make calling a service easier. Just for// demonstration purposes.asyncfncall_service<S>(svc:&mutS,method:Method,uri:&str,)-> (StatusCode,String)whereS:Service<Request<Body>,Response=Response<BoxBody>,Error=Infallible>{letreq=Request::builder().method(method).uri(uri).body(Body::empty()).unwrap();letres=svc.ready().await.unwrap().call(req).await.unwrap();letstatus=res.status();letbody=res.into_body();letbody=hyper::body::to_bytes(body).await.unwrap();letbody=String::from_utf8(body.to_vec()).unwrap(); (status,body)}Serviceaxum also supports routing to generalServices:
useaxum::{body::Body,http::Request,Router,service};usetower_http::services::ServeFile;usehttp::Response;usestd::convert::Infallible;usetower::service_fn;letapp=Router::new() .route(// Any request to `/` goes to a service"/",// Services who's response body is not `axum::body::BoxBody`// can be wrapped in `axum::service::any` (or one of the other routing filters)// to have the response body mappedservice::any(service_fn(|_:Request<Body>|async {letres=Response::new(Body::from("Hi from `GET /`"));Ok(res) })) ) .route("/foo",// This service's response body is `axum::body::BoxBody` so// it can be routed to directly.service_fn(|req:Request<Body>|asyncmove {letbody=Body::from(format!("Hi from `{} /foo`",req.method()));letbody=axum::body::box_body(body);letres=Response::new(body);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 theservice module for more details.
Note that routing to general services has a small gotcha when it comes toerrors. axum currently does not support mixing routes to fallible serviceswith infallible handlers. For example this doesnot compile:
useaxum::{Router,service,handler::get,http::{Request,Response},body::Body,};usestd::io;usetower::service_fn;letapp=Router::new()// this route cannot fail .route("/foo",get(||async {}))// this route can fail with io::Error .route("/",service::get(service_fn(|_req:Request<Body>|async {letcontents=tokio::fs::read_to_string("some_file").await?;Ok::<_,io::Error>(Response::new(Body::from(contents))) })), );The solution is to usehandle_error and handle the error from theservice:
useaxum::{Router,service,handler::get,http::{Request,Response},response::IntoResponse,body::Body,};usestd::{io,convert::Infallible};usetower::service_fn;letapp=Router::new()// this route cannot fail .route("/foo",get(||async {}))// this route can fail with io::Error .route("/",service::get(service_fn(|_req:Request<Body>|async {letcontents=tokio::fs::read_to_string("some_file").await?;Ok::<_,io::Error>(Response::new(Body::from(contents))) })) .handle_error(handle_io_error), );fnhandle_io_error(error:io::Error)->Result<implIntoResponse,Infallible> {// ...}In this particular case you can also handle the error directly inservice_fn but that is not possible, if you’re routing to a service whichyou don’t control.
See“Error handling” for more details onhandle_errorand error handling in general.
Routes can be nested by callingRouter::nest:
useaxum::{body::{Body,BoxBody},http::Request,handler::get,Router,routing::BoxRoute};usetower_http::services::ServeFile;usehttp::Response;fnapi_routes()->Router<BoxRoute> {Router::new() .route("/users",get(|_:Request<Body>|async {/* ... */ })) .boxed()}letapp=Router::new() .route("/",get(|_:Request<Body>|async {/* ... */ })) .nest("/api",api_routes());Note that nested routes will not see the orignal request URI but insteadhave the matched prefix stripped. This is necessary for services like staticfile serving to work. UseOriginalUri if you need the original requestURI.
An extractor is a type that implementsFromRequest. Extractors is howyou pick apart the incoming request to get the parts your handler needs.
For example,extract::Json is an extractor that consumes the requestbody and deserializes it as JSON into some target type:
useaxum::{extract,handler::post,Router,};useserde::Deserialize;letapp=Router::new().route("/users",post(create_user));#[derive(Deserialize)]structCreateUser {email:String,password:String,}asyncfncreate_user(payload:extract::Json<CreateUser>) {letpayload:CreateUser=payload.0;// ...}extract::Path can be used to extract params from a dynamic URL. Itis compatible with any type that implementsserde::Deserialize, such asUuid:
useaxum::{extract,handler::post,Router,};useuuid::Uuid;letapp=Router::new().route("/users/:id",post(create_user));asyncfncreate_user(extract::Path(user_id):extract::Path<Uuid>) {// ...}You can also apply multiple extractors:
useaxum::{extract,handler::get,Router,};useuuid::Uuid;useserde::Deserialize;letapp=Router::new().route("/users/:id/things",get(get_user_things));#[derive(Deserialize)]structPagination {page:usize,per_page:usize,}implDefaultforPagination {fndefault()->Self {Self {page:1,per_page:30 } }}asyncfnget_user_things(extract::Path(user_id):extract::Path<Uuid>,pagination:Option<extract::Query<Pagination>>,) {letpagination:Pagination=pagination.unwrap_or_default().0;// ...}AdditionallyRequest<Body> is itself an extractor:
useaxum::{body::Body,handler::post,http::Request,Router,};letapp=Router::new().route("/users/:id",post(handler));asyncfnhandler(req:Request<Body>) {// ...}However it cannot be combined with other extractors since it consumes theentire request.
See theextract module for more details.
Anything that implementsIntoResponse can bereturned from a handler:
useaxum::{body::Body,handler::{get,Handler},http::{Request,header::{HeaderMap,HeaderName,HeaderValue}},response::{IntoResponse,Html,Json,Headers},Router,};usehttp::{StatusCode,Response,Uri};useserde_json::{Value,json};// We've already seen returning &'static strasyncfnplain_text()->&'staticstr {"foo"}// String works too and will get a `text/plain` content-typeasyncfnplain_text_string(uri:Uri)->String {format!("Hi from {}",uri.path())}// Bytes will get a `application/octet-stream` content-typeasyncfnbytes()->Vec<u8> {vec![1,2,3,4]}// `()` gives an empty responseasyncfnempty() {}// `StatusCode` gives an empty response with that status codeasyncfnempty_with_status()->StatusCode {StatusCode::NOT_FOUND}// A tuple of `StatusCode` and something that implements `IntoResponse` can// be used to override the status codeasyncfnwith_status()-> (StatusCode,&'staticstr) { (StatusCode::INTERNAL_SERVER_ERROR,"Something went wrong")}// A tuple of `HeaderMap` and something that implements `IntoResponse` can// be used to override the headersasyncfnwith_headers()-> (HeaderMap,&'staticstr) {letmutheaders=HeaderMap::new();headers.insert(HeaderName::from_static("x-foo"),HeaderValue::from_static("foo"), ); (headers,"foo")}// You can also override both status and headers at the same timeasyncfnwith_headers_and_status()-> (StatusCode,HeaderMap,&'staticstr) {letmutheaders=HeaderMap::new();headers.insert(HeaderName::from_static("x-foo"),HeaderValue::from_static("foo"), ); (StatusCode::INTERNAL_SERVER_ERROR,headers,"foo")}// `Headers` makes building the header map easier and `impl Trait` is easier// so you don't have to write the whole typeasyncfnwith_easy_headers()->implIntoResponse {Headers(vec![("x-foo","foo")])}// `Html` gives a content-type of `text/html`asyncfnhtml()->Html<&'staticstr> {Html("<h1>Hello, World!</h1>")}// `Json` gives a content-type of `application/json` and works with any type// that implements `serde::Serialize`asyncfnjson()->Json<Value> {Json(json!({"data":42 }))}// `Result<T, E>` where `T` and `E` implement `IntoResponse` is useful for// returning errorsasyncfnresult()->Result<&'staticstr,StatusCode> {Ok("all good")}// `Response` gives full controlasyncfnresponse()->Response<Body> {Response::builder().body(Body::empty()).unwrap()}letapp=Router::new() .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("/with_headers",get(with_headers)) .route("/with_headers_and_status",get(with_headers_and_status)) .route("/with_easy_headers",get(with_easy_headers)) .route("/html",get(html)) .route("/json",get(json)) .route("/result",get(result)) .route("/response",get(response));axum is designed to take full advantage of the tower and tower-httpecosystem of middleware.
If you’re new to tower we recommend you read itsguides fora general introduction to tower and its concepts.
A middleware can be applied to a single handler like so:
useaxum::{handler::{get,Handler},Router,};usetower::limit::ConcurrencyLimitLayer;letapp=Router::new() .route("/",get(handler.layer(ConcurrencyLimitLayer::new(100))), );asyncfnhandler() {}Middleware can also be applied to a group of routes like so:
useaxum::{handler::{get,post},Router,};usetower::limit::ConcurrencyLimitLayer;letapp=Router::new() .route("/",get(get_slash)) .route("/foo",post(post_foo)) .layer(ConcurrencyLimitLayer::new(100));asyncfnget_slash() {}asyncfnpost_foo() {}Handlers created from async functions must always produce a response, evenwhen returning aResult<T, E> the error type must implementIntoResponse. In practice this makes error handling very predictable andeasier to reason about.
However when applying middleware, or embedding other tower services, errorsmight happen. For exampleTimeout will return an error if the timeoutelapses. By default these errors will be propagated all the way up to hyperwhere the connection will be closed. If that isn’t desirable you can callhandle_error to handle errors fromadding a middleware to a handler:
useaxum::{handler::{get,Handler},Router,};usetower::{BoxError,timeout::{TimeoutLayer,error::Elapsed},};usestd::{borrow::Cow,time::Duration,convert::Infallible};usehttp::StatusCode;letapp=Router::new() .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` returnsiferror.is::<Elapsed>() {returnOk::<_,Infallible>((StatusCode::REQUEST_TIMEOUT,"Request took too long".into(), )); }// If we encounter some error we don't handle return a generic// errorreturnOk::<_,Infallible>((StatusCode::INTERNAL_SERVER_ERROR,// `Cow` lets us return either `&str` or `String`Cow::from(format!("Unhandled internal error: {}",error)), )); })), );asyncfnhandle() {}The closure passed tohandle_error mustreturnResult<T, E> whereT implementsIntoResponse.
Seerouting::Router::handle_error for more details.
tower::ServiceBuilder can be used to combine multiple middleware:
useaxum::{body::Body,handler::get,http::Request,Router,};usetower::ServiceBuilder;usetower_http::compression::CompressionLayer;usestd::{borrow::Cow,time::Duration};letmiddleware_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();letapp=Router::new() .route("/",get(|_:Request<Body>|async {/* ... */ })) .layer(middleware_stack);tower::util andtower_http have a large collection of middleware that are compatiblewith axum. Some commonly used are:
useaxum::{body::{Body,BoxBody},handler::get,http::{Request,Response},Router,};usetower::{filter::AsyncFilterLayer,util::AndThenLayer,ServiceBuilder,};usestd::convert::Infallible;usetower_http::trace::TraceLayer;letmiddleware_stack=ServiceBuilder::new()// `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)) .into_inner();asyncfnmap_request(req:Request<Body>)->Result<Request<Body>,Infallible> {Ok(req)}asyncfnmap_response(res:Response<BoxBody>)->Result<Response<BoxBody>,Infallible> {Ok(res)}letapp=Router::new() .route("/",get(||async {/* ... */ })) .layer(middleware_stack);Additionally axum providesextract::extractor_middleware() for converting any extractor intoa middleware. Among other things, this can be useful for doing authorization. Seeextract::extractor_middleware() for more details.
You can also write you own middleware by implementingtower::Service:
useaxum::{body::{Body,BoxBody},handler::get,http::{Request,Response},Router,};usefutures::future::BoxFuture;usetower::{Service,layer::layer_fn};usestd::task::{Context,Poll};#[derive(Clone)]structMyMiddleware<S> {inner:S,}impl<S,ReqBody,ResBody>Service<Request<ReqBody>>forMyMiddleware<S>whereS:Service<Request<ReqBody>,Response=Response<ResBody>>+Clone+Send+'static,S::Future:Send+'static,ReqBody:Send+'static,ResBody:Send+'static,{typeResponse=S::Response;typeError=S::Error;typeFuture=BoxFuture<'static,Result<Self::Response,Self::Error>>;fnpoll_ready(&mutself,cx:&mutContext<'_>)->Poll<Result<(),Self::Error>> {self.inner.poll_ready(cx) }fncall(&mutself,mutreq:Request<ReqBody>)->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 detailsletclone=self.inner.clone();letmutinner=std::mem::replace(&mutself.inner,clone);Box::pin(asyncmove {letres:Response<ResBody>=inner.call(req).await?;println!("`MyMiddleware` received the response");Ok(res) }) }}letapp=Router::new() .route("/",get(||async {/* ... */ })) .layer(layer_fn(|inner|MyMiddleware {inner }));It is common to share some state between handlers for example to share apool of database connections or clients to other services. That can be doneusing theAddExtension middleware (applied withAddExtensionLayer)and theextract::Extension extractor:
useaxum::{AddExtensionLayer,extract,handler::get,Router,};usestd::sync::Arc;structState {// ...}letshared_state=Arc::new(State {/* ... */ });letapp=Router::new() .route("/",get(handler)) .layer(AddExtensionLayer::new(shared_state));asyncfnhandler(state:extract::Extension<Arc<State>>,) {letstate:Arc<State>=state.0;// ...}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 itsthe easiest way to get started.
Note thataxum::Server is re-exported by axum so if thats all you needthen you don’t have to explicitly depend on hyper.
Tower isn’t strictly necessary either but helpful for testing. See thetesting example in the repo to learn more about testing axum apps.
The axum repo containsa number of examples that show how to put all thepieces together.
axum uses a set offeature flags to reduce the amount of compiled andoptional dependencies.
The following optional features are available:
ws: Enables WebSockets support.headers: Enables extracting typed headers viaextract::TypedHeader.multipart: Enables parsingmultipart/form-data requests withextract::Multipart.tower-log: Enablestower’slog feature. Enabled by default.pub use async_trait::async_trait;pub usehttp;pub use hyper::Server;pub use tower_http::add_extension::AddExtension;pub use tower_http::add_extension::AddExtensionLayer;HTTP body 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 betweenServices.
Use TowerServices to handle requests.
Errors that can happen when using axum.
JSON Extractor/Response
The router type for composing handlers and services.
Alias for a type-erased error type.