Building Pages
One way to build up an application is to think of it in terms of pages.
Later we can split the pages into re-usable components but let's start with a simple page.
The Dioxus framework gives us the capability to build user interfaces out of components which can be rendered server side. It's worth looking at their components documentation.
I looked at lots of Rust markup and component libraries. Dioxus stood out as having a lightweight syntax and the ability to create components in a similar way to React.
Creating a web-pages crate
cargo init --lib crates/web-pages
Install Dioxus
cd crates/web-pages
cargo add [email protected]
cargo add [email protected]
Creating a Layout Component
A layout defines the surroundings of an HTML page. It's the place to define a common look and feel of your final output.
create a file called crates/web-pages/src/layout.rs
.
#![allow(non_snake_case)]
use dioxus::prelude::*;
#[component]
pub fn Layout(title: String, children: Element) -> Element {
rsx!(
head {
title {
"{title}"
}
meta {
charset: "utf-8"
}
meta {
"http-equiv": "X-UA-Compatible",
content: "IE=edge"
}
meta {
name: "viewport",
content: "width=device-width, initial-scale=1"
}
}
body {
{children}
}
)
}
Let's use this layout to create a very simple users screen that will show a table of users.
Make sure you're in the crates/web-pages
folder and add the db
crate to your Cargo.toml
using the following command:
cargo add --path ../db
Create a file crates/web-pages/src/users.rs
.
use crate::layout::Layout;
use db::User;
use dioxus::prelude::*;
use dioxus::prelude::component;
#[derive(Props, Clone, PartialEq)] pub struct IndexPageProps {
pub users: Vec<User>,
}
#[component]
pub fn IndexPage(props: IndexPageProps) -> Element {
rsx! {
Layout { title: "Users Table",
table {
thead {
tr {
th { "ID" }
th { "Email" }
}
}
tbody {
for user in props.users {
tr {
td {
strong {
"{user.id}"
}
}
td {
"{user.email}"
}
}
}
}
}
}
}
}
If we update our crates/web-pages/src/lib.rs
to look like the following...
mod layout;
pub mod users;
use dioxus::prelude::*;
pub fn render(mut virtual_dom: VirtualDom) -> String {
virtual_dom.rebuild_in_place();
let html = dioxus_ssr::render(&virtual_dom);
format!("<!DOCTYPE html><html lang='en'>{}</html>", html)
}
Then finally we can change our web-server
code to generate HTML rather than JSON.
Make sure you're in the crates/web-server
folder and add the web-pages
crate to your Cargo.toml
using the following command:
cargo add --path ../web-pages
Update crates/web-server/src/main.rs
mod config;
mod errors;
use crate::errors::CustomError;
use axum::response::Html;
use axum::{extract::Extension, routing::get, Router};
use dioxus::dioxus_core::VirtualDom;
use std::net::SocketAddr;
use web_pages::{
render,
users::{IndexPage, IndexPageProps},
};
#[tokio::main]
async fn main() {
let config = config::Config::new();
let pool = db::create_pool(&config.database_url);
let app = Router::new()
.route("/", get(users))
.layer(Extension(config))
.layer(Extension(pool.clone()));
let addr = SocketAddr::from(([0, 0, 0, 0], 3000));
println!("listening on... {}", addr);
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
axum::serve(listener, app.into_make_service())
.await
.unwrap();
}
pub async fn users(Extension(pool): Extension<db::Pool>) -> Result<Html<String>, CustomError> {
let client = pool.get().await?;
let users = db::queries::users::get_users().bind(&client).all().await?;
let html = render(VirtualDom::new_with_props(
IndexPage,
IndexPageProps { users },
));
Ok(Html(html))
}
You should get results like the screenshot below.