Pernah merasa kalau bikin frontend pakai React atau Vue itu overkill untuk project kecil? Kamu cuma butuh fetch data, update sebagian halaman, dan bikin form yang interaktif tapi harus setup bundler, state management, routing, dan seabrek config lainnya. Saya dulu juga begitu. Sampai akhirnya saya menemukan kombinasi HTMX dan Alpine.js yang mengubah cara saya membangun web app.
Tidak semua project butuh SPA (Single Page Application) yang full-blown. Kadang yang kamu butuhkan hanya server-side rendering yang sedikit lebih interaktif. Di sinilah HTMX dan Alpine.js bersinar mereka memberikan kekuatan dinamis tanpa kompleksitas framework JavaScript modern.
HTMX adalah library kecil (hanya ~14KB gzip) yang memungkinkan kamu melakukan AJAX request langsung dari HTML attribute. Tidak perlu menulis satu baris JavaScript pun untuk fetch data dan swap konten di halaman. Konsepnya sederhana: kamu memberi tahu browser "klik tombol ini, ambil HTML dari server, dan taruh di elemen ini."
<!-- Klik tombol ini akan mengambil /api/users dan taruh hasilnya di #user-list -->
<button hx-get="/api/users"
hx-target="#user-list"
hx-swap="innerHTML">
Load Users
</button>
<div id="user-list"></div>
Tiga attribute utama HTMX yang perlu kamu hafal:
Yang bikin HTMX berbeda dari pendekatan AJAX tradisional adalah filosofinya. HTMX mengembalikan HTML, bukan JSON. Server merender fragment HTML, HTMX menaruhnya di DOM. Ini artinya logika rendering tetap di server tempat yang paling natural untuk web.
Kalau HTMX menangani komunikasi client-server, Alpine.js menangani interaktivitas di sisi client. Bayangkan Alpine.js sebagai versi minimal dari Vue.js yang bisa langsung kamu pakai di HTML tanpa build step.
<div x-data="{ open: false }">
<button @click="open = !open">Toggle Menu</button>
<div x-show="open" x-transition>
<p>Ini konten yang bisa di-show/hide</p>
</div>
</div>
Attribute x-data mendefinisikan state lokal komponen. @click adalah shorthand untuk event listener. x-show mengontrol visibility dengan transisi halus. Semua ini tanpa satu file JavaScript pun yang terpisah.
Sekarang bagaimana kalau kita gabungkan keduanya? HTMX handle data fetching dan DOM swap, Alpine.js handle state lokal dan interaksi UI. Ini adalah kombinasi yang sangat powerful untuk web app yang server-rendered.
Mari kita bikin contoh nyata: sebuah todo list yang bisa add, toggle complete, dan delete semua tanpa framework JavaScript.
<div x-data="{ editing: false, editId: null }">
<!-- Form tambah todo -->
<form hx-post="/api/todos"
hx-target="#todo-list"
hx-swap="beforeend"
hx-on::after-request="this.reset()">
<input type="text" name="task" placeholder="Tambah task baru..." required>
<button type="submit">Add</button>
</form>
<!-- List todo -->
<ul id="todo-list">
<!-- Server render initial items -->
<li id="todo-1" class="todo-item">
<input type="checkbox"
hx-patch="/api/todos/1/toggle"
hx-swap="outerHTML">
<span>Belajar HTMX</span>
<button hx-delete="/api/todos/1"
hx-target="#todo-1"
hx-swap="outerHTML">Hapus</button>
</li>
</ul>
</div>
Perhatikan bagaimana setiap interaksi (add, toggle, delete) langsung terdefinisi di HTML. Tidak ada event handler yang ditulis di JavaScript, tidak ada state management, tidak ada API client. Server mengembalikan HTML fragment, HTMX menaruhnya di tempat yang tepat.
Untuk backend, kita pakai CI4 yang sudah support return HTML fragment. Berikut contoh controller untuk todo API:
<?php
namespace App\Controllers;
use CodeIgniter\RESTful\ResourceController;
class TodoApi extends ResourceController
{
protected $format = 'html';
public function index()
{
$todos = $this->todoModel->findAll();
return view('partials/todo_list', ['todos' => $todos]);
}
public function create()
{
$task = $this->request->getPost('task');
$todo = $this->todoModel->save([
'task' => $task,
'completed' => false
]);
// Return HTML fragment, bukan JSON!
$newTodo = $this->todoModel->find($this->todoModel->getInsertID());
return view('partials/todo_item', ['todo' => $newTodo]);
}
public function toggle($id)
{
$todo = $this->todoModel->find($id);
$this->todoModel->update($id, [
'completed' => !$todo['completed']
]);
// Return updated HTML fragment
$todo = $this->todoModel->find($id);
return view('partials/todo_item', ['todo' => $todo]);
}
public function delete($id)
{
$this->todoModel->delete($id);
return ''; // Empty response removes the element
}
}
View partial todo_item.php:
<li id="todo-=$todo['id'];?>" class="todo-item">
<input type="checkbox"
=$todo['completed'] ? 'checked' : '';?>
hx-patch="/api/todos/=$todo['id'];?>/toggle"
hx-swap="outerHTML">
<span class="=$todo['completed'] ? 'line-through' : '';">
=$todo['task'];?>
</span>
<button hx-delete="/api/todos/=$todo['id'];?>"
hx-target="#todo-=$todo['id'];?>"
hx-swap="outerHTML">
Hapus
</button>
</li>
Yang perlu diingat: HTMX mengirim request dengan header HX-Request: true. Kamu bisa cek header ini di controller untuk menentukan apakah harus return full page atau fragment HTML saja.
HTMX punya sistem event yang powerful. Kamu bisa trigger request berdasarkan event DOM, bahkan mendengarkan event dari response HTMX.
<!-- Search dengan debounce 500ms -->
<input type="text"
name="q"
hx-get="/api/search"
hx-trigger="keyup changed delay:500ms"
hx-target="#search-results"
hx-indicator="#loading">
<div id="loading" class="htmx-indicator">Searching...</div>
<div id="search-results"></div>
hx-trigger mendukung modifier seperti delay (debounce), changed (hanya trigger jika value berubah), dan throttle (batasi frekuensi request). Ini sangat berguna untuk fitur search real-time.
Kamu juga bisa trigger HTMX dari Alpine.js:
<div x-data="{ filter: 'all' }">
<select x-model="filter"
@change="$el.setAttribute('hx-get', '/api/todos?status=' + filter); htmx.trigger($el, 'refresh')">
<option value="all">Semua</option>
<option value="active">Aktif</option>
<option value="completed">Selesai</option>
</select>
</div>
Salah satu kelemahan SPA tradisional yang sering dilupakan adalah loading state. Dengan HTMX, kamu bisa handle ini dengan mudah:
/* Loading indicator styling */
.htmx-indicator {
display: none;
}
.htmx-request .htmx-indicator {
display: block;
}
.htmx-request.htmx-indicator {
display: block;
}
/* Transisi halus untuk swap */
.htmx-swapping {
opacity: 0;
transition: opacity 200ms ease-out;
}
.htmx-added {
opacity: 0;
}
.htmx-added {
opacity: 1;
transition: opacity 200ms ease-in;
}
Tambahkan class hx-indicator untuk menunjukkan loading spinner saat request berjalan. HTMX otomatis menambahkan class htmx-request pada elemen yang sedang loading.
Jangan salah paham HTMX bukan pengganti React atau Vue untuk semua kasus. Berikut panduan praktisnya:
Untuk CMS, admin panel, e-commerce catalog, dan web app yang mostly CRUD HTMX + Alpine.js adalah pilihan yang jauh lebih simpel dan cepat untuk dikembangkan.
Mari bandingkan ukuran bundle untuk todo app sederhana:
Selisih 100KB+ artinya perbedaan signifikan untuk user di jaringan 3G atau area dengan koneksi lambat yang masih sangat relevan di Indonesia. Time to Interactive (TTI) juga lebih cepat karena tidak ada hydration step.
Beberapa hal yang perlu kamu perhatikan saat deploy HTMX app ke production:
1. CSRF Protection: HTMX mengirim header HX-Request, tapi kamu tetap butuh CSRF token. CI4 sudah handle ini otomatis pastikan form HTMX include token:
<form hx-post="/api/todos">
<?= csrf_field(); ?>
<input type="text" name="task">
</form>
2. Error Handling: HTMX trigger event htmx:responseError saat server return error. Handle ini untuk user feedback:
document.body.addEventListener('htmx:responseError', function(evt) {
alert('Error: ' + evt.detail.xhr.status + ' - ' + evt.detail.xhr.responseText);
});
3. Browser History: Gunakan hx-push-url="true" untuk update URL tanpa full page reload. Ini penting agar back button tetap bekerja.
<a hx-get="/api/todos/1"
hx-target="#content"
hx-push-url="true">
Lihat Detail
</a>
HTMX dan Alpine.js bukan tentang "anti-JavaScript" ini tentang memilih tool yang tepat untuk job yang tepat. Untuk mayoritas web app yang kita bangun sehari-hari (admin panel, landing page interaktif, form wizard, CRUD app), kombinasi ini menawarkan development speed yang jauh lebih cepat dengan complexity yang jauh lebih rendah.
Kamu tetap bisa pakai React untuk dashboard analytics yang complex. Tapi untuk halaman "About" yang punya tab interaktif, atau form kontak dengan validasi real-time? HTMX + Alpine.js jawabannya. Mulai dari project kecil, rasakan bedanya, dan kamu akan tahu kapan harus reach for the bigger tools.
Coba bikin satu fitur interaktif di projectmu pakai HTMX minggu ini. Saya yakin kamu akan terkejut betapa sedikit code yang kamu butuhkan. Ada pertanyaan atau pengalaman pakai HTMX? Share di komentar!