Crate axum[−][src]
Expand description
axum is a web application framework that focuses on ergonomics and modularity.
Table of contents
- High level features
- Compatibility
- Example
- Routing
- Handlers
- Extractors
- Responses
- Error handling
- Middleware
- Sharing state with handlers
- Required dependencies
- Examples
- Feature flags
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 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(foo))
.route("/foo/bar", get(foo_bar));
// which calls one of these handlers
async fn root() {}
async fn 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`
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
Its 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::{
body::{Body, BoxBody},
routing::get,
http::{Request, Response},
error_handling::HandleErrorLayer,
Router,
};
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<BoxBody>) -> Result<Response<BoxBody>, 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
You can also write you own middleware by implementing tower::Service
:
use axum::{
body::{Body, BoxBody},
routing::get,
http::{Request, Response},
Router,
};
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<BoxBody>> + 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<BoxBody> = inner.call(req).await?;
println!("`MyMiddleware` received the response");
Ok(res)
})
}
}
let app = Router::new()
.route("/", get(|| async { /* ... */ }))
.layer(layer_fn(|inner| MyMiddleware { inner }));
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 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>>,
) {
// ...
}
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:
Name | Description | Default? |
---|---|---|
headers | Enables extracting typed headers via TypedHeader | No |
http1 | Enables hyper’s http1 feature | Yes |
http2 | Enables hyper’s http2 feature | No |
json | Enables the Json type and some similar convenience functionality | Yes |
multipart | Enables parsing multipart/form-data requests with Multipart | No |
tower-log | Enables tower ’s log feature | Yes |
ws | Enables WebSockets support via extract::ws | No |
Re-exports
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.
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.
json
JSON Extractor / Response.
The router type for composing handlers and services.
Type Definitions
Alias for a type-erased error type.