NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
crates.io · #267 most downloaded on crates.io
HTTP routing and request handling library that focuses on ergonomics and modularity
Last release 5 months ago
14 Apr 2026
Release timing varies
gaps range from 2 weeks to 5 months
Nearly every release is documented
notes for 59 of the last 60 stable releases
5 versions withdrawn
withdrawn after publishing
5 years old
94 releases · first in 2021
One column per quarter.
added: WebSocketUpgrade::{requested_protocols, set_selected_protocol} for more flexible subprotocol selection
WebSocketUpgrade::{requested_protocols, set_selected_protocol} for more flexible subprotocol selection (#3597)WebSocketUpgrade::{requested_protocols, set_selected_protocol} for more
flexible subprotocol selection (#3597)Clarify documentation for Router::route_layer
Router::route_layer (#3567)Relax implicit Send / Sync bounds on RouterAsService , RouterIntoService
Released without changes to fix docs.rs build.
Released without changes to fix docs.rs build.
fixed: Reject JSON request bodies with trailing characters after the JSON document ([#3453])
OptionalFromRequest for Multipart (#3220)Location::{status_code, location}middleware::ResponseAxumBodyLayer for mapping response body to axum::body::Body (#3469)impl FusedStream for WebSocket (#3443)sse module and Sse type no longer depend on the tokio feature (#3154)Redirects constructors is not a valid
header value, instead of panicking on construction, the IntoResponse impl now returns
an HTTP 500, just like Json does when serialization fails (#3377)added: Router::reset_fallback ([#3320])
added: Implement From for Message ([#3273])
From<Bytes> for Message (#3273)OptionalFromRequest for Json (#3142)OptionalFromRequest for Extension (#3157)WebSocketUpgrade (#3178)Yanked from crates.io due to unforeseen breaking change, see [#3190] for details.
Yanked from crates.io due to unforeseen breaking change, see #3190 for details.
fixed: Removed the warning about breaking changes from README
breaking: axum::ws::Message now uses Bytes in place of Vec , and a new Utf8Bytes type in place of String, for its variants ([#3078])
<details>
axum::extract::ws::Message now uses Bytes in place of Vec<u8>,
and a new Utf8Bytes type in place of String, for its variants (#3078)OptionalFromRequestParts impl for Query (#3088)tokio-tungstenite to 0.26 (#3078)serde_path_to_error to report fields that failed to parse (#3081)</details>
Note: there are further relevant changes in axum-core's changelog
/:single and /*many
to /{single} and /{*many}; the old syntax produces a panic to avoid silent change in behavior (#2645)Sync for all handlers and services added to Router
and MethodRouter (#2473)Path extractor deserializers now check that the number of parameters matches the tuple length exactly (#2931)Host extractor to axum-extra (#2956)WebSocket::close.
Users should explicitly send close messages themselves. (#2974)serve generic over the listener and IO types (#2941)Serve::tcp_nodelay and WithGracefulShutdown::tcp_nodelay.
See serve::ListenerExt for an API that let you set arbitrary TCP stream properties. (#2941)Option<Path<T>> no longer swallows all error conditions,
instead rejecting the request in many cases; see its documentation for details (#2475)axum::extract::ws::Message now uses Bytes in place of Vec<u8>,
and a new Utf8Bytes type in place of String, for its variants (#3078)serde_json::RawValue in Event::json_data (#2992)content-length before middleware.
This allows middleware to add bodies to requests without needing to manually set content-length (#2897)tokio-tungstenite to 0.26 (#3078)serde_path_to_error to report fields that failed to parse (#3081)method_not_allowed_fallback to set a fallback when a path matches but there is no handler for the given HTTP method (#2903)NoContent as a self-described shortcut for StatusCode::NO_CONTENT (#2978)get(ws_endpoint) handlers to any(ws_endpoint) (#2894)MethodFilter::CONNECT, routing::connect[_service]
and MethodRouter::connect[_service] (#2961)FailedToDeserializePathParams::kind enum with (ErrorKind::DeserializeError)
This new variant captures both key, value, and message from named path parameters parse errors,
instead of only deserialization error message in ErrorKind::Message. (#2720)Note: there are further relevant changes in axum-core's changelog
Host extractor to axum-extra (#2956)WebSocket::close.
Users should explicitly send close messages themselves. (#2974)serve generic over the listener and IO types (#2941)Serve::tcp_nodelay and WithGracefulShutdown::tcp_nodelay.
See serve::ListenerExt for an API that let you set arbitrary TCP stream properties. (#2941)Option<Path<T>> and Option<Query<T>> no longer swallow all error conditions,
instead rejecting the request in many cases; see their documentation for details (#2475)serde_json::RawValue in Event::json_data (#2992)content-length before middleware.
This allows middleware to add bodies to requests without needing to manually set content-length (#2897)method_not_allowed_fallback to set a fallback when a path matches but there is no handler for the given HTTP method (#2903)NoContent as a self-described shortcut for StatusCode::NO_CONTENT (#2978)get(ws_endpoint) handlers to any(ws_endpoint) (#2894)MethodFilter::CONNECT, routing::connect[_service]
and MethodRouter::connect[_service] (#2961)FailedToDeserializePathParams::kind enum with (ErrorKind::DeserializeError)
This new variant captures both key, value, and message from named path parameters parse errors,
instead of only deserialization error message in ErrorKind::Message. (#2720)Sync for all handlers and services added to Router
and MethodRouter (#2473)Path extractor deserializers now check that the number of parameters matches the tuple length exactly (#2931)/:single and /*many
to /{single} and /{*many}; the old syntax produces a panic to avoid silent change in behavior (#2645)Nothing published for this version
Nothing published for this version
fixed: Avoid setting content-length before middleware ([#3031])
fixed: Skip SSE incompatible chars of serde_json::RawValue in Event::json_data ([#2992])
serde_json::RawValue in Event::json_data (#2992)method_not_allowed_fallback to set a fallback when a path matches but there is no handler for the given HTTP method (#2903)MethodFilter::CONNECT, routing::connect[_service]
and MethodRouter::connect[_service] (#2961)NoContent as a self-described shortcut for StatusCode::NO_CONTENT (#2978)change: Remove manual tables of content from the documentation, since rustdoc now generates tables of content in the sidebar ([#2921])
change: Avoid cloning Arc during deserialization of Path
Arc during deserialization of Pathaxum::serve::Serve::tcp_nodelay and axum::serve::WithGracefulShutdown::tcp_nodelay (#2653)Router::has_routes function (#2790)Serve::local_addr and WithGracefulShutdown::local_addr functions (#2881)fixed: Fixed layers being cloned when calling axum::serve directly with a Router or MethodRouter ([#2586])
fixed: Fix performance regression present since axum 0.7.0 ([#2483])
added: Body implements From<()> now ([#2411])
Body implements From<()> now (#2411)tracing feature by default (#2460)serve (#2398)RouterIntoService implements Clone (#2456)added: Add axum::to_bytes ([#2373])
- fix: Fix readme.
breaking: Remove deprecated WebSocketUpgrade::max_send_queue
WebSocketUpgrade::max_send_queueB type param has been removed) (#1751 and #1789):
FromRequestPartsFromRequestHandlerServiceHandlerWithoutStateExtHandlerLayeredFutureLayeredMethodRouterNextRequestExtRouteFutureRouteRouterhyper::Body as that type is removed
in hyper 1.0. Instead axum has its own body type at axum::body::Body (#1751)extract::BodyStream has been removed as body::Body
implements Stream and FromRequest directly (#1751)sse::Event::json_data to use axum_core::Error as its error type (#1762)DefaultOnFailedUpdgrade to DefaultOnFailedUpgrade (#1664)OnFailedUpdgrade to OnFailedUpgrade (#1664)TypedHeader has been moved to axum-extra as axum_extra::TypedHeader and requires enabling the typed-header feature on axum-extra. The headers feature has been removed from axum; what it provided under axum::headers is now found in axum_extra::headers by default. (#1850)Empty and Full. Use
axum::body::Body::empty and axum::body::Body::from respectively (#1789)IntoResponse::into_response must use
axum::body::Body as the body type. axum::response::Response does this
(#1789)BoxBody type alias and its box_body
constructor. Use axum::body::Body::new instead (#1789)RawBody extractor. axum::body::Body implements FromRequest directly (#1789)http-body no longer implement IntoResponse:
Full, use Body::from insteadEmpty, use Body::empty insteadBoxBody, use Body::new insteadUnsyncBoxBody, use Body::new insteadMapData, use Body::new insteadMapErr, use Body::new insteadaxum::extract::Request type alias where the body is axum::body::Body (#1789)Router::as_service and Router::into_service to workaround
type inference issues when calling ServiceExt methods on a Router (#1835)axum::Server as it was removed in hyper 1.0. Instead
use axum::serve(listener, service) or hyper/hyper-util for more configuration options (#1868)Router::nest.
Routers nested with Router::nest_service will no longer inherit fallbacks (#1956)Sec-WebSocket-Key header in WebSocketUpgrade (#1972)axum::extract::Query::try_from_uri (#2058)IntoResponse for Box<str> and Box<[u8]> ([#2035])MethodFilter. It no longer uses bitflags (#2073).source() of composite rejections (#2030)#[debug_handler] (#2014)IntoResponse for (R,) where R: IntoResponse (#2143)NestedPath extractor (#1924)handle_error function to existing ServiceExt trait (#2235)impl<T> IntoResponse(Parts) for Extension<T> now requires
T: Clone, as that is required by the http crate (#1882)axum::Json::from_bytes (#2244)FromRequestParts for http::request::Parts (#2328)FromRequestParts for http::Extensions (#2328)DefaultBodyLimit to individual routes (#2157)changed: Deprecate WebSocketUpgrade::max_send_queue
WebSocketUpgrade::write_buffer_size and WebSocketUpgrade::max_write_buffer_sizeWebSocketUpgrade::max_send_queueHandler for T: IntoResponse (#2140)added: Add axum::Query::try_from_uri ([#2058])
axum::extract::Query::try_from_uri (#2058)IntoResponse for Box<str> and Box<[u8]> (#2035).source() of composite rejections (#2030)#[debug_handler] (#2014)fixed: Don't remove the Sec-WebSocket-Key header in WebSocketUpgrade ([#1972])
Sec-WebSocket-Key header in WebSocketUpgrade (#1972)fixed: Fix fallbacks causing a panic on CONNECT requests ([#1958])
CONNECT requests (#1958)fixed: Don't allow extracting MatchedPath in fallbacks ([#1934])
fixed: Removed additional leftover debug messages ([#1927])
fixed: Removed leftover "path_router hit" debug message ([#1925])
added: Log rejections from built-in extractors with the axum::rejection=trace target ([#1890])
axum::rejection=trace target (#1890)Router::nest introduced in
0.6.0. nest now flattens the routes which performs better (#1711)MatchedPath in nested handlers now gives the full
matched path, including the nested path (#1711)Deref and DerefMut for built-in extractors (#1922)added: Implement IntoResponse for MultipartError ([#1861])
fixed: Don't require S: Debug for impl Debug for Router ([#1836])
fixed: Add #[must_use] attributes to types that do nothing unless used ([#1809])
changed: Update to tower-http 0.4. axum is still compatible with tower-http 0.3 ([#1783])
fixed: Fix Allow missing from routers with middleware ([#1773])
added: Add FormRejection::FailedToDeserializeFormBody which is returned if the request body couldn't be deserialized into the target type, as opposed
fixed: Enable passing MethodRouter to Router::fallback ([#1730])
MethodRouter to Router::fallback (#1730)fixed: Fix #[debug_handler] sometimes giving wrong borrow related suggestions ([#1710])
- Depend on axum-macros 0.3.2
added: Implement IntoResponse for &'static [u8; N] and [u8; N] ([#1690])
IntoResponse for &'static [u8; N] and [u8; N] (#1690)Path support types using serde::Deserializer::deserialize_any (#1693)RawPathParams (#1713)Clone and Service for axum::middleware::Next (#1712)added: Add body_text and status methods to built-in rejections ([#1612])
added: Expand the docs for Router::with_state ([#1580])
Router::with_state (#1580)breaking: Remove extractor_middleware which was previously deprecated. Use axum::from_extractor instead ([#1077])
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_router` 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 of foo.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 accepts Handlers (similarly to
what get, post, etc. accept). Use the new Router::fallback_service for
setting any Service 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() {}
breaking: It is no longer supported to nest twice at the same path, i.e.
.nest("/foo", a).nest("/foo", b) will panic. Instead use .nest("/foo", a.merge(b))
breaking: It is no longer supported to nest a router and add a route at
the same path, such as .nest("/a", _).route("/a", _). Instead use
.nest("/a/", _).route("/a", _).
changed: Router::nest now only accepts Routers, the general-purpose
Service nesting method has been renamed to nest_service (#1368)
breaking: Allow Error: Into<Infallible> for Route::{layer, route_layer} (#924)
breaking: MethodRouter now panics on overlapping routes (#1102)
breaking: Router::route now only accepts MethodRouters created with
get, post, etc. Use the new Router::route_service for routing to
any Services (#1155)
breaking: Adding a .route_layer onto a Router or MethodRouter
without any routes will now result in a panic. Previously, this just did
nothing. #1327
breaking: RouterService has been removed since Router now implements
Service when the state is (). Use Router::with_state to provide the
state and get a Router<()>. Note that RouterService only existed in the
pre-releases, not 0.5 (#1552)
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 to State for sharing application state since that is more type
safe and faster. That is done by using Router::with_state and State.
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 won't 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 implement FromRequestParts.
This extractor in 0.5:
struct MyExtractor { /* ... */ }
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},
};
struct MyExtractor { /* ... */ }
// implement `FromRequestParts` if you don't need to consume the request body
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
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 the parse-body-based-on-content-type example.
added: FromRequest and FromRequestParts derive macro re-exports from
axum-macros behind the macros feature (#1352)
added: Add RequestExt and RequestPartsExt which adds convenience
methods for running extractors to http::Request and http::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 for FailedToDeserializeQueryString
rejections, instead of 422 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. Use DefaultBodyLimit instead (#1400)
breaking: RequestParts has been removed as part of the FromRequest
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 defaultbreaking: 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::from_fn functions (#1088)middleware::from_fn_with_state to enable running extractors that require
state (#1342)middleware::from_extractor_with_state (#1396)map_request, map_request_with_state for transforming the
request with an async function (#1408)map_response, map_response_with_state for transforming the
response with an async function (#1414)IntoResponse (#1152)extractor_middleware which was previously deprecated.
Use axum::middleware::from_extractor instead (#1077)Handler::layer to have
Infallible as the error type (#1152)simple-router-wasm example
for more details (#1382)ServiceExt with methods for turning any Service into a
MakeService similarly to Router::into_make_service (#1302)From impls have been added to extract::ws::Message
to be more inline with tungstenite (#1421)#[derive(axum::extract::FromRef)] (#1430)accept_unmasked_frames setting in WebSocketUpgrade (#1529)WebSocketUpgrade::on_failed_upgrade to customize what to do
when upgrading a connection fails (#1539)#[track_caller] so the error
message points to where the user added the invalid route, rather than
somewhere internally in axum (#1248)S: Service, the bounds have been
relaxed so the response type must implement IntoResponse rather than being a
literal Responsetokio default feature needed for WASM support. If you
don't need WASM support but have default_features = false for other reasons
you likely need to re-enable the tokio feature (#1382)handler::{WithState, IntoService} are merged into one type,
named HandlerService (#1418)<details> <summary>0.6.0 Pre-Releases</summary>
breaking: Router::with_state is no longer a constructor. It is instead used to convert the router into a RouterService ([#1532])
breaking: Router::with_state is no longer a constructor. It is instead
used to convert the router into a RouterService (#1532)
This nested router on 0.6.0-rc.4
Router::with_state(state).route(...);
Becomes this in 0.6.0-rc.5
Router::new().route(...).with_state(state);
breaking: Router::inherit_state has been removed. Use
Router::with_state instead (#1532)
breaking: Router::nest and Router::merge now only supports nesting
routers that use the same state type as the router they're being merged into.
Use FromRef for substates (#1532)
added: Add accept_unmasked_frames setting in WebSocketUpgrade (#1529)
fixed: Nested routers will now inherit fallbacks from outer routers (#1521)
added: Add WebSocketUpgrade::on_failed_upgrade to customize what to do
when upgrading a connection fails (#1539)
changed: The inner error of a JsonRejection is now serde_path_to_error::Error . Previously it was serde_json::Error ([#1371])
JsonRejection is now
serde_path_to_error::Error<serde_json::Error>. Previously it was
serde_json::Error (#1371)JsonRejection now displays the path at which a deserialization
error occurred (#1371)ContentLengthLimit (#1389)400 Bad Request for FailedToDeserializeQueryString
rejections, instead of 422 Unprocessable Entity (#1387)middleware::from_extractor_with_state (#1396)DefaultBodyLimit::max for changing the default body limit (#1397)map_request, map_request_with_state for transforming the
request with an async function (#1408)map_response, map_response_with_state for transforming the
response with an async function (#1414)ContentLengthLimit has been removed. Use DefaultBodyLimit instead (#1400)Router no longer implements Service, call .into_service()
on it to obtain a RouterService that does (#1368)Router::inherit_state, which creates a Router with an
arbitrary state type without actually supplying the state; such a Router
can't be turned into a service directly (.into_service() will panic), but
can be nested or merged into a Router with the same state type (#1368)Router::nest now only accepts Routers, the general-purpose
Service nesting method has been renamed to nest_service (#1368)simple-router-wasm example
for more details (#1382)tokio default feature needed for WASM support. If you
don't need WASM support but have default_features = false for other reasons
you likely need to re-enable the tokio feature (#1382)handler::{WithState, IntoService} are merged into one type,
named HandlerService (#1418)Multipart extractor (#1420)From impls have been added to extract::ws::Message
to be more inline with tungstenite (#1421)#[derive(axum::extract::FromRef)] (#1430)FromRequest and FromRequestParts derive macro re-exports from
[axum-macros] behind the macros feature (#1352)MatchedPath can now no longer be extracted in middleware for
nested routes (#1462)extract::RawForm for accessing raw urlencoded query bytes or request body (#1487)FormRejection::FailedToDeserializeQueryString to
FormRejection::FailedToDeserializeForm (#1496)Yanked, as it didn't compile in release mode.
Yanked, as it didn't compile in release mode.
breaking: Added default limit to how much data Bytes::from_request will consume. Previously it would attempt to consume the entire request body withou
breaking: Added default limit to how much data Bytes::from_request will
consume. Previously it would attempt to consume the entire request body
without checking its length. This meant if a malicious peer sent an large (or
infinite) request body your server might run out of memory and crash.
The default limit is at 2 MB and can be disabled by adding the new
DefaultBodyLimit::disable() middleware. See its documentation for more
details.
This also applies to these extractors which used Bytes::from_request
internally:
FormJsonString(#1346)
.route_layer onto a Router or MethodRouter
without any routes will now result in a panic. Previously, this just did
nothing. #1327middleware::from_fn_with_state and
middleware::from_fn_with_state_arc to enable running extractors that require
state (#1342)breaking: Remove extractor_middleware which was previously deprecated. Use axum::from_extractor instead ([#1077])
breaking: Nested Routers will no longer delegate to the outer Router's
fallback. Instead you must explicitly set a fallback on the inner Router (#1086)
This nested router on 0.5:
use axum::{Router, handler::Handler};
let api_routes = Router::new();
let app = Router::new()
.nest("/api", api_routes)
.fallback(fallback.into_service());
async fn fallback() {}
Becomes this in 0.6:
use axum::Router;
let api_routes = Router::new()
// we have to explicitly set the fallback here
// since nested routers no longer delegate to the outer
// router's fallback
.fallback(fallback);
let app = Router::new()
.nest("/api", api_routes)
.fallback(fallback);
async fn 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 of foo.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: 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/). That is no longer supported and such requests will
now be sent to the fallback. Consider using
axum_extra::routing::RouterExt::route_with_tsr if you want the old behavior
(#1119)
For example:
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() {}
breaking: Router::fallback now only accepts Handlers (similarly to
what get, post, etc accept). Use the new Router::fallback_service for
setting any Service 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() {}
breaking: Allow Error: Into<Infallible> for Route::{layer, route_layer} (#924)
breaking: MethodRouter now panics on overlapping routes (#1102)
breaking: Router::route now only accepts MethodRouters created with
get, post, etc. Use the new Router::route_service for routing to
any Services (#1155)
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 to State since that is more type
safe and faster. That is done by using Router::with_state and State.
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::with_state(AppState {})
.route("/", get(handler));
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::with_state(state).route("/", get(handler));
async fn handler(
State(client): State<HttpClient>,
State(database): State<Database>,
) {}
#[derive(Clone)]
struct AppState {
client: HttpClient,
database: Database,
}
#[derive(Clone)]
struct HttpClient {}
impl FromRef<AppState> for HttpClient {
fn from_ref(state: &AppState) -> Self {
state.client.clone()
}
}
#[derive(Clone)]
struct Database {}
impl FromRef<AppState> for Database {
fn from_ref(state: &AppState) -> Self {
state.database.clone()
}
}
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 won't 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 implement FromRequestParts.
This extractor in 0.5:
struct MyExtractor { /* ... */ }
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},
};
struct MyExtractor { /* ... */ }
// implement `FromRequestParts` if you don't need to consume the request body
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
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> {
// ...
}
}
breaking: RequestParts has been removed as part of the FromRequest
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 defaultadded: Add RequestExt and RequestPartsExt which adds convenience
methods for running extractors to http::Request and http::request::Parts (#1301)
extractor_middleware which was previously deprecated.
Use axum::middleware::from_extractor instead (#1077)middleware::from_fn functions (#1088)IntoResponse (#1152)Handler::layer to have
Infallible as the error type (#1152)S: Service, the bounds have been
relaxed so the response type must implement IntoResponse rather than being a
literal Response#[track_caller] so the error
message points to where the user added the invalid route, rather than
somewhere internally in axum (#1248)ServiceExt with methods for turning any Service into a
MakeService similarly to Router::into_make_service (#1302)</details>
Nothing published for this version
breaking: Added default limit to how much data Bytes::from_request will consume. Previously it would attempt to consume the entire request body withou
breaking: Added default limit to how much data Bytes::from_request will
consume. Previously it would attempt to consume the entire request body
without checking its length. This meant if a malicious peer sent an large (or
infinite) request body your server might run out of memory and crash.
The default limit is at 2 MB and can be disabled by adding the new
DefaultBodyLimit::disable() middleware. See its documentation for more
details.
This also applies to these extractors which used Bytes::from_request
internally:
FormJsonString(#1346)
fixed: Don't expose internal type names in QueryRejection response. ([#1171])
Yanked, as it contained an accidental breaking change.
Yanked, as it contained an accidental breaking change.
fixed: If WebSocketUpgrade cannot upgrade the connection it will return a WebSocketUpgradeRejection::ConnectionNotUpgradable rejection ([#1135])
added: Added debug_handler which is an attribute macro that improves type errors when applied to handler function. It is re-exported from axum-macros
debug_handler which is an attribute macro that improves
type errors when applied to handler function. It is re-exported from
axum-macros (#1144)added: Implement TryFrom for MethodFilter and use new NoMatchingMethodFilter error in case of failure ([#1130])
fixed: Make Router cheaper to clone ([#1123])
fixed: Fix compile error when the headers is enabled and the form feature is disabled ([#1107])
headers is enabled and the form
feature is disabled (#1107)added: Support resolving host name via Forwarded header in Host extractor ([#1078])
added: Implement Default for Extension ([#1043])
Improve error messages with #[diagnostic::do_not_recommend] .
Improve error messages with #[diagnostic::do_not_recommend].
WebSocket::protocol to return the selected WebSocket subprotocol, if there is one. (#1022)PathRejection::WrongNumberOfParameters to hint at using
Path<(String, String)> or Path<SomeStruct> (#1023)PathRejection::WrongNumberOfParameters now uses 500 Internal Server Error since
it's a programmer error and not a client error (#1023)InvalidFormContentType mentioning the wrong content typeYour coding agent can read these notes before it upgrades. Set up the MCP server →