6.9. Templates
Table of Contents
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:
- Template: a blueprint with holes.
- Data: the values that fill the holes.
- Rendered page: the final HTML that goes to the browser.
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:
<!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:
- Simple marketing pages
- Documentation that does not change often
- Assets like CSS and JavaScript files
Dynamic HTML with Templates
For a typical application, content depends on:
- The user that is logged in
- Data from the database
- Current time, settings, or request parameters
Creating such pages by hand in Python is painful:
def user_profile_page(user):
return (
"<html><body>"
"<h1>Profile for " + user["name"] + "</h1>"
"<p>Age: " + str(user["age"]) + "</p>"
"</body></html>"
)Problems:
- Hard to read
- Easy to break HTML
- Hard to maintain large pages
Templates let you write HTML like normal, but with placeholders for the dynamic parts:
<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:
- Reads a template file with placeholders
- Replaces placeholders with real values you provide
- Returns the final string, usually HTML
Popular template engines:
| Language | Common template engines |
|---|---|
| Python | Jinja2, Django Templates, Mako |
| JavaScript (node) | Handlebars, EJS, Pug |
| PHP | Blade (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:
<p>Hello, {{ name }}!</p>When you render the template with:
data = {"name": "Alice"}The final HTML becomes:
<p>Hello, Alice!</p>Another example:
Template user.html:
<h1>User: {{ user.username }}</h1>
<p>Email: {{ user.email }}</p>
<p>Joined: {{ user.joined_at }}</p>Rendered with:
data = {
"user": {
"username": "bob",
"email": "bob@example.com",
"joined_at": "2026-08-27"
}
}Result:
<h1>User: bob</h1>
<p>Email: bob@example.com</p>
<p>Joined: 2026-08-27</p>Most engines let you access:
- dictionary keys:
user["username"]often appears asuser.username - object attributes:
user.usernamedirectly - list items:
items[0]
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:
{% 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
{% ... %}is for control statements such asif,for, etc.{{ ... }}is for inserting values.
Another example with multiple branches:
{% 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:
{"cart": {"total_items": 3}}The HTML will show:
<p>You have 3 items in your cart.</p>Loops in Templates
You often want to display lists of things:
- list of products
- list of blog posts
- list of error messages
Templates provide for loops to repeat a block for each item.
Example: product list
<ul>
{% for product in products %}
<li>
{{ product.name }} - ${{ product.price }}
</li>
{% endfor %}
</ul>Data:
{
"products": [
{"name": "Keyboard", "price": 39.99},
{"name": "Mouse", "price": 19.99},
{"name": "Monitor", "price": 199.99},
]
}Rendered 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:
<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:
- the same
<head>section - the same header and footer
- the same navigation menu
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:
<!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>© {{ current_year }} My Company</p>
</footer>
</body>
</html>Here we define blocks:
titleblockcontentblock
Child templates fill in these blocks.
Child Templates
Example: home.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:
{% 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:
- starts with
base.html - replaces the block contents with the child template content
- produces the final HTML
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:
<script>alert('hacked');</script>If your template is:
<p>User: {{ user.name }}</p>A safe template engine will escape the special characters and output:
<p>User: <script>alert('hacked');</script></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:
- Dates:
{{ date_value | date("Y-m-d") }}or similar - Length:
{{ items | length }} - Uppercase:
{{ name | upper }}
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:
project/
app.py # your backend code
templates/
base.html
home.html
users/
list.html
detail.html
static/
styles.css
app.jsTypical patterns:
- A global
templatesfolder - Subfolders for each part of your application
users/list.htmlusers/detail.htmlproducts/list.html
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:
- Prepare your data in Python
- Call a render function with the name of the template and a context dict
A generic example:
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:
<h1>Users</h1>
<ul>
{% for user in users %}
<li>
<a href="/users/{{ user.id }}">{{ user.name }}</a>
</li>
{% endfor %}
</ul>Rendered example:
<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:
<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:
- GET
/login - Render the template with empty
formand noerror. - POST
/login - Validate the credentials.
- If invalid, render the template again, with:
error: an error messageform.email: the email the user entered
Example context on error:
{
"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:
| Approach | What backend returns | Typical client |
|---|---|---|
| Templates | Ready HTML pages | Browser |
| JSON API | JSON data | Single Page Apps, apps |
You often:
- Use templates for classic server rendered websites (blog, admin panel, simple apps).
- Use JSON APIs for more dynamic or mobile heavy applications.
You can also combine both. For example:
- Use templates for an admin panel.
- Use JSON endpoints for your public API.
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:
{% if user.balance > 1000 and user.country in ["US", "UK"] and discounts_enabled %}
<p>You are a premium customer!</p>
{% endif %}Better:
- Calculate a flag
is_premiumin Python. - Pass it to the template.
is_premium = user.balance > 1000 and user.country in ["US", "UK"] and discounts_enabled
context = {"user": user, "is_premium": is_premium}Template:
{% 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:
- What templates are and why they are useful for building dynamic HTML pages.
- How template engines work with variables, conditions, and loops.
- How template inheritance lets you create a common layout.
- How to pass data from backend code to templates.
- How templates interact with forms and how to avoid common mistakes.
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
KAHIBARO