30 days of Rust - Day Twenty Five - Blog Project - Routing
Yesterday, I created the home page of my website using Rust and Zero Javascript. I got a couple of questions from people.
- Is this indeed the end of Javascript and React?
- Is this just for very basic applications?
The answer to both these questions is No. Javascript still has a huge ecosystem and will still remain relevant for it has a bigger community and libraries which allow for teams to ship faster.
Secondly, I think we can actually build some really robust production ready web apps with Rust. For both the front and backend. Today I saw how powerful Yew is for front end development.
Day 25 - Routing between pages
Up till now, my webapp only has one page. This can be limiting if we are going to build a complex application which needs multiple pages such as the contact page, projects page as well as the blog post page to show hundreds of different posts.
The Router takes care of this, by displaying a certain page depending on the URL we are visiting in our browser. For this, I will use the Yew Router.
Installing the router
In order to install, we need to add the following dependency to our Cargo.toml
file.
yew-router = "0.16"
I then created a routes module src/routes/mod.rs
. In the file, I then added a Route
enum which has the URL
path for each value. This is used together with the switch
which returns a specific page depending on the route we're on. If we try to visit a page that does not exist, our match falls back to #[at("/404")]
, which shows that the page is not found.
use yew_router::prelude::*;
#[derive( Clone, Routable, PartialEq, Debug)]
pub enum Route {
#[at("/")]
Home,
#[at("/projects")]
Projects,
#[at("/blog")]
Blog,
#[at("/contact")]
Contact,
#[at("/post/:id")]
Post {id: String},
#[not_found]
#[at("/404")]
NotFound,
}
pub fn switch(routes: &Route) -> Html{
match routes {
Route::Home => html! {<Home />},
Route::Blog => html! {<Blog />},
Route::Projects => html! {
<Projects />
},
Route::Contact => html! {<Contact />},
Route::Post {id} => html! {
<Post id={id.clone()}/>
},
Route::NotFound => html! {<FourOhFour />}
}
}
Notice how Post
is different from the other routes. This is because we need it to show different information for each and every blog depending on the id
of the blog. So if you go to my website and check out the blogs, you will see a common pattern in the url.
Let's break it down;
https://www.masaimahapa.co.za/posts/30-days-of-rust-day-24 is basically https://www.masaimahapa.co.za/posts/{id}
So each time you visit the posts, a query is made to the server to get a post with a certain id
that you provided. If it is found, it then returns the blog post. If not we get an error that no such post is found.
When we navigate to http://localhost:8080/post/rust-is-fun
, we see the following page for now;
The Switch
component, takes a 2 parameters;
-
Routes
, which we have created above as an enum. -
Switch function as an argument to
render
.
#[function_component(App)]
pub fn app() -> Html {
html! {
<BrowserRouter>
<Switch <routes::Route> render={Switch::render(routes::switch)} />
</BrowserRouter>
}
}
The Switch is then wrapped by BrowserRouter
component which shall handle all the requests using the routes we provided.
Folder Structure
I also noticed that all the different components and pages were getting cluttered and it was like a huge bowl of spaghetti. I then decided to do some separation of concerns by moving components into their own module and each component having its own file. It now looks like this;
Layout Component
All the pages on my website have a navigation bar at the top as well as a footer at the bottom. So now I have to repeat the code on each and every page. Which is bad. Remember the DRY
principle. Don't repeat yourself.
I then created a component called Layout
to solve this problem. It contains the navigation bar and footer. I wrap around all the pages with it. It looks like this;
use yew::prelude::*;
use crate::components::{footer::Footer,
navbar::NavBar
};
#[derive(Properties, PartialEq)]
pub struct LayoutProps {
pub children: Children,
}
#[function_component(Layout)]
pub fn layout(props: &LayoutProps) -> Html {
html! {
<>
<NavBar />
{for props.children.iter()}
<Footer />
</>
}
}
The new thing here is that there is a struct called LayoutProps
. It only has one field name children
. This struct is used to accept other components named children
. In our case, they are pages. So between the <NavBar>
and <Footer>
there is a for loop which basically returns each element inside the children
property.
It is then used as such;
#[function_component(Contact)]
pub fn contact() -> Html {
html! {
<>
<Layout >
<h1>{"Contact me"}</h1>
// contact masai on this beautiful page
</Layout>
</>
}
}
Conclusion
This Blog website is taking shape quite fast I must say. Today was quite an amazing day and I have learnt a lot about how to move around between different pages.
Rust certainly provides a very familiar feel compared to React.js and anyone who has used React before will definitely feel at home. I don't think there are any limitations to using Rust as yet for most applications.
Thanks for spending some time learning Rust with me. Until next time. Peace ✌🏼 .