New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Expand the docs for Router::with_state
#1580
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,236 @@ | ||
Provide the state for the router. | ||
|
||
```rust | ||
use axum::{Router, routing::get, extract::State}; | ||
|
||
#[derive(Clone)] | ||
struct AppState {} | ||
|
||
let routes = Router::new() | ||
.route("/", get(|State(state): State<AppState>| async { | ||
// use state | ||
})) | ||
.with_state(AppState {}); | ||
|
||
# async { | ||
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap()) | ||
.serve(routes.into_make_service()) | ||
.await; | ||
# }; | ||
``` | ||
|
||
# Returning routers with states from functions | ||
|
||
When returning `Router`s from functions it is generally recommend not set the | ||
state directly: | ||
|
||
```rust | ||
use axum::{Router, routing::get, extract::State}; | ||
|
||
#[derive(Clone)] | ||
struct AppState {} | ||
|
||
// Don't call `Router::with_state` here | ||
fn routes() -> Router<AppState> { | ||
Router::new() | ||
.route("/", get(|_: State<AppState>| async {})) | ||
} | ||
|
||
// Instead do it before you run the server | ||
let routes = routes().with_state(AppState {}); | ||
|
||
# async { | ||
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap()) | ||
.serve(routes.into_make_service()) | ||
.await; | ||
# }; | ||
``` | ||
|
||
If you do need to provide the state, and you're _not_ nesting/merging the router | ||
into another router, then return `Router` without any type parameters: | ||
|
||
```rust | ||
# use axum::{Router, routing::get, extract::State}; | ||
# #[derive(Clone)] | ||
# struct AppState {} | ||
# | ||
// Don't return `Router<AppState>` | ||
fn routes(state: AppState) -> Router { | ||
Router::new() | ||
.route("/", get(|_: State<AppState>| async {})) | ||
.with_state(state) | ||
} | ||
|
||
let routes = routes(AppState {}); | ||
|
||
# async { | ||
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap()) | ||
.serve(routes.into_make_service()) | ||
.await; | ||
# }; | ||
``` | ||
|
||
This is because we can only call `Router::into_make_service` on `Router<()>`, | ||
not `Router<AppState>`. See below for more details about why that is. | ||
|
||
Note that the state defaults to `()` so `Router` and `Router<()>` is the same. | ||
|
||
If you are nesting/merging the router it is recommended to use a generic state | ||
type on the resulting router: | ||
|
||
```rust | ||
# use axum::{Router, routing::get, extract::State}; | ||
# #[derive(Clone)] | ||
# struct AppState {} | ||
# | ||
fn routes<S>(state: AppState) -> Router<S> { | ||
Router::new() | ||
.route("/", get(|_: State<AppState>| async {})) | ||
.with_state(state) | ||
} | ||
|
||
let routes = Router::new().nest("/api", routes(AppState {})); | ||
|
||
# async { | ||
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap()) | ||
.serve(routes.into_make_service()) | ||
.await; | ||
# }; | ||
``` | ||
|
||
# What `S` in `Router<S>` means | ||
|
||
`Router<S>` means a router that is _missing_ a state of type `S` to be able to | ||
handle requests. It does _not_ mean a `Router` that _has_ a state of type `S`. | ||
|
||
For example: | ||
|
||
```rust | ||
# use axum::{Router, routing::get, extract::State}; | ||
# #[derive(Clone)] | ||
# struct AppState {} | ||
# | ||
// A router that _needs_ an `AppState` to handle requests | ||
let router: Router<AppState> = Router::new() | ||
.route("/", get(|_: State<AppState>| async {})); | ||
|
||
// Once we call `Router::with_state` the router isn't missing | ||
// the state anymore, because we just provided it | ||
// | ||
// Therefore the router type becomes `Router<()>`, i.e a router | ||
// that is not missing any state | ||
let router: Router<()> = router.with_state(AppState {}); | ||
|
||
// Only `Router<()>` has the `into_make_service` method. | ||
// | ||
// You cannot call `into_make_service` on a `Router<AppState>` | ||
// because it is still missing an `AppState`. | ||
# async { | ||
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap()) | ||
.serve(router.into_make_service()) | ||
.await; | ||
# }; | ||
``` | ||
|
||
Perhaps a little counter intuitively, `Router::with_state` doesn't always return a | ||
`Router<()>`. Instead you get to pick what the new missing state type is: | ||
|
||
```rust | ||
# use axum::{Router, routing::get, extract::State}; | ||
# #[derive(Clone)] | ||
# struct AppState {} | ||
# | ||
let router: Router<AppState> = Router::new() | ||
.route("/", get(|_: State<AppState>| async {})); | ||
|
||
// When we call `with_state` we're able to pick what the next missing state type is. | ||
// Here we pick `String`. | ||
let string_router: Router<String> = router.with_state(AppState {}); | ||
|
||
// That allows us to add new routes that uses `String` as the state type | ||
let string_router = string_router | ||
.route("/needs-string", get(|_: State<String>| async {})); | ||
|
||
// Provide the `String` and choose `()` as the new missing state. | ||
let final_router: Router<()> = string_router.with_state("foo".to_owned()); | ||
|
||
// Since we have a `Router<()>` we can run it. | ||
# async { | ||
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap()) | ||
.serve(final_router.into_make_service()) | ||
.await; | ||
# }; | ||
``` | ||
|
||
This why this returning `Router<AppState>` after calling `with_state` doesn't | ||
work: | ||
|
||
```rust,compile_fail | ||
# use axum::{Router, routing::get, extract::State}; | ||
# #[derive(Clone)] | ||
# struct AppState {} | ||
# | ||
// This wont work because we're returning a `Router<AppState>` | ||
// i.e. we're saying we're still missing an `AppState` | ||
fn routes(state: AppState) -> Router<AppState> { | ||
Router::new() | ||
.route("/", get(|_: State<AppState>| async {})) | ||
.with_state(state) | ||
} | ||
|
||
let app = routes(AppState {}); | ||
|
||
// We can only call `Router::into_make_service` on a `Router<()>` | ||
// but `app` is a `Router<AppState>` | ||
# async { | ||
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap()) | ||
.serve(app.into_make_service()) | ||
.await; | ||
# }; | ||
``` | ||
|
||
Instead return `Router<()>` since we have provided all the state needed: | ||
|
||
```rust | ||
# use axum::{Router, routing::get, extract::State}; | ||
# #[derive(Clone)] | ||
# struct AppState {} | ||
# | ||
// We've provided all the state necessary so return `Router<()>` | ||
fn routes(state: AppState) -> Router<()> { | ||
Router::new() | ||
.route("/", get(|_: State<AppState>| async {})) | ||
.with_state(state) | ||
} | ||
|
||
let app = routes(AppState {}); | ||
|
||
// We can now call `Router::into_make_service` | ||
# async { | ||
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap()) | ||
.serve(app.into_make_service()) | ||
.await; | ||
# }; | ||
``` | ||
|
||
# A note about performance | ||
|
||
If you need a `Router` that implements `Service` but you don't need any state (perhaps | ||
you're making a library that uses axum internally) then it is recommended to call this | ||
method before you start serving requests: | ||
|
||
```rust | ||
use axum::{Router, routing::get}; | ||
|
||
let app = Router::new() | ||
.route("/", get(|| async { /* ... */ })) | ||
// even though we don't need any state, call `with_state(())` anyway | ||
.with_state(()); | ||
# let _: Router = app; | ||
``` | ||
|
||
This is not required but it gives axum a chance to update some internals in the router | ||
which may impact performance and reduce allocations. | ||
|
||
Note that [`Router::into_make_service`] and [`Router::into_make_service_with_connect_info`] | ||
do this automatically. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The link on this didn't work for some reason but I don't actually think we need it since the macro is exported at the same path as
FromRef
itself.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, there were no square brackets around the code block.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even with I couldn't get it working 🤷