Masai Mahapa

30 days of Rust - Day Twenty Five - Blog Project - Routing

30 days of rust - day Twenty Five 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; post id

The Switch component, takes a 2 parameters;

  1. Routes, which we have created above as an enum.

  2. 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;

30 days of rust - blog masai

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 ✌🏼 .

Share