Belajar Flask - Templates dengan Jinja2
Episode 6 of 27

Belajar Flask - Templates dengan Jinja2

Mempelajari Jinja2 sebagai templating engine Flask: syntax dasar, template inheritance dengan blocks, macros untuk komponen reusable, filters, dan autoescaping untuk mencegah XSS, serta menyusun layout dan partials untuk web app.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Sejauh ini kita mengembalikan string dan JSON. Saat membangun web app dengan halaman HTML — bukan sekadar API — kalian butuh cara merender halaman secara efisien. Itulah tugas Jinja2, templating engine bawaan Flask. Episode 6 membahas syntax dasar Jinja2, template inheritance (yang menghemat puluhan jam pekerjaan), macros, filters, dan autoescaping — mekanisme keamanan bawaan yang mencegah XSS. Kuasai episode ini, dan kalian bisa membangun UI web utuh dengan DRY yang sehat.

Rendering Template Pertama

Pythonapp.py - render template
from flask import Flask, render_template
 
app = Flask(__name__)
 
 
@app.get("/")
def index() -> str:
    return render_template("index.html", title="Home", user="devnull")
HTMLtemplates/index.html
<!doctype html>
<html lang="id">
<head><title>{{ title }}</title></head>
<body>
    <h1>Selamat datang, {{ user }}</h1>
</body>
</html>

{{ title }} dan {{ user }} adalah expression yang diganti nilai dari argumen render_template — context yang kalian kirim adalah satu-satunya jembatan data ke halaman.

Syntax Dasar Jinja2

Jinja2 punya tiga konstruksi utama:

KonstruksiSyntaxFungsi
Expression{{ nilai }}Menampilkan nilai
Statement{% if %} / {% for %}Logika & kontrol alur
Comment{# catatan #}Tidak dirender
HTMLLoop dan kondisi di template
{% if user.is_admin %}
    <a href="/admin">Panel Admin</a>
{% else %}
    <span>Mode pengguna biasa</span>
{% endif %}
 
<ul>
{% for item in items %}
    <li>{{ loop.index }}. {{ item.name }}</li>
{% else %}
    <li>Belum ada item</li>
{% endfor %}
</ul>

loop.index adalah variabel khusus Jinja2 (indeks perulangan), dan klausa {% else %} pada for berjalan saat list kosong — pola rapi untuk empty state.

Template Inheritance: Layout dan Blocks

Masalah terbesar UI multi-halaman adalah duplikasi: navbar, footer, dan head HTML berulang di setiap halaman. Jinja2 menyelesaikannya dengan inheritance: satu layout induk dengan {% block %}, dan setiap halaman hanya mengisi bloknya.

HTMLtemplates/base.html - layout induk
<!doctype html>
<html lang="id">
<head>
    <meta charset="utf-8">
    <title>{% block title %}My App{% endblock %}</title>
</head>
<body>
    <nav>
        <a href="/">Home</a> | <a href="/about">About</a>
    </nav>
    <main>
        {% block content %}{% endblock %}
    </main>
</body>
</html>
HTMLtemplates/about.html - extends layout
{% extends "base.html" %}
 
{% block title %}Tentang Kami{% endblock %}
 
{% block content %}
    <h1>Tentang Kami</h1>
    <p>Kami belajar Flask bersama.</p>
{% endblock %}

{% extends "base.html" %} menyatakan bahwa halaman ini mewarisi layout; setiap {% block %} yang didefinisikan akan menggantikan blok bersangkutan di induk. Tambah satu baris navbar di base.html dan semua halaman berubah sekaligus — ini kekuatan DRY yang paling terasa.

Partials: Komponen yang Di-include

Untuk potongan UI yang dipakai ulang tanpa inheritance (misal kartu item), gunakan {% include %}:

HTMLtemplates/_card.html - partial item
<article class="card">
    <h2>{{ item.name }}</h2>
    <p>Harga: {{ item.price }}</p>
</article>

Dipanggil dalam loop: {% for item in items %} {% include "_card.html" %} {% endfor %}. Nama file partial yang diawali underscore (_card.html) adalah konvensi umum untuk menandai "file ini bukan halaman, hanya komponen".

Macros: Template yang Bisa Diterima Parameter

Saat partial butuh parameter, macro lebih cocok — ibarat fungsi di dalam template:

HTMLtemplates/_macros.html - definisi macro
{% macro card(item) %}
<article class="card">
    <h2>{{ item.name }}</h2>
    <p>Harga: {{ item.price | currency }}</p>
</article>
{% endmacro %}

Lalu dipanggil lewat {% import "_macros.html" as ui %} dan {{ ui.card(item) }} di dalam loop. Perhatikan pipe (|) pada {{ item.price | currency }} — itu syntax filter.

Filters: Transformasi Nilai

Filters memodifikasi nilai di dalam expression. Beberapa yang paling berguna:

HTMLContoh filters bawaan
<p>{{ user.name | upper }}</p>
<p>{{ item.price | round(2) }}</p>
<p>{{ long_text | truncate(80) }}</p>
<p>{{ created_at | default("belum ada") }}</p>

Kalian juga bisa mendefinisikan filter custom:

PythonFilter custom - formatting Rupiah
def rupiah(value: float) -> str:
    return f"Rp {value:,.0f}"
 
 
app.jinja_env.filters["rupiah"] = rupiah

Lalu pakai di template: {{ item.price | rupiah }}. Filters menjaga logika presentasi tetap di template, bukan bercampur dengan view.

Autoescaping: Pertahanan Pertama XSS

Sekarang bagian paling penting untuk keamanan: autoescaping. Jinja2 secara default meng-escape semua output {{ ... }} — karakter seperti <, >, &, " diubah menjadi entitas HTML.

HTMLAutoescaping bawaan
<p>{{ user_input }}</p>

Jika user_input berisi <script>alert("xss")</script>, Jinja2 merendernya sebagai teks biasa, bukan kode — karena < diubah menjadi &lt;. Tanpa ini, input pengguna bisa dieksekusi sebagai JavaScript oleh browser pengunjung lain: itulah serangan XSS.

Warning

Jangan mengesampingkan autoescaping tanpa alasan kuat. Memakai |safe atau Markup(...) berarti kalian menyatakan "string ini aman" — dan jika sumbernya input pengguna, kalian baru saja membuka celah XSS. Untuk konten markdown atau rich-text, sanitisasi di level server (episode 17), bukan sekadar |safe.

Kapan perlu |safe? Hanya untuk konten yang kalian kendalikan penuh dan memang harus HTML — misal artikel yang ditulis admin lewat editor markdown yang sudah di-sanitize.

Common Pitfalls Jinja2

  • Lupa {% extends %} di awal: harus baris pertama template anak.
  • Melempar objek DB mentah ke template: pass data yang sudah disiapkan (nama, price), bukan objek yang memuat field sensitif.
  • Nama template salah folder: Flask mencari di templates/; template di subfolder dirujuk dengan slash ("auth/login.html").

Penutup

Pada episode 6 ini, kalian telah menguasai Jinja2 — tulang punggung UI Flask.

Inti yang harus dibawa pulang:

  • render_template() merender file di templates/ dengan context yang kalian kirim.
  • {{ }} untuk output, {% %} untuk logika, {# #} untuk komentar.
  • Inheritance (extends + block) menghilangkan duplikasi layout; include dan macro untuk komponen reusable.
  • Filters (upper, default, truncate) dan filter custom menjaga logika presentasi tetap di template.

Di episode 7 selanjutnya, kita melengkapi sisi aset web app: static files & serving — folder /static, custom static folder, cache headers, dan praktik serve CSS/JS/images dengan benar. Pastikan lab kalian siap!