Files
dioxus/examples/07-fullstack/server_state.rs
T
Jonathan Kelley 7102bc3b6a more fullstack cleanups: loaders serialize, ServeConfig only (#4785)
* some opinionated cleanups in dioxus-server

* add serialize to loader

* add `result` to Resource

* clippy

* fix

* writable memos, resources, `onmount`, `.pending`, and `.value()` on actions

* fix compile issues

* fix typo
2025-10-14 17:34:52 -07:00

121 lines
3.7 KiB
Rust

//! This example shows how to use global state to maintain state between server functions.
use dioxus::prelude::*;
#[cfg(feature = "server")]
use {
dioxus::fullstack::Lazy,
dioxus::fullstack::axum,
futures::lock::Mutex,
sqlx::{Executor, Row},
std::sync::LazyLock,
};
/*
Option 1:
For simple, synchronous, thread-safe data, we can use statics with atomic types or mutexes.
The `LazyLock` type from the standard library is a great choice for simple, synchronous data
*/
#[cfg(feature = "server")]
static MESSAGES: LazyLock<Mutex<Vec<String>>> = LazyLock::new(|| Mutex::new(Vec::new()));
#[post("/api/messages")]
async fn add_message() -> Result<()> {
MESSAGES.lock().await.push("New message".to_string());
Ok(())
}
#[get("/api/messages")]
async fn read_messages() -> Result<Vec<String>> {
Ok(MESSAGES.lock().await.clone())
}
/*
Option 2:
For complex async data, we can use the `Lazy` type from Dioxus Fullstack. The `Lazy` type provides
an interface like `once_cell::Lazy` but supports async initialization. When reading the value from
a `Lazy<T>`, the value will be initialized synchronously, blocking the current task until the value is ready.
Alternatively, you can create a `Lazy<T>` with `Lazy::lazy` and then initialize it later with
`Lazy::initialize`.
*/
#[cfg(feature = "server")]
static DATABASE: Lazy<sqlx::SqlitePool> = Lazy::new(|| async move {
use sqlx::sqlite::SqlitePoolOptions;
dioxus::Ok(
SqlitePoolOptions::new()
.max_connections(5)
.connect_with("sqlite::memory:".parse().unwrap())
.await?,
)
});
/// When using the `Lazy<T>` type, it implements `Deref<Target = T>`, so you can use it like a normal reference.
#[get("/api/users")]
async fn get_users() -> Result<Vec<String>> {
let users = DATABASE
.fetch_all(sqlx::query("SELECT name FROM users"))
.await?
.iter()
.map(|row| row.get::<String, _>("name"))
.collect::<Vec<_>>();
Ok(users)
}
/*
Option 3:
For data that needs to be provided per-request, we can use axum's `Extension` type to provide
data to our app. This is useful for things like request-scoped data or data that needs to be
initialized per-requestz
*/
#[cfg(feature = "server")]
type BroadcastExtension = axum::Extension<tokio::sync::broadcast::Sender<String>>;
#[post("/api/broadcast", ext: BroadcastExtension)]
async fn broadcast_message() -> Result<()> {
ext.send("New broadcast message".to_string())?;
Ok(())
}
fn main() {
#[cfg(not(feature = "server"))]
dioxus::launch(app);
// When using `Lazy` items, or axum `Extension`s, we need to initialize them in `dioxus::serve`
// before launching our app.
#[cfg(feature = "server")]
dioxus::serve(|| async move {
use dioxus::server::axum::Extension;
// For axum `Extension`s, we can use the `layer` method to add them to our router.
let router = dioxus::server::router(app)
.layer(Extension(tokio::sync::broadcast::channel::<String>(16).0));
Ok(router)
});
}
fn app() -> Element {
let mut users = use_action(get_users);
let mut messages = use_action(read_messages);
let mut broadcast = use_action(broadcast_message);
let mut add = use_action(add_message);
rsx! {
div {
button { onclick: move |_| users.call(), "Get Users" }
pre { "{users.value():?}" }
button { onclick: move |_| messages.call(), "Get Messages" }
pre { "{messages.value():?}" }
button { onclick: move |_| broadcast.call(), "Broadcast Message" }
pre { "{broadcast.value():?}" }
button { onclick: move |_| add.call(), "Add Message" }
pre { "{add.value():?}" }
}
}
}