You will build a full-stack web app using the rust programming language. We will be building a fully functional CRUD Server API using Then we will build a fully functional frontend in rust using the Yew framework (Yew looks like ReactJS but in Rust). We will then connect the front end and the back end to run from the same port/URL. You will learn how to create Yew components, routing, create forms and handle GET and POST requests using the rust reqwest crate in the front end. This course is good for developers who already know at least one programming language and have some web development experience. It is not a course for folks who are completely new to programming.
Rust is the most loved programming language for the past several years according to StackOverflow. All the big tech companies are now using rust and a lot of startups are using rust. More so, a lot of the newer blockchain projects are building with rust. The rust programming language is growing very rapidly.
This course will help you start developing in rust very fast, as you will build a real full-stack app. You can use the concepts to expand your rust dev options.
The course is divided into 3 main sections. Section 1 is an intro to rust. You can skip this section if you are already familiar with the material. Section 2 will cover the Server API dev using AXUM and section 3 covers developing frontend apps using the popular Yew framework.
Full source code provided at the end of the course.
The videos goes over the agenda and discusses why you may want to consider learning and using rust
use axum::{
routing::{get},Router,
};
use tower_http::cors::{Any, CorsLayer};
#[tokio::main]
async fn main() {
//tracing
tracing_subscriber::fmt::init();
// add cors
let cors = CorsLayer::new()
.allow_origin(Any);
let app = Router::new()
.route("/", get(root))
.layer(cors);
tracing::debug!("listening on {}", "0.0.0.0:3000");
println!(" Listening on port {}" , "0.0.0.0:3000" );
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
// basic handler that responds with a static string
async fn root() -> &'static str {
"Hello, REST AXUM API"
}
Cargo.toml
[package]
name = "frontend"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
yew = { version = "0.20.0", features = ["csr"] }
yew-router = "0.17"
serde = "1.0.152"
serde_json = "1.0.91"
wasm-bindgen-futures = "0.4.33"
reqwest = { version = "0.11.13", features = ["json"] }
web-sys = {version = "0.3.60", features = ["HtmlInputElement"] }
gloo-console = "0.2.3"
below is the final products.rs file
use serde::{Deserialize, Serialize};
use yew::prelude::*;
use yew_router::prelude::*;
use crate::router::Route;
#[derive(Serialize, Deserialize, Debug)]
pub struct Product {
id: i32,
name: String,
price: i32
}
#[function_component]
pub fn Products() -> Html {
let data: UseStateHandle<Vec<Product>> = use_state(|| vec![]);
{
let data_clone = data.clone();
use_effect(move ||{
wasm_bindgen_futures::spawn_local( async move {
let fetched_data = reqwest::get("http://localhost:3000/api/products")
.await
.expect("cannot get data from url")
.json::<Vec<Product>>()
.await
.expect("cannot convert to json");
data_clone.set(fetched_data);
});
|| ()
});
}
let products = data.iter().map(|product| html! {
<ul>
<li key={product.id}>{format!("Name: {}, Price: {}", product.name, product.price)}</li>
</ul>
}).collect::<Html>();
html! {
<div class="container">
<button class="btn-primary">
<Link<Route> to={Route::AddProduct}>{ "Add new Product" }</Link<Route>>
</button>
<h2>{"List of Products: "} {data.len()} </h2>
<p>{products}</p>
</div>
}
}
let products = data.iter().map(|product| html! {
<ul>
<li key={product.id}>{format!("Name: {}, Price: {}", product.name, product.price)}</li>
</ul>
}).collect::<Html>();
===================================
#### Connecting frontend and server ####
create and add to Cargo.toml
[workspace]
members = [
"server", "frontend"
]
#create and add to .gitignore
target/
dist/
.env
create frontend/Trunk.toml and add contents
[build]
target = "index.html"
dist = "../dist"
now you can run: trunk serve
# now go to server/main.rs
axum::response::IntoResponse
axum::http::StatusCode,
use std::io;
//serving frontend static files
let serve_dir = ServeDir::new("../frontend/dist").not_found_service(ServeFile::new("../dist/frontend/index.html"));
let serve_dir = get_service(serve_dir).handle_error(handle_error);
# add to router
.nest_service("/", serve_dir.clone())
.fallback_service(serve_dir.clone());
# add handle error fn
async fn handle_error(_err: io::Error) -> impl IntoResponse {
(StatusCode::INTERNAL_SERVER_ERROR, "Something went wrong...")
}
# change current / to /home
cd into backend: cargo watch -x run
=========================================
final router.rs
use yew_router::prelude::*;
use yew::prelude::*;
use crate::products::Products;
use crate::form::Form;
#[derive(Clone, Routable, PartialEq)]
pub enum Route {
#[at("/")]
Home,
#[at("/addproduct")]
AddProduct,
#[at("/about")]
About,
#[not_found]
#[at("/404")]
NotFound,
}
pub fn switch(routes: Route) -> Html {
match routes {
Route::Home => html! { <Products /> },
Route::AddProduct => html! { <Form />},
Route::About => html! { <h1>{ "About us " }</h1>},
Route::NotFound => html! { <h1>{ "404" }</h1> },
}
}
OpenCourser helps millions of learners each year. People visit us to learn workspace skills, ace their exams, and nurture their curiosity.
Our extensive catalog contains over 50,000 courses and twice as many books. Browse by search, by topic, or even by career interests. We'll match you to the right resources quickly.
Find this site helpful? Tell a friend about us.
We're supported by our community of learners. When you purchase or subscribe to courses and programs or purchase books, we may earn a commission from our partners.
Your purchases help us maintain our catalog and keep our servers humming without ads.
Thank you for supporting OpenCourser.