Advanced Askama: Custom Filters, Error Handling & Web Integration

Learning Objectives: By the end of this lesson, you will be able to:

  • Write and use custom filters in Askama templates
  • Handle template rendering errors gracefully
  • Integrate Askama with a web framework (Axum)

1. Welcome and Introduction (5 minutes)

Icebreaker Activity

Have you ever needed to format data in a special way in your templates, or wondered how to handle errors when rendering HTML? What about connecting your templates to a real web server?

Lesson Overview

  • Duration: 75-90 minutes
  • Prerequisites: Day 1-3 lessons (Askama setup, control flow, reusability)
  • Materials Needed:
    • Rust toolchain
    • Code editor
    • Askama project (preferably with Axum for web integration)

2. Learning Objectives and Session Outline (5 minutes)

What We’ll Cover Today:

  1. Writing custom filters with #[askama::filter_fn]
  2. Error handling in Askama
  3. Integrating Askama with Axum (or another web framework)
  4. Hands-on practice and exercises
  5. Common pitfalls and troubleshooting

Success Criteria:

By the end of this session, you should be able to extend Askama with custom logic, handle errors, and serve templates in a web app.

3. Foundation Knowledge (15-20 minutes)

Core Concepts

Custom Filters

Filters let you transform data in your templates. Askama comes with many built-in filters, but you can write your own in Rust for custom formatting.

Real-World Analogy: Like using a kitchen tool to slice or dice your data before serving it on a plate (the template).

Why This Matters:

  • Format data exactly as you need
  • Reuse formatting logic across templates

Error Handling

Template typos fail at compile time; runtime errors can come from custom filters. The to_kebab_case filter above is infallible, so its Err branch is only defensive. See the failing shout filter example below for an actual render failure. Askama’s render() returns a Result, so you can handle errors gracefully instead of panicking.

Web Integration

Askama integrates with web frameworks like Axum, making it easy to return rendered templates as HTTP responses.

Key Terminology

  • Custom Filter: A Rust function used in templates to transform data
  • Error Handling: Managing failures in rendering or data
  • Integration: Connecting Askama to a web server

4. Detailed Explanation and Examples (25-30 minutes)

Understanding Advanced Askama Features

Writing a Custom Filter

// src/filters.rs
use askama::Result;

#[askama::filter_fn]
pub fn to_kebab_case(s: impl std::fmt::Display, _values: &dyn askama::Values) -> Result<String> {
    let s = s.to_string();
    Ok(s.replace(" ", "-").to_lowercase())
}
// main.rs
mod filters;
use askama::Template;

#[derive(Template)]
#[template(path = "kebab.html")]
struct KebabTemplate {
    phrase: String,
}
<!-- templates/kebab.html -->
<p>{{ phrase|to_kebab_case }}</p>

Error Handling

fn main() {
    let t = KebabTemplate { phrase: "Hello World".to_string() };
    match t.render() {
        Ok(html) => println!("{}", html),
        Err(e) => eprintln!("Render error: {}", e),
    }
}

Integrating with Axum

// Cargo.toml [dependencies]:
// askama = "0.16"
// askama_web = { version = "0.16", features = ["axum-0.8"] }
// axum = "0.8"
// tokio = { version = "1", features = ["full"] }

use askama::Template;
use askama_web::WebTemplate;
use axum::{Router, routing::get};

#[derive(Template, WebTemplate)]
#[template(path = "hello.html")]
struct HelloTemplate {
    name: String,
}

async fn hello() -> HelloTemplate {
    HelloTemplate { name: "Web".to_string() }
}

#[tokio::main]
async fn main() {
    let app = Router::new().route("/", get(hello));
    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}
<!-- templates/hello.html -->
<!DOCTYPE html>
<html>
  <head><title>Hello</title></head>
  <body>
    <h1>Hello, {{ name }}!</h1>
  </body>
</html>

Key Points:

  • WebTemplate implements IntoResponse for the template; it returns HTML with the proper content type and a 500 response on rendering failure. Log failures server-side without leaking details.
  • The template is rendered and sent as an HTTP response.
  • You can add more routes and templates for a multi-page app.

Serving Static Files

You can use tower_http::services::ServeDir (the fs feature) alongside templates, for example with Router::nest_service("/static", ServeDir::new("static")).

Whitespace Control and Configuration

  • Use {%- ... -%} to trim whitespace
  • Configure Askama with askama.toml for template directories, etc.

Common Misconceptions

  1. Misconception: “Custom filters can do anything.” Reality: Filters must follow Askama’s signature and error handling rules.
  2. Misconception: “Templates always panic on error.” Reality: You can handle errors with Result.

5. Live Coding Demonstration (20-25 minutes)

Example 1: Custom Filter in Action

// src/filters.rs
#[askama::filter_fn]
pub fn shout(s: impl std::fmt::Display, _values: &dyn askama::Values) -> askama::Result<String> {
    let text = s.to_string();
    if text.trim().is_empty() {
        return Err(askama::Error::custom(std::io::Error::new(
            std::io::ErrorKind::InvalidInput, "message must not be empty",
        )));
    }
    Ok(text.to_uppercase() + "!")
}
// main.rs
mod filters;
use askama::Template;

#[derive(Template)]
#[template(path = "shout.html")]
struct ShoutTemplate {
    message: String,
}

fn main() {
    let t = ShoutTemplate { message: "hello".to_string() };
    println!("{}", t.render().unwrap());
}
<!-- templates/shout.html -->
<p>{{ message|shout }}</p>

Example 2: Error Handling

Replace Example 1’s main with this version; the filter rejects empty input at render time:

fn main() {
    let t = ShoutTemplate { message: "".to_string() };
    match t.render() {
        Ok(html) => println!("{}", html),
        Err(e) => eprintln!("Error rendering template: {}", e),
    }
}

Example 3: Axum Integration

Below is a complete example of integrating Askama with Axum to serve a dynamic web page:

// main.rs
use askama::Template;
use askama_web::WebTemplate;
use axum::{Router, routing::get};

#[derive(Template, WebTemplate)]
#[template(path = "hello.html")]
struct HelloTemplate {
    name: String,
}

async fn hello() -> HelloTemplate {
    HelloTemplate { name: "Web".to_string() }
}

#[tokio::main]
async fn main() {
    let app = Router::new().route("/", get(hello));
    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}
<!-- templates/hello.html -->
<!DOCTYPE html>
<html>
  <head><title>Hello</title></head>
  <body>
    <h1>Hello, {{ name }}!</h1>
  </body>
</html>

Key Points:

  • The handler returns a Template + WebTemplate struct, which implements IntoResponse.
  • The template is rendered and sent as an HTTP response.
  • You can add more routes and templates for a multi-page app.

Debugging Demonstration

Show common errors and how to fix them:

// Incorrect in Askama 0.16: missing the runtime values argument and Result return type.
fn bad_filter(s: &str) -> String { s.to_owned() }

// Correct:
#[askama::filter_fn]
pub fn good_filter(s: impl std::fmt::Display, _values: &dyn askama::Values) -> askama::Result<String> {
    Ok(s.to_string())
}

6. Hands-On Practice (30-40 minutes)

Exercise 1: Basic Practice (10 minutes)

Objective: Write and use a custom filter to reverse a string.

Instructions:

  1. Create a filters.rs file
  2. Write a reverse filter that reverses the input string
  3. Use it in a template to display reversed text

Starter Code:

#[askama::filter_fn]
pub fn reverse(s: impl std::fmt::Display, _values: &dyn askama::Values) -> askama::Result<String> {
    // Fill in here
}

Solution:

#[askama::filter_fn]
pub fn reverse(s: impl std::fmt::Display, _values: &dyn askama::Values) -> askama::Result<String> {
    Ok(s.to_string().chars().rev().collect())
}

Exercise 2: Intermediate Challenge (15 minutes)

Objective: Handle a runtime filter error when rendering a template. Missing fields are compile-time errors, not runtime errors.

Requirements:

  • Write a custom filter that returns Err(askama::Error::Custom(...)) for empty input
  • Render with empty data and handle that error
  • Print a user-friendly error message

Hints:

  • Use Result and match in main()

Exercise 3: Creative Application (15 minutes)

Objective: Integrate Askama with Axum to serve a dynamic web page.

Challenge:

  • Set up a minimal Axum app
  • Create a template that takes a list of items
  • Render the template in a handler and serve it at /items

Extension Ideas:

  • Add a custom filter for formatting items
  • Serve static CSS for styling

7. Common Pitfalls and Troubleshooting (10 minutes)

Frequent Mistakes

  1. Problem: Wrong filter signature Cause: Missing #[askama::filter_fn], missing &dyn askama::Values, or not returning askama::Result<T> Solution: Follow Askama 0.16’s filter requirements; impl Display is a useful input type
  2. Problem: Unhandled render errors Cause: Using unwrap() everywhere Solution: Use match or ? to handle errors

Debugging Tips

  • Read error messages for filter signature issues
  • Test filters with different data
  • Use logging for error handling in web apps

8. Best Practices and Professional Tips (5 minutes)

Industry Standards

  • Keep filters small and focused
  • Handle errors gracefully in production
  • Organize filters in a separate module
  • Use configuration files for template settings

Next Steps in Your Learning Journey

  • Explore advanced Axum features (routing, middleware)
  • Write more complex filters and macros
  • Read the Askama and Axum documentation for deeper integration

9. Summary and Review (5 minutes)

Key Takeaways

  1. Custom filters extend Askama’s power
  2. Error handling prevents crashes and improves UX
  3. Web integration makes Askama practical for real apps

Quick Review Questions

  1. What are the required arguments and return type for an Askama 0.16 custom filter?
  2. How do you handle errors from render()?
  3. How do you return a template as a response in Axum?

For Instructors

Teaching Tips

  • Show filter signature errors and how to fix them
  • Demonstrate error handling in both CLI and web contexts
  • Use live coding to connect Askama and Axum

Assessment Ideas

  • Ask students to write and use a custom filter
  • Review error handling in student code
  • Peer review: test each other’s web handlers

Adaptation Notes

  • For beginners: provide filter and Axum scaffolding
  • For advanced students: challenge with more complex filters and error scenarios
  • Online: use screen sharing for web integration demos