Membangun aplikasi web yang terasa modern biasanya identik dengan menulis JavaScript: kamu perlu fetch(), memanipulasi DOM, dan menjaga state agar antarmuka selalu sinkron dengan server. Namun, bagaimana jika kamu bisa mendapatkan pengalaman yang sama interaktifnya — live search, pembaruan parsial, validasi form — hanya dengan atribut HTML biasa dan server yang mengembalikan potongan HTML? Itulah janji htmx. Di tutorial ini kamu akan membangun aplikasi todo interaktif secara penuh dengan Flask dan htmx, tanpa menulis satu baris JavaScript pun. Jika belum familiar dengan Flask, baca dulu panduan Membangun Aplikasi Web dengan Flask sebelum melanjutkan.
Apa Itu htmx?
htmx adalah library JavaScript kecil (sekitar 14 KB, tanpa dependensi) yang memungkinkan kamu mengakses AJAX, WebSocket, dan Server-Sent Events langsung dari HTML melalui atribut seperti hx-get, hx-post, dan hx-trigger. Filosofinya sederhana: biarkan server yang menentukan antarmuka. Alih-alih server mengembalikan JSON lalu kamu render ulang di sisi klien, server cukup mengembalikan potongan HTML (partial), dan htmx menggantinya ke bagian halaman yang kamu tunjuk.
Pendekatan ini sangat berbeda dari kebiasaan memanipulasi DOM secara manual dengan JavaScript, seperti yang dibahas di artikel Memahami Dasar JavaScript: Variabel, Fungsi, dan DOM. Dengan htmx, logika UI tetap hidup di template server, sehingga kode jadi lebih sedikit dan lebih mudah dipahami.
Kenapa Memilih htmx?
Ada beberapa alasan kuat mengapa htmx kian populer di kalangan developer full-stack yang ingin menjauh dari kompleksitas SPA:
- Kode lebih sedikit — tidak perlu menulis dan memelihara state klien, reducer, atau client-side routing.
- Satu bahasa rendering — kamu merender HTML hanya di server (Jinja2, Django template, dsb.).
- Inkremental — htmx bisa dipakai pada aplikasi yang sudah ada tanpa menulis ulang semuanya.
- Mudah dipelajari — cukup pahami sekitar belasan atribut HTML.
Catatan versi: tutorial ini memakai htmx 2.x (rilis 2024). Jika kamu melihat contoh lama yang memakai hx-ws atau atribut hx-vars, itu berasal dari htmx 1.x. Sebagian besar atribut inti tetap sama, tetapi pastikan kamu membaca dokumentasi versi yang kamu pakai.
Persiapan Proyek
Pertama, buat virtual environment dan pasang Flask. Jalankan perintah berikut di terminal Linux/macOS (atau sesuaikan dengan Command Prompt/PowerShell di Windows):
mkdir htmx-todo && cd htmx-todo
python3 -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install flask
htmx sendiri tidak perlu di-install via pip — ia hanyalah satu file JavaScript yang kita muat lewat CDN di dalam template. Struktur proyek akhir akan terlihat seperti ini:
htmx-todo/
├── app.py
└── templates/
├── index.html
└── _todo_list.html
Menulis Kode Flask (app.py)
Kita pakai daftar (list) di dalam memori sebagai pengganti database agar tutorial tetap fokus pada htmx. Dalam aplikasi nyata, kamu bisa menggantinya dengan SQLite atau PostgreSQL. Buat file app.py berikut:
from flask import Flask, render_template, request
app = Flask(__name__)
# "Database" sederhana di dalam memori
todos = [
{"id": 1, "task": "Belajar htmx", "done": False},
{"id": 2, "task": "Bikin aplikasi todo", "done": False},
]
@app.route("/")
def index():
return render_template("index.html", todos=todos)
@app.route("/search")
def search():
q = request.args.get("q", "").strip().lower()
results = [t for t in todos if q in t["task"].lower()]
return render_template("_todo_list.html", todos=results)
@app.route("/add", methods=["POST"])
def add():
task = request.form.get("task", "").strip()
if task:
todos.append({"id": len(todos) + 1, "task": task, "done": False})
return render_template("_todo_list.html", todos=todos)
@app.route("/toggle/<int:todo_id>", methods=["POST"])
def toggle(todo_id):
for t in todos:
if t["id"] == todo_id:
t["done"] = not t["done"]
break
return render_template("_todo_list.html", todos=todos)
@app.route("/delete/<int:todo_id>", methods=["POST"])
def delete(todo_id):
global todos
todos = [t for t in todos if t["id"] != todo_id]
return render_template("_todo_list.html", todos=todos)
if __name__ == "__main__":
app.run(debug=True)
Perhatikan polanya: setiap endpoint tidak pernah mengembalikan halaman penuh, melainkan selalu merender _todo_list.html — potongan HTML berisi daftar tugas terbaru. Di situlah kunci interaktivitas htmx.
Template Utama (templates/index.html)
Buat templates/index.html. Di sinilah kita memuat htmx dari CDN dan memasang atribut-atribut htmx pada elemen HTML:
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Todo App dengan htmx</title>
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
<style>
body { font-family: system-ui, sans-serif; max-width: 520px; margin: 3rem auto; padding: 0 1rem; }
input[type=text] { padding: .5rem; width: 70%; font-size: 1rem; }
button { padding: .5rem 1rem; cursor: pointer; }
li { margin: .5rem 0; }
.done { text-decoration: line-through; color: #888; }
</style>
</head>
<body>
<h1>Todo List (htmx + Flask)</h1>
<!-- Live search -->
<input type="text" name="q"
placeholder="Cari tugas..."
hx-get="/search"
hx-trigger="keyup changed delay:300ms"
hx-target="#todo-list" />
<!-- Tambah tugas -->
<form hx-post="/add" hx-target="#todo-list" hx-on::after-request="this.reset()">
<input type="text" name="task" placeholder="Tambah tugas baru..." required />
<button type="submit">Tambah</button>
</form>
<!-- Daftar tugas -->
<ul id="todo-list">
{% include "_todo_list.html" %}
</ul>
</body>
</html>
Template Partial (templates/_todo_list.html)
Partial ini dirender oleh semua endpoint. Ia menghasilkan daftar <li> lengkap dengan atribut htmx untuk toggle dan hapus:
{% for todo in todos %}
<li>
<input type="checkbox"
{% if todo.done %}checked{% endif %}
hx-post="/toggle/{{ todo.id }}"
hx-target="#todo-list" />
<span class="{% if todo.done %}done{% endif %}">{{ todo.task }}</span>
<button hx-post="/delete/{{ todo.id }}"
hx-target="#todo-list"
hx-confirm="Yakin ingin menghapus?">Hapus</button>
</li>
{% else %}
<li><em>Tidak ada tugas.</em></li>
{% endfor %}
Memahami Atribut Inti htmx
Mari kita bedah atribut yang dipakai di atas, karena inilah inti dari htmx:
hx-get="/search"
hx-post="/add"
hx-trigger="keyup changed delay:300ms"
hx-target="#todo-list"
hx-confirm="Yakin ingin menghapus?"
hx-on::after-request="this.reset()"
hx-get/hx-post— menentukan URL dan method HTTP yang dipanggil. Ada jugahx-put,hx-patch, danhx-delete.hx-trigger— kapan request dikirim. Default untuk form adalahsubmitdan untuk input adalahchange; di sini kita memakaikeyup changed delay:300msagar pencarian terasa responsif tanpa membanjiri server.hx-target— elemen mana yang akan di-update dengan respons. Nilai#todo-listadalah selector CSS.hx-confirm— menampilkan dialog konfirmasi sebelum request dikirim.hx-on::after-request— menjalankan sedikit JavaScript setelah request selesai (misalnya mereset form).
Atribut terpenting yang sering terlupakan adalah hx-swap. Secara default htmx mengganti isi dalam elemen target (innerHTML). Kamu bisa mengubahnya menjadi hx-swap="outerHTML", beforeend, afterbegin, atau none sesuai kebutuhan — misalnya saat kamu ingin menambahkan item ke daftar alih-alih mengganti seluruh daftar.
Menjalankan dan Menguji Aplikasi
python app.py
Buka http://127.0.0.1:5000 di browser. Coba beberapa skenario berikut:
- Ketik di kolom pencarian — daftar tugas menyaring secara langsung tanpa reload halaman.
- Tambah tugas baru — form ter-reset otomatis dan daftar diperbarui tanpa reload.
- Centang checkbox — teks tugas langsung dicoret (toggle selesai).
- Klik Hapus — muncul dialog konfirmasi, lalu item hilang tanpa reload.
Jika kamu membuka tab Network di DevTools, kamu akan melihat bahwa setiap aksi menghasilkan request AJAX kecil yang merespons dengan potongan HTML — bukan JSON — dan htmx menyuntikkannya ke #todo-list. Inilah yang membuat semuanya terasa instan.
Kelebihan dan Keterbatasan htmx
htmx bukan pengganti segalanya. Ia bersinar untuk aplikasi yang didominasi interaksi server (CRUD, form, dashboard) dan tim yang ingin menghindari kompleksitas SPA. Namun untuk aplikasi dengan state klien yang sangat kompleks, interaksi offline-first, atau animasi rumit, framework seperti React tetap lebih cocok — kamu bisa membandingkannya di Belajar React untuk Pemula.
Karena htmx memindahkan rendering ke server, perhatikan beban server dan strategi caching. Untuk UI yang cantik tanpa CSS manual, padukan htmx dengan framework utility seperti Tailwind CSS — lihat Belajar Tailwind CSS untuk memulainya.
Kesimpulan
Dengan belasan atribut HTML, htmx memungkinkan kamu membangun aplikasi web yang terasa hidup — live search, partial update, dan CRUD — sambil tetap merender HTML di server. Kamu baru saja membuat todo app interaktif lengkap dengan Flask tanpa satu baris JavaScript pun. Dari sini kamu bisa memperdalam Flask lewat panduan lengkap Flask atau menjelajahi pola lain seperti validasi form server-side dan infinite scroll dengan hx-trigger="revealed". Selamat mencoba!