Tutorial Reflex: Membangun Web App Full-Stack dengan Python Murni

# Reflex: Membangun Aplikasi Web Full-Stack dengan Python Murni Reflex memungkinkan Anda membangun aplikasi web lengkap — frontend, backend, dan basis data — tanpa keluar dari Python. Berbeda dengan...

By Ruby Abdullah · · tutorial
ReflexWeb DevelopmentFull-StackPythonFrontendFastAPI

Reflex: Membangun Aplikasi Web Full-Stack dengan Python Murni

Reflex memungkinkan Anda membangun aplikasi web lengkap — frontend, backend, dan basis data — tanpa keluar dari Python. Berbeda dengan tool yang berorientasi dashboard, Reflex mengompilasi komponen Python Anda menjadi frontend React/Next.js sementara logika aplikasi berjalan di backend FastAPI, sehingga Anda mendapatkan manajemen state, routing, dan persistensi yang sesungguhnya. Tutorial ini membahas arsitektur, model state, komponen, routing, handler async, akses basis data, dan deployment menggunakan satu contoh task manager yang utuh.

Apa Itu Reflex dan Bagaimana Cara Kerjanya

Reflex (sebelumnya Pynecone) adalah framework untuk membangun aplikasi web full-stack di mana Anda hanya menulis Python. Saat proses build, komponen yang Anda deskripsikan dalam Python dikompilasi menjadi frontend React/Next.js. Saat runtime, state aplikasi Anda berada di kelas-kelas Python yang dijalankan di backend FastAPI. Browser dan backend berkomunikasi melalui koneksi WebSocket: interaksi pengguna memicu event handler di server, server mengubah state, dan hanya state yang berubah yang dikirim kembali ke klien untuk me-render ulang komponen yang terdampak.

Model ini berbeda dari Streamlit. Pada Streamlit, setiap interaksi menjalankan ulang seluruh skrip dari atas ke bawah, dan Anda mengelola persistensi melalui st.sessionstate. Reflex justru menyimpan objek state berumur panjang per sesi klien dan memperbarui UI secara reaktif — hanya bagian yang terikat ke state var yang berubah saja yang diperbarui. Anda mendapatkan nuansa single-page application dengan routing sisi klien, ditambah backend nyata yang bisa Anda hubungkan dengan basis data dan background task.

Sekilas Arsitektur

  • Frontend: Komponen Python dikompilasi menjadi komponen React yang di-render oleh Next.js. Styling dipetakan ke CSS/Tailwind di balik layar.
  • Backend: Server FastAPI meng-host kelas State dan event handler Anda.
  • Transport: Sebuah WebSocket membawa event dari klien ke server dan delta state kembali.
  • Basis data (opsional): Integrasi SQLModel bawaan (SQLite secara default) yang dapat diakses dari event handler.

Browser (React/Next.js)  <-- WebSocket -->  Backend FastAPI (State Python + handler)

|

Basis data (SQLModel)

Instalasi dan Penyiapan Proyek

Reflex membutuhkan Python 3.10 atau lebih baru. Buat environment terisolasi, lalu instal paketnya.

python -m venv .venv

source .venv/bin/activate # Windows: .venv\Scripts\activate

pip install reflex

Verifikasi instalasi

reflex --version

Inisialisasi proyek baru di dalam direktori kosong. Perintah init menyusun struktur dan meminta Anda memilih template; pilih template kosong untuk memulai dari awal yang bersih.

mkdir taskmanager && cd taskmanager

reflex init

Saat diminta, pilih template "blank"

Struktur Proyek

Setelah inisialisasi, Anda mendapatkan tata letak seperti berikut:

taskmanager/

├── taskmanager/

│ └── taskmanager.py # Aplikasi Anda: state, komponen, halaman

├── assets/ # File statis (gambar, favicon)

├── rxconfig.py # Konfigurasi aplikasi (nama, dburl, dll.)

└── requirements.txt

File rxconfig.py mendeklarasikan nama aplikasi dan konfigurasi seperti URL basis data serta port frontend/backend.

import reflex as rx

config = rx.Config(

appname="taskmanager",

dburl="sqlite:///taskmanager.db",

)

Menjalankan dalam Mode Pengembangan

Jalankan server pengembangan. Reflex mengompilasi frontend, menjalankan backend FastAPI, dan menyajikan aplikasi dengan hot reload.

reflex run

Frontend di http://localhost:3000, backend di http://localhost:8000

Model State

State adalah inti dari Reflex. Anda mendefinisikan state dengan menurunkan kelas dari rx.State. Atribut kelas dengan anotasi tipe menjadi state var — nilai reaktif yang disimpan per sesi klien. Method pada kelas state adalah event handler: keduanya berjalan di backend dan merupakan satu-satunya tempat yang seharusnya mengubah state.

import reflex as rx


class CounterState(rx.State):

count: int = 0

def increment(self):

self.count += 1

def decrement(self):

self.count -= 1

def setcount(self, value: str):

# Event handler menerima payload event sebagai argumen

self.count = int(value or 0)

Ketika increment berjalan, ia mengubah self.count. Reflex menghitung delta dan mengirimkannya ke klien, yang lalu me-render ulang hanya komponen yang terikat ke count. Tidak ada rerun seluruh skrip: bagian halaman yang tidak terkait tetap utuh, dan state komponen lokal di frontend (posisi scroll, fokus) tetap terjaga.

Menghubungkan State ke Komponen

Anda membangun UI dengan mengembalikan komponen dari sebuah fungsi dan mengikat prop serta event-nya ke state.

def counter() -> rx.Component:

return rx.hstack(

rx.button("-", onclick=CounterState.decrement),

rx.text(CounterState.count),

rx.button("+", onclick=CounterState.increment),

spacing="3",

align="center",

)

CounterState.count di sini bukan integer biasa — melainkan referensi reaktif. Ketika nilainya berubah di backend, rx.text ikut diperbarui secara otomatis. Event seperti onclick diikat ke referensi handler (bukan dipanggil), dan Reflex menjalankannya di server saat dipicu.

Membangun UI dari Komponen

Reflex menyediakan banyak komponen. Primitif tata letak seperti rx.vstack, rx.hstack, dan rx.box menata anak-anak komponen; komponen konten seperti rx.text, rx.heading, rx.input, dan rx.button me-render UI. Prop diberikan sebagai argumen kata kunci.

def taskinput() -> rx.Component:

return rx.vstack(

rx.heading("Tugas Saya", size="6"),

rx.hstack(

rx.input(

placeholder="Apa yang perlu dikerjakan?",

value=TaskState.newtask,

onchange=TaskState.setnewtask,

width="100%",

),

rx.button("Tambah", onclick=TaskState.addtask),

width="100%",

),

rx.box(height="1rem"),

spacing="3",

width="100%",

maxwidth="480px",

)

Di sini prop value input diikat ke sebuah state var dan onchange memperbaruinya pada setiap ketikan. Ikatan dua arah ini bersifat eksplisit: nilai yang ditampilkan selalu mencerminkan state backend, dan setiap perubahan mengalir kembali melalui event handler.

Computed Var serta Rendering Kondisional/Iteratif

Computed var adalah properti yang didekorasi dengan @rx.var yang menurunkan nilai dari state var lain. Ia menghitung ulang otomatis ketika dependensinya berubah, sehingga data turunan tetap berada di luar event handler.
class TaskState(rx.State):

tasks: list[dict] = []

newtask: str = ""

@rx.var

def opencount(self) -> int:

return len([t for t in self.tasks if not t["done"]])

@rx.var

def hastasks(self) -> bool:

return len(self.tasks) > 0

Karena UI dikompilasi, Anda tidak bisa menggunakan if/for Python biasa terhadap state var di dalam komponen — pada saat kompilasi nilai sebenarnya belum diketahui. Sebagai gantinya, gunakan rx.cond untuk rendering kondisional dan rx.foreach untuk iterasi.

def taskrow(task: dict, index: int) -> rx.Component:

return rx.hstack(

rx.checkbox(

checked=task["done"],

onchange=lambda checked: TaskState.toggle(index),

),

rx.text(task["title"]),

rx.spacer(),

rx.button(

"Hapus",

onclick=lambda: TaskState.delete(index),

colorscheme="red",

variant="soft",

),

width="100%",

align="center",

)

def tasklist() -> rx.Component:

return rx.cond(

TaskState.hastasks,

rx.vstack(

rx.foreach(TaskState.tasks, taskrow),

width="100%",

),

rx.text("Belum ada tugas. Tambahkan di atas.", color="gray"),

)

rx.cond(kondisi, jikabenar, jikasalah) me-render salah satu cabang berdasarkan kondisi reaktif. rx.foreach(iterable, fungsirender) memetakan setiap item (dan indeks opsional) menjadi komponen; fungsi render harus didefinisikan pada level modul karena ia dikompilasi, bukan dijalankan per render.

Routing Multi-Halaman

Aplikasi Reflex bersifat multi-halaman secara default. Anda mendaftarkan halaman baik dengan dekorator @rx.page maupun dengan memanggil app.addpage. Setiap halaman dipetakan ke sebuah rute.

app = rx.App()


@rx.page(route="/", title="Tugas")

def index() -> rx.Component:

return rx.container(taskinput(), tasklist())

def about() -> rx.Component:

return rx.container(

rx.heading("Tentang"),

rx.text("Task manager sederhana yang dibangun dengan Reflex."),

rx.link("Kembali ke tugas", href="/"),

)

app.addpage(about, route="/about", title="Tentang")

Rute Dinamis dan Redirect

Segmen dinamis dideklarasikan dengan tanda kurung siku pada rute. Nilainya tersedia melalui state var router.

class DetailState(rx.State):

@rx.var

def taskid(self) -> str:

return self.router.page.params.get("id", "")

@rx.page(route="/task/[id]")

def taskdetail() -> rx.Component:

return rx.container(

rx.heading("Detail tugas"),

rx.text("Melihat tugas: ", DetailState.taskid),

rx.link("Kembali", href="/"),

)

Untuk berpindah halaman secara programatik dari event handler, kembalikan rx.redirect.

def saveandexit(self):

# ... menyimpan perubahan ...

return rx.redirect("/")

Styling, Tema, dan Prop Responsif

Anda bisa men-style komponen secara inline dengan nama prop yang dipetakan ke CSS, mengoper sebuah dict style, atau mendefinisikan tema global pada aplikasi. Reflex menggunakan Tailwind di balik layar dan terintegrasi dengan pustaka komponen berbasis Radix agar tema tetap konsisten.

app = rx.App(

theme=rx.theme(

appearance="light",

accentcolor="indigo",

radius="medium",

),

)

Dict style dan prop responsif memungkinkan adaptasi terhadap ukuran layar. Oper sebuah list ke prop untuk menetapkan nilai per breakpoint (mobile-first).

def card(content: rx.Component) -> rx.Component:

return rx.box(

content,

style={

"padding": "1.5rem",

"border": "1px solid var(--gray-5)",

"borderradius": "12px",

},

width=["100%", "100%", "480px"], # penuh di layar kecil, tetap di layar besar

)

Event Handler Async dan Pembaruan Streaming

Event handler bisa bersifat async, yang penting untuk memanggil API atau model eksternal tanpa memblokir. Dengan yield di dalam handler, Anda mendorong state perantara ke klien sehingga menghasilkan pembaruan UI progresif — berguna untuk men-stream output model.

import asyncio

import reflex as rx

class ChatState(rx.State):

prompt: str = ""

answer: str = ""

isstreaming: bool = False

async def ask(self):

self.isstreaming = True

self.answer = ""

yield # kirim status "streaming dimulai" ke klien

# Simulasi streaming token demi token dari model/API

async for chunk in fakemodelstream(self.prompt):

self.answer += chunk

yield # dorong setiap jawaban parsial ke UI

self.isstreaming = False

async def fakemodelstream(prompt: str):

for word in f"Anda bertanya: {prompt}. Ini balasan streaming.".split():

await asyncio.sleep(0.05)

yield word + " "

Setiap yield mengirim delta state saat ini melalui WebSocket. UI yang terikat ke ChatState.answer diperbarui secara bertahap, dan Anda bisa mengikat spinner ke isstreaming.

def chatview() -> rx.Component:

return rx.vstack(

rx.input(

value=ChatState.prompt,

onchange=ChatState.setprompt,

placeholder="Tanyakan sesuatu...",

),

rx.button("Kirim", onclick=ChatState.ask, loading=ChatState.isstreaming),

rx.text(ChatState.answer),

width="100%",

)

Form dengan Validasi Sisi Klien dan Sisi Server

rx.form mengumpulkan field dan mengirim nilainya sebagai dict ke sebuah handler. Gunakan batasan tingkat HTML (required, type, minlength) untuk pemeriksaan sisi klien, dan validasi ulang di server sebelum menyimpan.
class FormState(rx.State):

error: str = ""

def submit(self, formdata: dict):

title = (formdata.get("title") or "").strip()

if len(title) < 3:

self.error = "Judul minimal 3 karakter."

return

self.error = ""

return TaskState.addtaskfromform(title)

def taskform() -> rx.Component:

return rx.form(

rx.vstack(

rx.input(name="title", placeholder="Judul tugas", required=True),

rx.cond(

FormState.error != "",

rx.text(FormState.error, color="red", size="2"),

),

rx.button("Buat", type="submit"),

),

onsubmit=FormState.submit,

resetonsubmit=True,

)

required di sisi klien memblokir submit kosong di browser; pemeriksaan panjang di sisi server bersifat otoritatif karena klien dapat melewati validasi HTML. Selalu jadikan validasi server sebagai sumber kebenaran.

Akses Basis Data dengan rx.Model

Reflex menyertakan integrasi SQLModel. Turunkan kelas dari rx.Model dengan table=True untuk mendefinisikan sebuah tabel. Anda melakukan query dan mutasi di dalam event handler menggunakan sesi melalui rx.session().

import reflex as rx

from sqlmodel import select

class Task(rx.Model, table=True):

title: str

done: bool = False

Buat skema dengan menghasilkan dan menerapkan migrasi (Reflex membungkus Alembic).

reflex db init       # sekali saja, menyiapkan migrasi

reflex db makemigrations --message "add task table"

reflex db migrate

Sekarang baca dan tulis di dalam handler. Muat baris yang tersimpan ke dalam state var agar UI dapat me-render-nya.

class TaskState(rx.State):

tasks: list[Task] = []

newtask: str = ""

def loadtasks(self):

with rx.session() as session:

self.tasks = session.exec(select(Task)).all()

def addtask(self):

title = self.newtask.strip()

if not title:

return

with rx.session() as session:

session.add(Task(title=title))

session.commit()

self.newtask = ""

self.loadtasks()

def toggle(self, taskid: int):

with rx.session() as session:

task = session.get(Task, taskid)

if task:

task.done = not task.done

session.add(task)

session.commit()

self.loadtasks()

Panggil loadtasks saat halaman dimuat dengan melampirkannya ke onload halaman.

@rx.page(route="/", onload=TaskState.loadtasks)

def index() -> rx.Component:

return rx.container(taskinput(), tasklist())

Background Task

Untuk pekerjaan berdurasi panjang yang tidak boleh memblokir event loop atau menahan lock state, deklarasikan event background dengan @rx.event(background=True). Di dalamnya, Anda harus mengubah state di dalam blok async with self untuk mengambil lock dengan aman.

class JobState(rx.State):

progress: int = 0

@rx.event(background=True)

async def runjob(self):

for i in range(1, 101):

await asyncio.sleep(0.1)

async with self:

self.progress = i

Cara ini menjaga UI tetap responsif selama job berjalan dan men-stream pembaruan progres seiring loop berlanjut.

Build dan Deployment

Untuk build bergaya produksi secara lokal, ekspor artefak frontend dan backend.

# Menghasilkan frontend statis dan bundel backend

reflex export

Reflex menyediakan perintah deploy terkelola yang menyiapkan dan mengirim aplikasi.

reflex deploy

Untuk self-hosting, kontainerisasi aplikasi. Dockerfile minimal menginstal dependensi, menjalankan migrasi, dan memulai server produksi.

FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

COPY . .

RUN reflex export --frontend-only --no-zip

EXPOSE 3000 8000

CMD ["reflex", "run", "--env", "prod"]

Di balik reverse proxy (nginx/Caddy), arahkan port frontend dan proksikan path WebSocket/backend agar klien dan server terhubung dengan benar.

Praktik Terbaik

  • Jaga state tetap minimal dan dapat diserialisasi. State var melintasi WebSocket sebagai JSON; simpan data sederhana dan muat objek berat sesuai kebutuhan di dalam handler.
  • Ubah state hanya di event handler. Perlakukan komponen sebagai tampilan murni dari state; jangan pernah mengubah state saat rendering.
  • Gunakan computed var untuk data turunan. Hindari duplikasi logika antar handler; biarkan @rx.var menghitung ulang otomatis.
  • Pisahkan state berdasarkan fitur. Beberapa subclass rx.State menjaga keterpisahan urusan dan mengurangi pembaruan yang tidak perlu.
  • Validasi di server. Batasan sisi klien meningkatkan UX, tetapi backend harus menegakkan kebenaran sebelum menulis ke basis data.
  • Muat ulang dari basis data setelah penulisan. Setelah commit, segarkan state var terkait agar UI mencerminkan kebenaran yang tersimpan.
  • Gunakan handler async untuk I/O dan event background untuk job panjang. Lakukan yield untuk men-stream progres dan menjaga antarmuka tetap responsif.
  • Kunci versi Reflex di requirements.txt agar output kompilasi tetap reprodusibel di seluruh environment.

Kesimpulan dan Poin Penting

Reflex menjembatani jarak antara prototipe Python yang cepat dan aplikasi full-stack yang sesungguhnya. Dengan mengompilasi komponen Python menjadi frontend React/Next.js dan menjalankan state Anda di backend FastAPI, Reflex memberikan pembaruan UI reaktif, routing sisi klien, form, dan persistensi basis data tanpa menulis JavaScript.

Poin penting:

  • Reflex bersifat full-stack: komponen Python dikompilasi menjadi frontend React; State berjalan di backend FastAPI melalui WebSocket.
  • Model state reaktif hanya memperbarui komponen yang berubah, berbeda dengan rerun penuh dari atas ke bawah pada Streamlit.
  • Bangun UI dari komponen, ikat prop ke state var, dan hubungkan event ke handler; gunakan rx.cond dan rx.foreach untuk rendering dinamis.
  • Computed var @rx.var menurunkan data; routing multi-halaman, rute dinamis, dan rx.redirect menangani navigasi.
  • Handler async dengan yield men-stream pembaruan progresif; rx.Model menyediakan akses basis data bawaan; event background menangani pekerjaan berdurasi panjang.
  • Kirim ke produksi dengan reflex export, reflex deploy, atau kontainer Docker di balik reverse proxy.

Dengan blok-blok bangunan ini, Anda dapat mengembangkan satu file menjadi aplikasi multi-halaman yang persisten dan mudah dipelihara — semuanya dalam Python murni.

Artikel Terkait

Tutorial LitServe: Framework Serving Model AI yang Cepat dan Mudah

Tutorial LitServe: Framework Serving Model AI yang Cepat dan Mudah Pendahuluan LitServe adalah framework open-source dar...

SQLModel: ORM Modern Python untuk Aplikasi AI yang Type-Safe

SQLModel: ORM Modern Python untuk Aplikasi AI yang Type-Safe Dalam pengembangan aplikasi AI/ML, pengelolaan data di data...

Tutorial Lengkap FastAPI untuk Machine Learning: Building Production ML APIs

Tutorial Lengkap FastAPI untuk ML: Build Production ML APIs FastAPI adalah framework web Python modern dengan performa t...

Tutorial Lengkap Streamlit Advanced: Build Production-Ready ML Apps

Tutorial Lengkap Streamlit Advanced: Build Production-Ready ML Apps Streamlit adalah library Python yang powerful untuk ...