loading...

A Web App in Rust - 02 Templates

krowemoh profile image Nivethan ・3 min read

Welcome back! At this point we have a very bare web application written in rust. All it does so far is respond to one request with some plain text. In this chapter we will add support for templating.

But first lets make our life easier and instead having to do cargo run after each modification, we'll have it automatically recompile and run. To do this we will install cargo-watch.

> cargo install cargo-watch
Enter fullscreen mode Exit fullscreen mode

Then to run it.

> cd hacker-clone/
hacker-clone> cargo watch -x run
Enter fullscreen mode Exit fullscreen mode

Now we can make modifications to our application without having to kill our server each time.

Tera

The templating engine we will be using is Tera.

You can find more information here:
https://tera.netlify.app/docs/

This engine uses syntax in the vein of jinga2 and for the most part is intuitive.

The first step to using Tera is to add it to our dependencies in Cargo.toml.

./Cargo.toml

[dependencies]
actix-web = "3"
tera = "1.5.0" 
Enter fullscreen mode Exit fullscreen mode

Next we will create a folder to hold our templates.

mkdir templates
Enter fullscreen mode Exit fullscreen mode

We now have a folder called templates sitting next out src which contains our rust files.

Now we'll create a very basic index page just to get some data on the screen. Once we do that we'll make it a little bit more complex by adding some loops and conditionals.

For now though, the below will be fine:

templates/index.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>{{title}}</title>
    </head>
    <body>
        Hello, {{name}}!
    </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Everything in the curly brackets will get processed by the Tera templating engine and the real values will get substituted in.

Now we will move back to Rust and get our web application actually using this template.

src/main.rs

use actix_web::{HttpServer, App, web, HttpResponse, Responder};
use tera::{Tera, Context};

async fn index(tera: web::Data<Tera>) -> impl Responder {
    let mut data = Context::new();
    data.insert("title", "Hacker Clone");
    data.insert("name","Nivethan");

    let rendered = tera.render("index.html", &data).unwrap();
    HttpResponse::Ok().body(rendered)
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        let tera = Tera::new("templates/**/*").unwrap();
        App::new()
            .data(tera)
            .route("/", web::get().to(index))
    })
    .bind("127.0.0.1:8000")?
    .run()
    .await
}
Enter fullscreen mode Exit fullscreen mode

The first thing to notice is that we removed the anonymous function in the index route for a named function. When we implement this new function, index, we'll need to also add in the Responder portion of actix so that will also get added to our includes.

The next thing is to see that we set up tera. We create a new instance of Tera with the path to the template directory. We may have a second level within our templates so that is what the ** is for. This will give us a Tera object that we can use to render templates.

We use the unwrap function because if tera fails for whatever reason, our entire application would be moot so panicking would be the best bet. Had we wanted to handle the error gracefully we could use unwrap_or_else or we can do the match construct. In our case plain unwrap is fine. Our index function should however error gracefully but we'll do with that later on, for now we'll use unwrap because it'll be the easiest and quickest way to know when something goes wrong.

Next we register the tera object into our App with the use of the .data method. This way any functions we run in our App will always have access to tera.

In our index function we can access tera by passing it in via the function parameters with a type of web::Data.

At this point we have set everything up for tera to be used in the actual route handler function.

In the index function we start off by building a key value object called data with the constructor being Context. We then insert in the name value pairs of title and name. We then run our renderer saying which file we want to render and its associated data. All the tera.render is doing is processing the template along with the data to generate HTML. This HTML string is then sent back as a response to the browser.

And voila! We can go to browser now (cargo-watch should have restarted our server on each change we made) to 127.0.0.1:8000 and we should see "Hello, Nivethan!" and the title of the page should be "Hacker Clone".

Next we'll add some serialization so that we can get entire objects and arrays onto our growing website. Onward and forward!

Discussion

pic
Editor guide