Cover Photo by Agnis Leznins on Unsplash
With a working API layer under our belt, it's time to put in the final piece of the puzzle: the presentation layer. This layer consists of static resources (HTML, CSS, JavaScript) and API calls to display and add confessions.
✏️ As this tutorial is not frontend focused, I wanted to keep things simple and use vanilla JavaScript, HTML and CSS. I will also not go into too much detail with why or how these are implemented, as we are not really here for frontend stuff, are we? 😎
Working With Templates
When we serve index.html back to the user, we want it to have a confession already so the user won’t load the page and then wait for the first API call to return the confession. That basically means that our application needs to parse the index.html static file, find the right place to inject a confession, and then inject the content in the correct place after fetching a random confession from the database. Now that might work if you have one item to inject, but what if you have two or ten?
That's where templates come into play. Templates allow us to have a static HTML that contains placeholders (aka variables) that the templating engine will replace with the content of our choice. In addition, templates allow us to use tags to control the template logic (if statements, for example). However, we will not be dealing with those in this tutorial.
For Late Night Confessions, I chose Askama as the templating engine because of its easy integration and simple syntax. Let’s create a template for our index.html file (trust me, it's easier than you think):
- In the project root folder, create a new folder called templates.
- Inside the templates folder, create a new file called index.html with the following content:
The vast majority of this file is plain old HTML syntax, take a glance at lines 32 and 47— These are Askama variables:
- confession (line 32) — This variable will be replaced by an actual confession from the database.
- total_confessions (line 47) — This variable will be replaced by the total number of confessions in our database.
And that's basically that for making our HTML an Askama template. Let’s quickly add the styles.css and confessions.js files to the site/static folder and get back to Rust to integrate Askama:
A few points of interest in our JavaScript:
- The refreshCard function makes a call to our GET API to get a new random confession and update the UI.
- The event listener for click validates the text area (so it is not empty, somewhat naive, I know, but 🤷♂️), and then if all is well, makes a call to our POST API with the new confession. If everything checks out (line 43), we alert the user everything is fine and update the footer note with the updated saved confessions number.
- We listen to the animationend event to update the displayed confession once the timer runs off (lines 53–55).
Working With Askama
- Back in our Rust project, let’s add Askama to the Cargo.toml file:
- As mentioned earlier, Askama works with templates, so the first thing we need to do is tell Askama where our template is and which variables it's going to have:
🔬 So what do we have here?
- Line 3 : We derive the Template trait, which is the main trait Askama uses. It includes template related methods such as render.
- Line 4 : We use the template attribute to specify the path to our template. The path is relative to the templates folder in the project root folder. Other than path you can also specify a source (directly set the template, without a template file) or enable debug using the print sub-attribute. Read more about the different uses of the template attribute on the official docs.
- Lines 5–8 : We define the fields the struct will hold, which are identical to the variables we defined in the template itself.
- We now have two functions that need to get a random confession from the database: get_confession (the handler for the GET API) and root (the handler that renders index.html ). To avoid code duplication, let’s extract get_confession’s confession fetching logic to a new function, get_random_confession:
Notice that our new function takes a conn variable of type &PgConnection as argument and return a Result of either a Confession (if successful) or diesel’s error type (if there was a database-related error).
- With the get_random_confession function in place, let’s update the get_confession handler to use the new function as its closure:
- Lastly, let’s update the root handler to render the template using Askama and set it as the response:
🔬 So what do we have here?
- Line 2 : We changed the return type to Html for success and our CustomError in case of an error.
- Line 3 : We get a random confession from the database using the new get_random_confession function, just like we are doing for the API call to /api/confession.
- Lines 4–6 : We query Postgres for the number of confessions we have saved on it.
- Lines 8–11 : We initiate the HomepageTemplate struct (a.k.a our template definition) with the data from Postgres.
- Lines 13–14 : This is where the magic happens. Askama turns our template into an HTML string (line 13), which we return as the handler response (line 14).
And that's basically it! Go ahead and run cargo run and browse to localhost:8000. You should get something similar to the following:
Summary
Our site creation journey has come to its end, but take a look back at everything you achieved! You now have a web app that is able to handle static files and API requests, uses a Postgres instance for persisting data and takes advantage of templates for quicker and easier HTML manipulation.
I would love to read your comments on this mini-post series over on Twitter, and please join me on my next Rust post series: “Let’s Build a Fitness Tracking Webapp With Rust and Yew”.
Credit Where Credit’s Due
- Thank you, Carla Notarobot, for the starry night CSS animation background!
- Thank you, Traf, for the progress bar CSS animation!
Top comments (1)
Thanks for writing this series!
I believe the gist meant for
index.html
is pointing atmain.rs
.