Skip to content

Templates and Frontend (Jinja2)

Nori uses the industry-standard Jinja2 template system. Each route can respond by rendering an HTML file with variable contextual dictionaries served from the controller.

The base hierarchy resides in /rootsystem/templates/. Templates must be organized in folders matching the module name:

rootsystem/templates/
    base.html
    auth/
        login.html
        register.html
    product/
        list.html
        form.html
        show.html
    404.html
    500.html

Inheritance and Blocks

Jinja2 templates use base.html inheritance via {% extends %}, letting child templates override specific blocks.

base.html (Base Layout): Builds the portal wrapper, with titled placeholders called {% block %}:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>{% block title %}Nori App{% endblock %}</title>
    <!-- Your Custom CSS Injections here -->
    {% block head %}{% endblock %} 
</head>
<body>
    <nav>...</nav>

    <main>
        <!-- Your Child Content is rendered here -->
        {% block content %}{% endblock %}
    </main>
</body>
</html>

Child / HTML View (home.html): Starts by injecting the extends directive on line one and saturates its own context overrides at will:

{% extends "base.html" %}

{% block title %}Main Dashboard{% endblock %}

{% block content %}
    <h1>Client List</h1>
    <!-- More HTML... -->
{% endblock %}

Variables, Loops, and Conditionals

Jinja2 provides minimal logic for rendering dynamic variables transferred by your controller, using double braces {{ variable }} and enclosed blocks for if/for {% instruction %}.

If / Else

{% if request.session.get('user_id') %}
    <p>Welcome Administrator!</p>
{% else %}
    <a href="/login">Log In</a>
{% endif %}

(Important: The request object is automatically available in templates because Starlette's TemplateResponse(request, ...) injects it into the template context. You do not need to pass it manually in the context dictionary — just pass it as the first argument to TemplateResponse. This means request.url.path, request.session, and request.url_for(...) are always accessible in your Jinja2 templates).

For Loops (Collection Cycles or Querysets)

<ul>
    {% for user in total_users %}
        <li><a href="/user/{{ user.id }}">{{ user.name }}</a></li>
    {% else %}
        <li>The list is empty — no registered users.</li>
    {% endfor %}
</ul>

Calling url_for on the Request component.

<a href="{{ request.url_for('edit_client', client_id=123) }}">Click to Edit</a>

CSRF Protection in Forms

Every POST form must include a CSRF token. csrf_field is a Jinja2 global — call it directly without passing it from the controller:

<form method="POST" action="/articles">
    {{ csrf_field(request.session)|safe }}
    <input type="text" name="title" />
    <button type="submit">Create</button>
</form>

For AJAX requests, use the raw token via csrf_token(request.session) in the X-CSRF-Token header. See Forms & Validation for full details.

Flash Messages

flash() and get_flashed_messages() are available for one-time session notifications. get_flashed_messages is registered as a Jinja2 global:

{# In base.html — renders flash messages and clears them from the session #}
{% for msg in get_flashed_messages(request.session) %}
    <div class="alert alert-{{ msg.category }}">{{ msg.message }}</div>
{% endfor %}

See Flash Messages for controller-side usage.

Native Static Files (StaticFiles)

Any css, javascript file, svg logo, or mp4 multimedia that does not need compilation should be copied purely into rootsystem/static/.

rootsystem/
    static/
        css/style.css
        js/app.js
        images/logo_nori.png

Nori exposes the static tag positionally without unnecessary abstract routes:

<!-- In base.html, inside <head> -->
<link rel="stylesheet" href="/static/css/style.css">

<!-- Image Rendering -->
<img src="/static/images/logo_nori.png" alt="Startup Logo">

Custom Error Pages

By switching Nori's .env configuration to DEBUG=false on a Production server, your site will hide Interactive Exception Tracebacks, instantly throwing and compiling the templates from the Root Templates folder (404.html for a "Not Found" or fake URLs, and 500.html protecting unexpected database crashes from hackers).

Their implementation does not differ from a normal base-extended HTML in order to keep the look of your native application intact and neat while protecting its crash state (Hiding it from the end user and rendering your own intact emergency dashboard).