Flask Routes, Templates, and Jinja

You can read this note directly if you know Python functions and basic HTML. Flask connects incoming web requests to Python functions. Jinja lets those functions generate HTML containing dynamic values.

Beginner Mental Model

A Flask application waits for requests.

request method + URL path
-> matching Flask route
-> Python processing
-> response

Example:

GET /greet -> run greet() -> return HTML response

A route is not the same as a file path. It is a URL pattern connected to a Python function.

Project Structure

Caption: A basic Flask project separates Python routes, HTML templates, and static files such as CSS.

Typical structure:

project/
  app.py
  templates/
    index.html
    result.html
  static/
    style.css
LocationMain purpose
app.pyFlask application, routes, and Python processing
templates/HTML files rendered through Jinja
static/CSS and fixed assets such as images

Minimal Flask App

from flask import Flask
 
app = Flask(__name__)
 
 
@app.route("/")
def index():
    return "Hello from Flask"
 
 
if __name__ == "__main__":
    app.run()

@app.route("/") maps the root URL to index(). The function’s return value becomes the response.

app.run() starts Flask’s local development server. It is suitable for local learning and testing, not public production deployment.

Rendering a Template

from flask import Flask, render_template
 
app = Flask(__name__)
 
 
@app.route("/")
def index():
    return render_template("index.html", name="Aisha")

templates/index.html:

<h1>Hello, {{ name }}</h1>

render_template():

  1. loads the template from templates/;
  2. makes the supplied values available to Jinja;
  3. renders final HTML;
  4. sends that HTML to the browser.

The browser receives:

<h1>Hello, Aisha</h1>

It does not receive the original {{ name }} expression.

Route Parameters

A route may capture part of the URL:

@app.route("/student/<name>")
def student(name):
    return render_template("student.html", name=name)

A request to /student/Aisha assigns "Aisha" to the Python parameter name.

GET and POST

GET commonly retrieves a page or search result. POST commonly submits data for processing.

from flask import Flask, render_template, request
 
app = Flask(__name__)
 
 
@app.route("/greet", methods=["GET", "POST"])
def greet():
    if request.method == "POST":
        name = request.form["name"]
        return render_template("greet.html", name=name)
 
    return render_template("form.html")

templates/form.html:

<form method="post" action="{{ url_for('greet') }}">
  <label for="name">Name</label>
  <input id="name" type="text" name="name">
  <button type="submit">Greet</button>
</form>

Trace:

StepValue
HTML input keyname="name"
user typesAisha
Flask readsrequest.form["name"]
Python variablename = "Aisha"
template receivesname="Aisha"
browser displaysHello, Aisha

The HTML name attribute and the key used in request.form[...] must match.

Jinja Syntax

Jinja distinguishes output from control logic.

PurposeSyntax
output a value{{ value }}
control structure{% ... %}
template comment{# ... #}

Variables

<p>{{ title }}</p>

Conditional Statements

{% if score >= 50 %}
  <p>Pass</p>
{% else %}
  <p>Try again</p>
{% endif %}

Jinja uses explicit closing tags such as {% endif %}. Python-style colons are not used inside Jinja tags.

Loops

<ul>
{% for title in titles %}
  <li>{{ title }}</li>
{% endfor %}
</ul>

To display dictionary values clearly:

<table>
{% for subject, score in results.items() %}
  <tr>
    <td>{{ subject }}</td>
    <td>{{ score }}</td>
  </tr>
{% endfor %}
</table>

Filters

A filter transforms a displayed value:

{{ name|upper }}
{{ title|length }}

Jinja normally escapes HTML-like text for safety. Avoid using |safe on user-supplied content because it can allow injected markup or scripts to be interpreted.

url_for()

url_for() builds a URL from a Flask endpoint name.

Route link:

<a href="{{ url_for('greet') }}">Greeting form</a>

Static stylesheet:

<link rel="stylesheet"
      href="{{ url_for('static', filename='style.css') }}">

For a route function called student with a parameter:

<a href="{{ url_for('student', name='Aisha') }}">Aisha</a>

Using url_for() avoids scattering hard-coded route paths through templates.

Template Inheritance

A base template stores shared page structure.

templates/layout.html:

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>{% block title %}Web App{% endblock %}</title>
  </head>
  <body>
    {% block body %}{% endblock %}
  </body>
</html>

Child template:

{% extends "layout.html" %}
 
{% block title %}Home{% endblock %}
 
{% block body %}
  <h1>Home</h1>
{% endblock %}

This reduces duplication and improves consistency across pages.

Debugging by Layer

SymptomFirst check
404 Not Foundroute path and requested URL
405 Method Not Allowedform method and route methods=[...]
form key missinginput name and request.form[...]
template not foundfilename and templates/ folder
Jinja variable blankvalue passed by render_template()
CSS missingstatic/ folder and url_for('static', ...)
loop shows nothingcollection passed to the template

Common Mistakes

  • Forgetting to import request before reading form data.
  • Using request.form["name"] when the input has a different name.
  • Forgetting to allow POST on a route receiving a POST form.
  • Placing templates outside templates/.
  • Writing Jinja syntax in a file served as plain static HTML.
  • Confusing a URL route with a template filename.
  • Hard-coding URLs when url_for() would be clearer.
  • Using |safe with untrusted user input.

Paper 1 and Paper 2 Emphasis

Paper 1-style reasoning: explain request-response flow, distinguish GET from POST, trace values from form to route to template, and identify errors in a short code fragment.

Paper 2-style practical work: create routes, match input names to request.form, render templates, use Jinja loops or conditionals, and test the app on a local server.

Check Your Understanding

  1. What does a Flask route connect?
  2. What does render_template() produce?
  3. What is the difference between {{ ... }} and {% ... %}?
  4. Why use url_for()?
  5. Why might a route return 405 instead of 404?

Answers:

  1. A URL pattern and HTTP method to a Python function.
  2. Final HTML generated from a template and supplied values.
  3. {{ ... }} outputs a value; {% ... %} controls template logic.
  4. It generates URLs from endpoint or static-file names without hard-coding paths.
  5. The route exists, but it does not allow the request method used.