Routing
-
fixed: Nested routers are now allowed to have fallbacks (#1521):
let api_router = Router::new() .route("/users", get(|| { ... })) .fallback(api_fallback); let app = Router::new() // this would panic in 0.5 but in 0.6 it just works // // requests starting with `/api` but not handled by `api_router` // will go to `/api_fallback` .nest("/api", api_router);
The outer router's fallback will still apply if a nested router doesn't have
its own fallback:// this time without a fallback let api_router = Router::new().route("/users", get(|| { ... })); let app = Router::new() .nest("/api", api_router) // `api_fallback` will inherit this fallback .fallback(app_fallback);
-
breaking: The request
/foo/
no longer matches/foo/*rest
. If you want
to match/foo/
you have to add a route specifically for that (#1086)For example:
use axum::{Router, routing::get, extract::Path}; let app = Router::new() // this will match `/foo/bar/baz` .route("/foo/*rest", get(handler)) // this will match `/foo/` .route("/foo/", get(handler)) // if you want `/foo` to match you must also add an explicit route for it .route("/foo", get(handler)); async fn handler( // use an `Option` because `/foo/` and `/foo` don't have any path params params: Option<Path<String>>, ) {}
-
breaking: Path params for wildcard routes no longer include the prefix
/
. e.g./foo.js
will match/*filepath
with a value offoo.js
, not
/foo.js
(#1086)For example:
use axum::{Router, routing::get, extract::Path}; let app = Router::new().route("/foo/*rest", get(handler)); async fn handler( Path(params): Path<String>, ) { // for the request `/foo/bar/baz` the value of `params` will be `bar/baz` // // on 0.5 it would be `/bar/baz` }
-
fixed: Routes like
/foo
and/*rest
are no longer considered
overlapping./foo
will take priority (#1086)For example:
use axum::{Router, routing::get}; let app = Router::new() // this used to not be allowed but now just works .route("/foo/*rest", get(foo)) .route("/foo/bar", get(bar)); async fn foo() {} async fn bar() {}
-
breaking: Automatic trailing slash redirects have been removed.
Previously if you added a route for/foo
, axum would redirect calls to
/foo/
to/foo
(or vice versa for/foo/
):use axum::{Router, routing::get}; let app = Router::new() // a request to `GET /foo/` will now get `404 Not Found` // whereas in 0.5 axum would redirect to `/foo` // // same goes the other way if you had the route `/foo/` // axum will no longer redirect from `/foo` to `/foo/` .route("/foo", get(handler)); async fn handler() {}
Either explicitly add routes for
/foo
and/foo/
or use
axum_extra::routing::RouterExt::route_with_tsr
if you want the old behavior
(#1119) -
breaking:
Router::fallback
now only acceptsHandler
s (similarly to
whatget
,post
, etc. accept). Use the newRouter::fallback_service
for
setting anyService
as the fallback (#1155)This fallback on 0.5:
use axum::{Router, handler::Handler}; let app = Router::new().fallback(fallback.into_service()); async fn fallback() {}
Becomes this in 0.6
use axum::Router; let app = Router::new().fallback(fallback); async fn fallback() {}
-
changed:
Router::nest
now only acceptsRouter
s, the general-purpose
Service
nesting method has been renamed tonest_service
(#1368) -
breaking: Allow
Error: Into<Infallible>
forRoute::{layer, route_layer}
(#924) -
breaking:
MethodRouter
now panics on overlapping routes (#1102) -
breaking:
Router::route
now only acceptsMethodRouter
s created with
get
,post
, etc. Use the newRouter::route_service
for routing to
anyService
s (#1155) -
breaking: Adding a
.route_layer
onto aRouter
orMethodRouter
without any routes will now result in a panic. Previously, this just did
nothing. #1327 -
breaking:
RouterService
has been removed sinceRouter
now implements
Service
when the state is()
. UseRouter::with_state
to provide the
state and get aRouter<()>
. Note thatRouterService
only existed in the
pre-releases, not 0.5 (#1552)
Extractors
-
added: Added new type safe
State
extractor. This can be used with
Router::with_state
and gives compile errors for missing states, whereas
Extension
would result in runtime errors (#1155)We recommend migrating from
Extension
toState
for sharing application state since that is more type
safe and faster. That is done by usingRouter::with_state
andState
.This setup in 0.5
use axum::{routing::get, Extension, Router}; let app = Router::new() .route("/", get(handler)) .layer(Extension(AppState {})); async fn handler(Extension(app_state): Extension<AppState>) {} #[derive(Clone)] struct AppState {}
Becomes this in 0.6 using
State
:use axum::{routing::get, extract::State, Router}; let app = Router::new() .route("/", get(handler)) .with_state(AppState {}); async fn handler(State(app_state): State<AppState>) {} #[derive(Clone)] struct AppState {}
If you have multiple extensions, you can use fields on
AppState
and implement
FromRef
:use axum::{extract::{State, FromRef}, routing::get, Router}; let state = AppState { client: HttpClient {}, database: Database {}, }; let app = Router::new().route("/", get(handler)).with_state(state); async fn handler( State(client): State<HttpClient>, State(database): State<Database>, ) {} // the derive requires enabling the "macros" feature #[derive(Clone, FromRef)] struct AppState { client: HttpClient, database: Database, } #[derive(Clone)] struct HttpClient {} #[derive(Clone)] struct Database {}
-
breaking: It is now only possible for one extractor per handler to consume
the request body. In 0.5 doing so would result in runtime errors but in 0.6 it
is a compile error (#1272)axum enforces this by only allowing the last extractor to consume the
request.For example:
use axum::{Json, http::HeaderMap}; // This wont compile on 0.6 because both `Json` and `String` need to consume // the request body. You can use either `Json` or `String`, but not both. async fn handler_1( json: Json<serde_json::Value>, string: String, ) {} // This won't work either since `Json` is not the last extractor. async fn handler_2( json: Json<serde_json::Value>, headers: HeaderMap, ) {} // This works! async fn handler_3( headers: HeaderMap, json: Json<serde_json::Value>, ) {}
This is done by reworking the
FromRequest
trait and introducing a new
FromRequestParts
trait.If your extractor needs to consume the request body then you should implement
FromRequest
, otherwise implementFromRequestParts
.This extractor in 0.5:
struct MyExtractor { /* ... */ } #[async_trait] impl<B> FromRequest<B> for MyExtractor where B: Send, { type Rejection = StatusCode; async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> { // ... } }
Becomes this in 0.6:
use axum::{ extract::{FromRequest, FromRequestParts}, http::{StatusCode, Request, request::Parts}, async_trait, }; struct MyExtractor { /* ... */ } // implement `FromRequestParts` if you don't need to consume the request body #[async_trait] impl<S> FromRequestParts<S> for MyExtractor where S: Send + Sync, { type Rejection = StatusCode; async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> { // ... } } // implement `FromRequest` if you do need to consume the request body #[async_trait] impl<S, B> FromRequest<S, B> for MyExtractor where S: Send + Sync, B: Send + 'static, { type Rejection = StatusCode; async fn from_request(req: Request<B>, state: &S) -> Result<Self, Self::Rejection> { // ... } }
For an example of how to write an extractor that accepts different
Content-Types
see theparse-body-based-on-content-type
example. -
added:
FromRequest
andFromRequestParts
derive macro re-exports from
axum-macros
behind themacros
feature (#1352) -
added: Add
RequestExt
andRequestPartsExt
which adds convenience
methods for running extractors tohttp::Request
andhttp::request::Parts
(#1301) -
added:
JsonRejection
now displays the path at which a deserialization
error occurred (#1371) -
added: Add
extract::RawForm
for accessing raw urlencoded query bytes or request body (#1487) -
fixed: Used
400 Bad Request
forFailedToDeserializeQueryString
rejections, instead of422 Unprocessable Entity
(#1387) -
changed: The inner error of a
JsonRejection
is now
serde_path_to_error::Error<serde_json::Error>
. Previously it was
serde_json::Error
(#1371) -
changed: The default body limit now applies to the
Multipart
extractor (#1420) -
breaking:
ContentLengthLimit
has been removed. UseDefaultBodyLimit
instead (#1400) -
breaking:
RequestParts
has been removed as part of theFromRequest
rework (#1272) -
breaking:
BodyAlreadyExtracted
has been removed (#1272) -
breaking: The following types or traits have a new
S
type param
which represents the state (#1155):Router
, defaults to()
MethodRouter
, defaults to()
FromRequest
, no defaultHandler
, no default
-
breaking:
MatchedPath
can now no longer be extracted in middleware for
nested routes. In previous versions it returned invalid data when extracted
from a middleware applied to a nested router.MatchedPath
can still be
extracted from handlers and middleware that aren't on nested routers (#1462) -
breaking: Rename
FormRejection::FailedToDeserializeQueryString
to
FormRejection::FailedToDeserializeForm
(#1496)
Middleware
- added: Support running extractors on
middleware::from_fn
functions (#1088) - added: Add
middleware::from_fn_with_state
to enable running extractors that require
state (#1342) - added: Add
middleware::from_extractor_with_state
(#1396) - added: Add
map_request
,map_request_with_state
for transforming the
request with an async function (#1408) - added: Add
map_response
,map_response_with_state
for transforming the
response with an async function (#1414) - added: Support any middleware response that implements
IntoResponse
(#1152) - breaking: Remove
extractor_middleware
which was previously deprecated.
Useaxum::middleware::from_extractor
instead (#1077) - breaking: Require middleware added with
Handler::layer
to have
Infallible
as the error type (#1152)
Misc
- added: Support compiling to WASM. See the
simple-router-wasm
example
for more details (#1382) - added: Add
ServiceExt
with methods for turning anyService
into a
MakeService
similarly toRouter::into_make_service
(#1302) - added: String and binary
From
impls have been added toextract::ws::Message
to be more inline withtungstenite
(#1421) - added: Add
#[derive(axum::extract::FromRef)]
(#1430) - added: Add
accept_unmasked_frames
setting in WebSocketUpgrade (#1529) - added: Add
WebSocketUpgrade::on_failed_upgrade
to customize what to do
when upgrading a connection fails (#1539) - fixed: Annotate panicking functions with
#[track_caller]
so the error
message points to where the user added the invalid route, rather than
somewhere internally in axum (#1248) - changed: axum's MSRV is now 1.60 (#1239)
- changed: For methods that accept some
S: Service
, the bounds have been
relaxed so the response type must implementIntoResponse
rather than being a
literalResponse
- breaking: New
tokio
default feature needed for WASM support. If you
don't need WASM support but havedefault_features = false
for other reasons
you likely need to re-enable thetokio
feature (#1382) - breaking:
handler::{WithState, IntoService}
are merged into one type,
namedHandlerService
(#1418)