Cookbook / Files and pages
HTML templates
Server-rendered pages with auto-escaping templates, layouts and form handling.
Templates use minijinja, which follows the Jinja syntax: inheritance, loops, filters and macros. Output is HTML-escaped by default, so user input cannot inject markup.
use axeon::templates::Templates;
use axeon::{json, Server, ServerError};
use serde::Deserialize;
#[derive(Deserialize)]
struct Comment {
author: String,
text: String,
}
fn main() {
let templates = Templates::new()
.add("layout.html", "<!doctype html><title>{% block title %}{% endblock %}</title>{% block body %}{% endblock %}")
.unwrap()
.add(
"hello.html",
"{% extends 'layout.html' %}\
{% block title %}Hello {{ name }}{% endblock %}\
{% block body %}<h1>Hello, {{ name }}!</h1>\
<ul>{% for c in comments %}<li><b>{{ c.author }}</b>: {{ c.text }}</li>{% endfor %}</ul>\
{% endblock %}",
)
.unwrap();
// In a real app, load a directory instead: Templates::from_dir("views")
let mut app = Server::new();
app.templates(templates);
app.get("/hello/:name", |req| async move {
req.render(
"hello.html",
json!({
"name": req.params()["name"],
"comments": [{ "author": "Ada", "text": "<script>alert(1)</script>" }],
}),
)
});
// An HTML form posts urlencoded data.
app.post("/comments", |req| async move {
let comment: Comment = req.form()?;
Ok::<_, ServerError>((201, format!("thanks {}: {}", comment.author, comment.text)))
});
app.listen("127.0.0.1:3000").expect("server failed to start");
}
curl http://127.0.0.1:3000/hello/Grace # the <script> comes out escaped
curl -X POST http://127.0.0.1:3000/comments -d 'author=Ada&text=hi'
Notes#
Templates::from_dir("views")loads every file under the directory, so templates are named by their relative path.- Add
Csrfmiddleware (see Cookies, sessions and CSRF) to any app that posts HTML forms.