KAHIBARO
Discord Login Register

6.9. Templates

Why Templates Matter

When you build web backends, you often need to send HTML pages to the browser. You could build HTML strings by hand in your Python code, but that becomes messy very quickly.

Templates solve this problem.

A template is an HTML file that contains small placeholders for dynamic data. Your backend fills those placeholders with real values at runtime, and sends the final HTML to the client.

Very informally:

This chapter focuses on the general idea of templates and typical template engines, not on a specific framework. Later, when you use FastAPI or another framework, you will apply the same ideas with its own tools.

Key idea: A template engine takes template + data and produces HTML:
$$\text{rendered\_html} = \text{template\_engine}(\text{template}, \text{data})$$

Static HTML vs Templates

Static HTML

A static HTML file is just plain HTML. It does not change unless you edit the file.

Example: about.html:

html
<!DOCTYPE html>
<html>
  <head>
    <title>About our site</title>
  </head>
  <body>
    <h1>About</h1>
    <p>This is a static page.</p>
  </body>
</html>

Every visitor sees the exact same content.

Static HTML is fine for:

Dynamic HTML with Templates

For a typical application, content depends on:

Creating such pages by hand in Python is painful:

python
def user_profile_page(user):
    return (
        "<html><body>"
        "<h1>Profile for " + user["name"] + "</h1>"
        "<p>Age: " + str(user["age"]) + "</p>"
        "</body></html>"
    )

Problems:

Templates let you write HTML like normal, but with placeholders for the dynamic parts:

html
<h1>Profile for {{ user.name }}</h1>
<p>Age: {{ user.age }}</p>

The backend then renders this with a dictionary like {"user": user}.

What Is a Template Engine?

A template engine is a library that:

  1. Reads a template file with placeholders
  2. Replaces placeholders with real values you provide
  3. Returns the final string, usually HTML

Popular template engines:

LanguageCommon template engines
PythonJinja2, Django Templates, Mako
JavaScript (node)Handlebars, EJS, Pug
PHPBlade (Laravel), Twig

You do not need to remember all of these. You just need to understand:

A template engine uses a template language with special syntax (like {{ ... }}) to:

  • insert values
  • repeat blocks
  • conditionally show or hide content

We will use a Jinja-like syntax in examples, because many Python frameworks use it or something very similar.

Basic Template Syntax: Variables

The most basic thing in a template is a variable. This is data that your backend passes to the template.

In many engines, variables are written like this:

html
<p>Hello, {{ name }}!</p>

When you render the template with:

python
data = {"name": "Alice"}

The final HTML becomes:

html
<p>Hello, Alice!</p>

Another example:

Template user.html:

html
<h1>User: {{ user.username }}</h1>
<p>Email: {{ user.email }}</p>
<p>Joined: {{ user.joined_at }}</p>

Rendered with:

python
data = {
    "user": {
        "username": "bob",
        "email": "bob@example.com",
        "joined_at": "2026-08-27"
    }
}

Result:

html
<h1>User: bob</h1>
<p>Email: bob@example.com</p>
<p>Joined: 2026-08-27</p>

Most engines let you access:

Conditions in Templates

Templates often need to show content only if something is true. For example, show a "Login" link only if the user is anonymous.

Template engines usually support if statements.

Example:

html
{% if user.is_logged_in %}
  <p>Welcome back, {{ user.name }}!</p>
  <a href="/logout">Logout</a>
{% else %}
  <p>Hello, guest!</p>
  <a href="/login">Login</a>
{% endif %}

Here

Another example with multiple branches:

html
{% if cart.total_items == 0 %}
  <p>Your cart is empty.</p>
{% elif cart.total_items == 1 %}
  <p>You have 1 item in your cart.</p>
{% else %}
  <p>You have {{ cart.total_items }} items in your cart.</p>
{% endif %}

You pass something like:

python
{"cart": {"total_items": 3}}

The HTML will show:

html
<p>You have 3 items in your cart.</p>

Loops in Templates

You often want to display lists of things:

Templates provide for loops to repeat a block for each item.

Example: product list

html
<ul>
  {% for product in products %}
    <li>
      {{ product.name }} - ${{ product.price }}
    </li>
  {% endfor %}
</ul>

Data:

python
{
  "products": [
    {"name": "Keyboard", "price": 39.99},
    {"name": "Mouse", "price": 19.99},
    {"name": "Monitor", "price": 199.99},
  ]
}

Rendered HTML:

html
<ul>
  <li>
    Keyboard - $39.99
  </li>
  <li>
    Mouse - $19.99
  </li>
  <li>
    Monitor - $199.99
  </li>
</ul>

You can also combine loops and conditions:

html
<ul>
  {% for product in products %}
    {% if product.in_stock %}
      <li>{{ product.name }} is in stock!</li>
    {% else %}
      <li>{{ product.name }} is out of stock.</li>
    {% endif %}
  {% endfor %}
</ul>

This lets you keep logic that affects display only inside the template.

Template Inheritance and Layouts

Real websites usually have:

Copying this HTML into every file is tedious and error prone.

Template engines usually support template inheritance so you can define a base layout and extend it.

Base Template

Example: base.html:

html
<!DOCTYPE html>
<html>
  <head>
    <title>{% block title %}My Site{% endblock %}</title>
    <link rel="stylesheet" href="/static/styles.css">
  </head>
  <body>
    <header>
      <h1>My Site</h1>
      <nav>
        <a href="/">Home</a>
        <a href="/products">Products</a>
        <a href="/about">About</a>
      </nav>
    </header>
    <main>
      {% block content %}{% endblock %}
    </main>
    <footer>
      <p>&copy; {{ current_year }} My Company</p>
    </footer>
  </body>
</html>

Here we define blocks:

Child templates fill in these blocks.

Child Templates

Example: home.html:

html
{% extends "base.html" %}
{% block title %}Home | My Site{% endblock %}
{% block content %}
  <h2>Welcome</h2>
  <p>This is the homepage.</p>
{% endblock %}

Example: products.html:

html
{% extends "base.html" %}
{% block title %}Products | My Site{% endblock %}
{% block content %}
  <h2>Products</h2>
  <ul>
    {% for product in products %}
      <li>{{ product.name }} - ${{ product.price }}</li>
    {% endfor %}
  </ul>
{% endblock %}

The template engine:

This keeps your layouts consistent, and makes it easy to change a header or footer in one place.

Escaping and Security in Templates

When you insert user data into HTML, you must think about security, especially Cross-Site Scripting (XSS). Many template engines automatically escape values to prevent HTML or JavaScript from being interpreted.

For example, suppose a malicious user name is:

text
<script>alert('hacked');</script>

If your template is:

html
<p>User: {{ user.name }}</p>

A safe template engine will escape the special characters and output:

html
<p>User: &lt;script&gt;alert('hacked');&lt;/script&gt;</p>

So the browser shows the text instead of executing the script.

Important rule: Never disable auto escaping for untrusted input. If you mark data as "safe" in the template, do it only for data you created yourself, not from users.

Most engines also provide filters for formatting, such as:

Exact syntax depends on the engine, but the idea is the same.

Directory Structure for Templates

Frameworks usually expect templates in a specific directory. A common layout:

text
project/
  app.py             # your backend code
  templates/
    base.html
    home.html
    users/
      list.html
      detail.html
  static/
    styles.css
    app.js

Typical patterns:

Frameworks then have a function like render_template("users/list.html", data).

Passing Data from Backend to Templates

Even though this chapter focuses on templates in general, it is useful to see what it looks like to call the engine from Python. The exact code will differ per framework, but the pattern is always similar.

You usually:

  1. Prepare your data in Python
  2. Call a render function with the name of the template and a context dict

A generic example:

python
def user_list_endpoint(request):
    # 1. Load users from database
    users = [
        {"id": 1, "name": "Alice"},
        {"id": 2, "name": "Bob"},
    ]
    # 2. Render template with data
    html = render_template("users/list.html", {"users": users})
    # 3. Return HTML as a response
    return HTMLResponse(html)

Template users/list.html:

html
<h1>Users</h1>
<ul>
  {% for user in users %}
    <li>
      <a href="/users/{{ user.id }}">{{ user.name }}</a>
    </li>
  {% endfor %}
</ul>

Rendered example:

html
<h1>Users</h1>
<ul>
  <li>
    <a href="/users/1">Alice</a>
  </li>
  <li>
    <a href="/users/2">Bob</a>
  </li>
</ul>

Using Templates with Forms

Templates are also useful for displaying forms and showing validation errors.

Simple Login Form Template

Template auth/login.html:

html
<h1>Login</h1>
{% if error %}
  <p style="color: red;">{{ error }}</p>
{% endif %}
<form method="post" action="/login">
  <label>
    Email:
    <input type="email" name="email" value="{{ form.email }}">
  </label>
  <br>
  <label>
    Password:
    <input type="password" name="password">
  </label>
  <br>
  <button type="submit">Login</button>
</form>

Backend flow could be:

  1. GET /login
    • Render the template with empty form and no error.
  2. POST /login
    • Validate the credentials.
    • If invalid, render the template again, with:
      • error: an error message
      • form.email: the email the user entered

Example context on error:

python
{
  "error": "Invalid email or password",
  "form": {"email": "user@example.com"}
}

The template shows the error and keeps the email field filled.

When to Use Templates vs JSON APIs

Not every backend uses templates. Some backends only serve JSON to frontend applications built in React, Vue, or mobile apps.

A quick comparison:

ApproachWhat backend returnsTypical client
TemplatesReady HTML pagesBrowser
JSON APIJSON dataSingle Page Apps, apps

You often:

You can also combine both. For example:

Understanding templates is important even if you plan to build APIs. Many admin interfaces, dashboards, and internal tools still use server rendered templates.

Common Pitfalls and Best Practices

Keep Business Logic Out of Templates

Templates are for presentation. They should not contain complex business rules.

Bad pattern:

html
{% if user.balance > 1000 and user.country in ["US", "UK"] and discounts_enabled %}
  <p>You are a premium customer!</p>
{% endif %}

Better:

python
is_premium = user.balance > 1000 and user.country in ["US", "UK"] and discounts_enabled
context = {"user": user, "is_premium": is_premium}

Template:

html
{% if is_premium %}
  <p>You are a premium customer!</p>
{% endif %}

Use Template Inheritance

Do not repeat your header, footer, or menu in every template. Put them in a base template and extend it.

Avoid Building HTML by Hand in Python

If you see a lot of string concatenation in your backend to build HTML, move it to a template.

Do Not Trust User Content

Let the template engine escape values by default. Only mark content as safe if you are sure it does not contain harmful HTML or JavaScript.

Summary

In this chapter you learned:

Later, when you work with specific frameworks like FastAPI, you will use these concepts with their built in tools to render HTML pages from your backend.

Views: 9

Comments

Please login to add a comment.

Don't have an account? Register now!