Flask, Routing, and Templates
Build server side apps with Flask, manage routes, and render Jinja templates effectively.
Content
Jinja2 templating syntax
Versions:
Watch & Learn
AI-discovered learning video
Sign in to watch the learning video for this topic.
Jinja2 Templating Syntax — Flask Views Made Beautiful
Remember how routes hand you data and request/response objects hand you context? Jinja2 is the theatrical costume department that makes that data look good on stage.
Why Jinja2 matters (no, really)
You already know how to build routes and use url_for from our Routing and URL building work. You know how to grab form data from request and send a Response. Jinja2 sits between your Python view functions and the user's browser: it takes Python objects you pass from Flask and renders them into HTML safely, efficiently, and expressively.
If Flask is the chef and routes are orders, Jinja2 is the plating technique that makes the dish Instagram-ready.
Quick anatomy: view -> template
- Flask route collects data (lists, dicts, objects).
- You call render_template('page.html', name=name, items=items).
- Jinja2 inserts variables and evaluates control logic in the template.
- Browser receives HTML string.
This builds on the request/response flow you already learned: view functions produce a response by rendering templates instead of returning raw HTML strings.
Core syntax: variables, filters, and control structures
Variables
Use double curly braces for expression output. Output is autoescaped by default (good for preventing XSS).
<!-- templates/hello.html -->
<h1>Hello, {{ user_name }}!</h1>
<p>You have {{ messages | length }} new messages.</p>
Micro explanation: {{ ... }} prints results. Jinja evaluates the expression inside using the context you passed from Flask.
Filters
Filters modify output with a pipe, like Unix but friendlier.
- lower, upper
- safe (disable escaping, use carefully)
- default('fallback')
- join(', ')
<p>{{ tagline | default('Welcome') | upper }}</p>
<p>{{ items | join(', ') }}</p>
Control structures
Use {% ... %} for logic: loops, conditionals, set, import, extends, block.
<ul>
{% for item in items %}
<li>{{ loop.index }}: {{ item }}</li>
{% else %}
<li>No items found.</li>
{% endfor %}
</ul>
{% if user.is_admin %}
<a href="{{ url_for('admin') }}">Admin</a>
{% endif %}
Micro explanation: loop is a special variable available inside for loops with useful fields like index, index0, first, last.
Template inheritance: DRY HTML like a champ
When pages share layout, extend a base template.
<!-- templates/base.html -->
<html>
<head>
<title>{% block title %}Site{% endblock %}</title>
</head>
<body>
<header>{% block header %}My Site{% endblock %}</header>
<main>{% block content %}{% endblock %}</main>
<footer>{% block footer %}© 2026{% endblock %}</footer>
</body>
</html>
<!-- templates/index.html -->
{% extends 'base.html' %}
{% block title %}Home{% endblock %}
{% block content %}
<h2>Welcome</h2>
<p>Hello, {{ user_name }}.</p>
{% endblock %}
Why this rocks: change header once in base.html and every page inherits it. This is the templating equivalent of writing a function instead of repeating code — building on the Python fundamentals you already know about clean, readable code.
Reuse: macros, includes, and imports
- include: inserts a partial template (like partials/components).
- macro: define reusable template functions.
<!-- templates/_card.html -->
<div class="card">
<h3>{{ title }}</h3>
<div>{{ body }}</div>
</div>
<!-- templates/dashboard.html -->
{% include '_card.html' %}
<!-- macros -->
{% macro link(url, text) %}
<a href="{{ url }}">{{ text }}</a>
{% endmacro %}
{{ link(url_for('index'), 'Home') }}
Micro explanation: macros behave like small functions inside templates. Use them to keep templates tidy and semantic.
Escaping, safe, and security reminders
- Jinja2 autoescapes HTML by default when rendering templates from Flask. That stops raw user inputs from breaking your page or executing scripts.
- Use the safe filter only when you absolutely trust the content (e.g., sanitized markdown output).
Bad: {{ user_input | safe }}
Good: {{ user_input }} (escape is automatic)
Security tip: escaping is your friend. Treat safe like a loaded weapon.
Useful patterns and small examples
- Enumerate with readable indexes:
{% for item in items %}
<li>{{ loop.index }} — {{ item }}</li>
{% endfor %}
- Default fallback:
<p>{{ user.nickname | default(user.username) }}</p>
- Conditional class:
<div class="card {% if important %}card-important{% endif %}">
- Render a dict safely:
<pre>{{ data | tojson(indent=2) }}</pre>
Debugging tips
- Render a template with minimal context to isolate failures.
- Use Jinja2's error messages; they include template name and line numbers.
- If a variable is undefined, consider passing it explicitly or use default filter.
- In development, set Flask debug mode to True to get helpful tracebacks.
Why people misunderstand this: newbies often try to run heavy logic in templates. Remember: templates are for presentation. Keep business logic in Python views and models — this keeps code testable and maintainable, a lesson straight from Python fundamentals.
When to use Jinja features vs Python
Do in Python:
- Complex data transformations
- Database calls
- Authentication checks
Do in Jinja:
- Show/hide elements
- Looping to render lists
- Small formatting helpers (filters, macros)
This separation keeps templates readable and logic centralized.
Key takeaways
- {{ }} prints expressions; {% %} runs logic.
- Use extends and block for layout inheritance — DRY HTML.
- Filters are your lightweight formatters; macros are reusable template functions.
- Autoescaping prevents XSS; use safe only when you must.
- Keep heavy logic in Python views — templates are for presentation.
"This is the moment where the concept finally clicks." — When you stop writing HTML strings in views and start composing templates, your web code becomes readable, secure, and joyful.
Quick next steps (build on routing and request/response)
- Update a route to use render_template with a context dict.
- Create a base template and inherit it across pages.
- Replace repeated HTML with macros and includes.
- Practice escaping by rendering user input and observing autoescaping.
Go build something small: a todo list view where the Flask route queries items and the template uses a loop, conditional classes, and a macro for each todo card. Keep it neat, keep it testable, and have fun plating your data.
Comments (0)
Please sign in to leave a comment.
No comments yet. Be the first to comment!