HTML Templating (Optional)

1. Welcome and Icebreaker:

Welcome! Today, we’re diving into server-side HTML templating in Rust with Axum.

Icebreaker: Have you ever used a server-rendered website? What did you like or dislike about it? Share your experience!

2. Outline the Session:

Here’s what we’ll cover in this session:

  1. Concept Explanation: What is HTML templating? Why and how do we use it in Axum?
  2. Live Coding Demonstration: Render HTML using a template engine (Askama and Tera).
  3. Tasks: Practice by building your own HTML endpoint and experimenting with templates.

Learning Outcomes:

  • Understand the basics of server-side HTML templating in Rust.
  • Integrate a template engine (Askama or Tera) with Axum.
  • Pass dynamic data from handlers to templates.
  • Compare compile-time and runtime template checking.

Expectations:

  • Ask questions and try rendering dynamic HTML with your own data!
  • Experiment with both Askama and Tera for a broader perspective.

3. Concept Explanation:

HTML templating allows you to generate dynamic HTML on the server, making it possible to serve personalized or data-driven pages. In Rust, popular template engines include:

  • Askama: Compile-time checked templates (safer, errors caught at compile time).
  • Tera: Runtime checked templates (more flexible, but errors may appear at runtime).

Why use templating in Axum?

  • Cleanly separate your Rust logic from HTML presentation.
  • Easily render dynamic content (e.g., user profiles, lists, dashboards).
  • Reuse templates for consistent layouts.

How does it work?

  • Define a template file (e.g., hello.html).
  • Create a Rust struct representing the data to inject.
  • Render the template in your handler, passing in the data.

4. Live Coding Demonstration:

Add askama = "0.16" alongside Axum 0.8 and Tokio 1. This manual rendering pattern needs no integration crate; alternatively see the Askama lessons for askama_web.

use askama::Template;
use axum::{response::Html, http::StatusCode, Router, routing::get};

#[derive(Template)]
#[template(path = "hello.html")]
struct HelloTemplate<'a> {
    name: &'a str,
}

async fn hello() -> Result<Html<String>, StatusCode> {
    let template = HelloTemplate { name: "Axum" };
    template.render().map(Html).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
}

#[tokio::main]
async fn main() {
    let app = Router::new().route("/hello", get(hello));
    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

hello.html (in your templates/ folder):

<!DOCTYPE html>
<html>
  <head><title>Hello</title></head>
  <body>
    <h1>Hello, {{ name }}!</h1>
  </body>
</html>

Step-by-step:

  1. Add askama and axum to your Cargo.toml.
  2. Create a templates/hello.html file.
  3. Define a struct with #[derive(Template)] and a template attribute.
  4. Render the template in your handler and return Result<Html<String>, StatusCode> so render failures do not panic.

Best Practices:

  • Use compile-time checked templates for safety (Askama).
  • Keep templates simple and focused on presentation.
  • Organize templates in a dedicated folder (e.g., templates/).

Common Pitfalls:

  • Forgetting to add template files to your project (they must be present at compile time for Askama).
  • Not handling template rendering errors (use proper error handling in production).
  • Mixing too much logic into templates—keep logic in Rust, presentation in HTML.

Tera Example: Tera is another popular engine. Its usage is similar, but templates are checked at runtime. Try both to see which fits your workflow!

5. Tasks

  1. Create a List Template:
    • Make a template that displays a list of items (e.g., a shopping list or todo list).
    • Pass a vector of items from your handler to the template.
  2. Dynamic Data:
    • Modify your handler to accept a query parameter (e.g., ?name=YourName) and display it in the template.
  3. Try Tera:
    • Replace Askama with Tera in your project. Render the same data using Tera templates. Compare the developer experience and error handling.

Bonus:

  • Add error handling for template rendering.
  • Experiment with template inheritance (layouts) if your engine supports it.

Happy coding!