MUHAMMAD
FARI
MADYAN
[ Press ESC or Click to Skip ]

Rebuild Python FastAPI + React E-Learning

1

Part 1: Pengenalan & Arsitektur: Dari Monolit ke Arsitektur Async Terpisah

2

Part 2: Pemodelan Domain & Skema Database Async dengan SQLAlchemy 2.0 & Alembic

3

Part 3: Keamanan Stateless JWT, RBAC & Engine Impersonasi 1-Klik

4

Part 4: Engine Ujian CBT, Anti-Cheat & Telemetri Real-Time Native WebSocket

5

Part 5: Frontend Modern dengan React 19, TypeScript & Tailwind CSS v4

6

Part 6: Pemrosesan Batch openpyxl Performa Tinggi & Deployment Docker Produksi

Artikel ini tersedia dalam Bahasa Inggris

🇬🇧 Read in English
🇮🇩 Bahasa Indonesia📚 Rebuild Python FastAPI + React E-Learning

Membangun Ulang Laravel E-Learning ke Python FastAPI & React: Arsitektur Async Concurrency Tinggi & Migrasi Cloud-Ready

Panduan lengkap membangun ulang platform monolitik Laravel E-Learning & CBT ke arsitektur async cloud-ready performa tinggi dengan Python 3.12, FastAPI, SQLAlchemy 2.0 Async, Pydantic v2, React 19, Tailwind CSS v4, Redis, dan telemetri proctoring native WebSocket.

Muhammad Fari MadyanPenulis

13 menit baca

·

4 Oktober 2026


Latar Belakang & Motivasi: Melompati Batas Worker Sinkron

Pada artikel sebelumnya di blog ini, saya telah mendokumentasikan perjalanan transformasi aplikasi ujian sekolah tahun 2017 berbasis Laravel 5.2 menuju Laravel 13 modern dengan Filament 3 dan Livewire 3 (tersedia open-source di mfarim/laravel-elearning), serta edisi enterprise pendamping berbasis Java 21 Spring Boot & React.

Versi monolitik Laravel tersebut terbukti sangat produktif untuk operasional satu sekolah:

  • Filament 3 memungkinkan pembuatan panel administrasi CRUD dalam sekejap.
  • Livewire 3 menghadirkan pengalaman ujian reaktif tanpa perlu pusing mengelola pipeline build frontend terpisah.
  • Pest PHP menjamin seluruh alur pengujian fitur dan penguatan celah IDOR (Insecure Direct Object Reference) berjalan otomatis.

Namun, ketika platform Computer Based Test (CBT) dan Learning Management System (LMS) ini dipersiapkan untuk skala yang jauh lebih luas—seperti ujian serentak antarsekolah satu distrik/provinsi, sertifikasi profesional berskala puluhan ribu peserta, dan integrasi pemrosesan Artificial Intelligence (AI), kita berhadapan dengan batas fisik arsitektur worker sinkron:

  1. Lonjakan Konkurensi Saat "Exam Rush Hour": Ketika 2.000 hingga 10.000 peserta serempak mengklik tombol "Mulai Ujian" tepat pukul 08:00:00 WIB, setiap proses worker PHP-FPM mengalokasikan satu thread proses sistem operasi penuh (mengonsumsi RAM 40MB–80MB per worker). Menjalankan ribuan worker sinkron sekaligus memicu kejenuhan memori dan overhead pergantian konteks (context-switching) CPU yang sangat berat.
  2. Kebutuhan Telemetri Real-Time Asinkron Tanpa Beban: Guru pengawas di ruang kontrol memerlukan telemetri langsung (sisa waktu, progres nomor soal, peringatan keluar aplikasi/pindah tab). Menangani ribuan koneksi real-time dua arah yang persistent membutuhkan event loop asinkron non-blocking yang sangat hemat memori (~15MB–25MB).
  3. Kesiapan Ekosistem AI & Machine Learning: Pendidikan modern kini bergerak cepat menuju penilaian esai otomatis via Natural Language Processing (NLP), deteksi kemiripan soal semantik, dan kalibrasi tingkat kesulitan berbasis data. Python adalah bahasa utama (lingua franca) di dunia AI/ML dan data science.
  4. Ekosistem Multi-Platform yang Terpisah: Lembaga pendidikan modern memerlukan antarmuka web dan aplikasi mobile khusus (Android, iOS, CBT lockdown browser) yang mengakses REST API dan WebSocket yang sama dengan tipe data ketat dan terdokumentasi otomatis.

Untuk menjawab kebutuhan tersebut, saya membangun edisi pendamping ketiga: Python FastAPI + React E-Learning.

┌─────────────────────────────────────────────────────────────────────────────┐ │ RINGKASAN EVOLUSI ARSITEKTUR SISTEM │ ├──────────────────────────────────────┬──────────────────────────────────────┤ │ SUMBER: Stack Monolitik │ TARGET: Stack Async Terpisah │ │ (https://github.com/mfarim/ │ (https://github.com/mfarim/ │ │ laravel-elearning) │ fastapi-elearning-react) │ ├──────────────────────────────────────┼──────────────────────────────────────┤ │ • Laravel 13 & PHP 8.3 │ • Python 3.12+ & FastAPI (ASGI) │ │ • Server-Side Blade & Livewire 3 │ • React 19 + TypeScript + Vite 6 │ │ • Filament 3 Admin Panel │ • Dashboard Kustom Tailwind CSS v4 │ │ • Stateful Sessions & Cookie CSRF │ • Stateless OAuth2 Bearer JWT + RBAC │ │ • Laravel Reverb / Pusher WebSockets │ • Native ASGI WebSockets + Redis P/S │ │ • Eloquent ORM & Migrasi Laravel │ • SQLAlchemy 2.0 Asyncpg + Alembic │ │ • Validasi FormRequest │ • Pydantic v2 (Engine berbasis Rust) │ │ • Database MySQL 8 │ • PostgreSQL 16 + Cache Redis 7 │ │ • PhpSpreadsheet Batch Import │ • openpyxl Streaming Batch XLSX │ └──────────────────────────────────────┴──────────────────────────────────────┘

Perbandingan Arsitektur: Monolit vs. Layanan Async Terpisah

Berikut adalah perbandingan mendalam bagaimana setiap lapisan sistem berevolusi:

Aspek ArsitekturEdisi Monolitik LaravelEdisi Python FastAPI + React
Bahasa & RuntimePHP 8.3, PHP-FPM process-per-requestPython 3.12+ dengan Uvicorn (ASGI event loop, non-blocking async/await)
Framework BackendLaravel 13 MonolithFastAPI 0.111+ (Framework mikro asinkron berkecepatan tinggi)
Framework FrontendTemplate Blade + Komponen reaktif Livewire 3React 19 SPA + TypeScript + Vite 6 + Tailwind CSS v4
Validasi Data & DTOClass FormRequest bawaan LaravelPydantic v2 (Kompilasi engine Rust, serialisasi sub-milidetik)
AutentikasiCookie sesi PHP stateful + token CSRFStateless OAuth2 Bearer JWT dengan Engine Impersonasi 1-Klik
Otorisasi (RBAC)Gate & Model Policy LaravelDependency Injection FastAPI (Depends(require_roles([...])))
Database & MigrasiMySQL 8 dengan migrasi Artisan PHPPostgreSQL 16 dengan SQLAlchemy 2.0 Async Engine + Alembic
Caching & Pub/SubCache Redis via fasad Cache LaravelRedis 7 via connection pool async redis-py & channel Pub/Sub
Telemetri Real-TimeLaravel Reverb / Client EchoNative ASGI WebSockets (/ws/exams/{id}/monitor) via broadcast Redis
Pemrosesan BatchAntrean Laravel Queue + PhpSpreadsheetPembaca streaming openpyxl hemat RAM untuk template Excel .xlsx
Dokumentasi APIGenerator OpenAPI pihak ketiga (Scribe/Scramble)Standar OpenAPI 3.1 otomatis dengan Swagger UI interaktif (/docs) & ReDoc
Model DeploymentSatu kontainer / Linux VPS systemd unitMulti-kontainer Docker Compose (Nginx, React, FastAPI, Postgres, Redis, MinIO)

Cetak Biru Arsitektur Sistem

Berikut adalah rancangan arsitektur sistem decoupled siap-cloud yang diimplementasikan pada repositori Python FastAPI + React:

┌───────────────────────────────┐ │ CLIENT LAYER (SPA) │ │ React 19 + Tailwind CSS v4 │ └───────────────┬───────────────┘ │ HTTPS (REST) / WSS (WebSocket) │ ▼ ┌─────────────────────────────────────────────────────────────────────────────────────────────────┐ │ FASTAPI ASGI BACKEND (Uvicorn) │ │ │ │ ┌───────────────────────────────────────────────────────────────────────────────────────────┐ │ │ │ LAPISAN KEAMANAN & GATEWAY │ │ │ │ • OAuth2PasswordBearer + PyJWT (Validasi Stateless Bearer) │ │ │ │ • Hashing Password & Verifikasi Salt BCrypt via Passlib │ │ │ │ • Otorisasi Dependency Injection: Depends(require_roles(["ROLE_ADMIN", "ROLE_TEACHER"])) │ │ │ │ • Impersonasi Akun 1-Klik (/api/v1/admin/impersonate/{userId}) │ │ │ │ • Middleware Amplop Seragam ApiResponse[T] dengan Timestamp ISO-8601 │ │ │ └─────────────────────────────────────────────┬─────────────────────────────────────────────┘ │ │ │ │ │ ┌──────────────────────────────────────┼──────────────────────────────────────┐ │ │ ▼ ▼ ▼ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Router Auth │ │ Router Mapel │ │ Router Ujian │ │ │ │ & Pengguna │ │ & Materi │ │ CBT & Monitor│ │ │ │ - Siswa │ │ - Kelas │ │ - Runner API │ │ │ │ - Guru │ │ - Materi PDF │ │ - Anti-Cheat │ │ │ │ - Impersonate│ │ - Tugas │ │ - WebSockets │ │ │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌───────────────────────────────────────────────────────────────────────────────────────────┐ │ │ │ LAPISAN LAYANAN & LOGIKA BISNIS │ │ │ │ • ExcelService (Parser openpyxl impor massal siswa & bank soal) │ │ │ │ • ExamRunnerService (Kalkulasi timer server, autosave jawaban, scoring otomatis) │ │ │ │ • WebSocketManager (Broadcaster ke ruang pengawas ujian aktif dengan Redis Pub/Sub) │ │ │ └─────────────────────────────────────────────┬─────────────────────────────────────────────┘ │ │ │ │ │ ┌──────────────────────────────────────┴──────────────────────────────────────┐ │ │ ▼ ▼ │ │ ┌──────────────────────────────┐ ┌─────────────────┐ │ │ │ LAPISAN ORM ASYNC │ │ CACHE & PUBSUB │ │ │ │ SQLAlchemy 2.0 (asyncpg) │ │ Redis 7 Cache │ │ │ │ Migrasi Async Alembic │ │ Broker Pub/Sub │ │ │ └──────────────┬───────────────┘ └─────────┬───────┘ │ │ │ │ │ └─────────────────┼─────────────────────────────────────────────────────────────────────┼─────────┘ │ │ ▼ ▼ ┌────────────────────────┐ ┌────────────────────────┐ │ PostgreSQL 16 │ │ Dashboard Pengawas │ │ Database Relasional │ │ Telemetri Real-Time │ └────────────────────────┘ └────────────────────────┘

Sorotan Rekayasa Teknis Utama

1. Dependency Injection FastAPI: Stateless JWT & Engine Impersonasi 1-Klik

Pada aplikasi monolitik, proses admin berpindah peran (impersonation) biasanya bertumpu pada sesi stateful di server (Auth::loginUsingId($userId)). Pada Single Page Application (SPA), kita memerlukan token JWT stateless yang membawa hak akses yang pasti namun tetap mendukung fitur impersonasi dengan jejak audit yang jelas.

Pada file backend/app/api/v1/auth.py, endpoint impersonasi mencetak token sementara yang mengatasnamakan user target sambil merekam ID administrator asli di dalam payload token:

PYTHON

@router.post("/admin/impersonate/{user_id}", response_model=ApiResponse[AuthResponse])
async def impersonate(
    user_id: int,
    admin_user: User = Depends(require_roles(["ROLE_ADMIN"])),
    db: AsyncSession = Depends(get_db),
):
    stmt = (
        select(User)
        .options(
            selectinload(User.roles),
            selectinload(User.teacher_profile),
            selectinload(User.student_profile).selectinload(Student.classroom),
        )
        .where(User.id == user_id, User.deleted_at.is_(None))
    )
    target_user = (await db.execute(stmt)).scalars().first()
    if not target_user:
        raise HTTPException(status_code=404, detail="Target user not found")

    target_roles = [r.name for r in target_user.roles]
    token = create_access_token(
        subject=target_user.id,
        roles=target_roles,
        extra_claims={
            "is_impersonating": True,
            "original_admin_id": admin_user.id,
        },
    )

    auth_data = build_auth_response(target_user, token, admin_user.id)
    return ApiResponse.ok(data=auth_data, message=f"Impersonating {target_user.name}")

Pemulihan Sesi Otomatis

Ketika admin selesai melakukan investigasi kendala siswa, tombol keluar pada banner atas memanggil /api/v1/admin/stop-impersonate. Backend membaca klaim original_admin_id pada token, mengembalikan sesi admin semula dengan aman, dan mencabut hak akses siswa tanpa mengharuskan admin login ulang.


2. Engine Ujian CBT Presisi Tinggi: Anti-Cheat & Telemetri Native WebSocket

Logika eksekusi ujian pada backend/app/api/v1/exam_runner.py menerapkan aturan ketat di sisi server yang tidak dapat dimanipulasi oleh perubahan jam lokal di perangkat siswa:

Perhitungan Timer Terpusat di Server

PYTHON

now = datetime.utcnow()
total_duration_sec = exam.duration_minutes * 60

if attempt and attempt.status in ["in_progress"]:
    # Hitung selisih durasi tepat dari waktu mulai server
    elapsed = (now - attempt.started_at).total_seconds()
    remaining = max(0, int(total_duration_sec - elapsed))
    
    # Kunci batas waktu maksimal
    if remaining <= 0:
        attempt.status = "submitted"
        attempt.finished_at = now
        await db.commit()
        raise HTTPException(
            status_code=400, 
            detail="Batas waktu ujian telah habis"
        )

Deteksi Pelanggaran Anti-Cheat & Broadcast Native WebSocket

Di sisi frontend (frontend/src/pages/exams/ExamRunner.tsx), lingkungan browser siswa dikunci ketat:

  • Klik kanan dan kombinasi tombol copy-paste dicegah.
  • Layar blur atau perpindahan tab dideteksi melalui API HTML5 visibilitychange.
  • Pelanggaran dikirimkan ke server melalui /api/v1/exams/attempts/{id}/violation. Jika akumulasi pelanggaran mencapai 5 kali, ujian akan dihentikan dan disubmit otomatis oleh server.

PYTHON

# Handler pencatatan pelanggaran di server dengan broadcast instan
attempt.violations_count += 1
if attempt.violations_count >= 5:
    attempt.status = "submitted"
    attempt.finished_at = datetime.utcnow()

await db.commit()

# Broadcast event ke layar pengawas guru via WebSocketManager
await ws_manager.broadcast_to_exam(
    exam_id=attempt.examination_id,
    event_type="VIOLATION_LOGGED",
    data={
        "studentId": student.id,
        "studentName": student.name,
        "violationsCount": attempt.violations_count,
        "status": attempt.status,
    }
)

Manajer koneksi WebSocket meneruskan event ini ke seluruh layar guru pengawas yang sedang membuka monitoring ujian (ExamMonitor.tsx) dalam latensi sub-milidetik:

PYTHON

class ConnectionManager:
    def __init__(self):
        self.active_rooms: Dict[int, Set[WebSocket]] = {}

    async def broadcast_to_exam(self, exam_id: int, event_type: str, data: dict):
        if exam_id not in self.active_rooms:
            return

        payload = {"eventType": event_type, "data": data}
        message_str = json.dumps(payload, default=str)
        dead_connections = set()

        for connection in self.active_rooms[exam_id]:
            try:
                await connection.send_text(message_str)
            except Exception:
                dead_connections.add(connection)

        for dead in dead_connections:
            self.disconnect(exam_id, dead)

3. Pemrosesan Batch Hemat Memori dengan openpyxl

Ketika mengimpor ribuan data siswa baru atau ratusan butir soal ujian lengkap dengan opsi pilihan ganda dan kunci jawaban, skrip sinkron kerap membentur limit memori atau batas timeout eksekusi.

Pada backend FastAPI, kita memanfaatkan pustaka openpyxl dengan mode streaming (read_only=True) untuk membaca baris file Excel langsung dari memori byte buffer dan menyimpannya secara atomik ke database PostgreSQL:

PYTHON

from openpyxl import load_workbook
import io

@router.post("/students/import-excel", response_model=ApiResponse[Dict[str, int]])
async def import_students_excel(
    file: UploadFile = File(...),
    db: AsyncSession = Depends(get_db),
    admin: User = Depends(require_roles(["ROLE_ADMIN"])),
):
    content = await file.read()
    wb = load_workbook(filename=io.BytesIO(content), read_only=True, data_only=True)
    sheet = wb.active

    imported_count = 0
    # Iterasi baris data tanpa membebani memori struktur DOM XML
    for row in sheet.iter_rows(min_row=2, values_only=True):
        nis, name, email, gender, classroom_id = row[0], row[1], row[2], row[3], row[4]
        if not nis or not name:
            continue
        
        # Buat akun User + profil Siswa dalam transaksi terpadu
        await create_student_record(db, nis=str(nis), name=name, email=email, gender=gender, classroom_id=classroom_id)
        imported_count += 1

    await db.commit()
    return ApiResponse.ok(data={"imported": imported_count}, message="Impor data siswa berhasil")

4. SQLAlchemy 2.0 Async Engine & Kecepatan Validasi Pydantic v2

Berbeda dengan ORM generasi lama yang menyamarkan pemanggilan I/O pemblokir di balik fungsi getter relasi, SQLAlchemy 2.0 mewajibkan penulisan kueri asinkron yang eksplisit dan aman:

PYTHON

# Kueri join asinkron dengan eager loading relasi terkait
stmt = (
    select(Examination)
    .options(
        selectinload(Examination.teacher),
        selectinload(Examination.subject),
        selectinload(Examination.classroom),
        selectinload(Examination.questions),
    )
    .where(Examination.id == exam_id, Examination.deleted_at.is_(None))
)
result = await db.execute(stmt)
exam = result.scalars().first()

Dipadukan dengan Pydantic v2, seluruh struktur request dan respon divalidasi oleh mesin inti berbasis bahasa pemrograman Rust (pydantic-core). Hasilnya, kecepatan parsing data meningkat drastis antara 5 hingga 15 kali lipat lebih cepat dibandingkan pustaka validasi Python konvensional.


Arsitektur Frontend: React 19, TypeScript & Tailwind CSS v4

Aplikasi antarmuka client (frontend/) dirancang agar terasa sangat cepat, bersih, dan optimal baik di komputer laboratorium sekolah maupun di ponsel pintar siswa:

  1. Aplikasi SPA React 19 Modern: Dibangun di atas Vite 6 dengan modul Hot Module Replacement (HMR) secepat kilat dan antarmuka TypeScript yang ketat.
  2. Desain Bernuansa Emerald: Palet warna slate dan emerald profesional (#059669, #10b981) menggunakan Tailwind CSS v4 yang konsisten dengan estetika platform pendidikan modern.
  3. Penyimpanan Status Terpusat via Zustand: Menyimpan token JWT dan status impersonasi secara persisten lintas tab browser.
  4. Palet Nomor Soal Interaktif: Navigasi sentuh cepat ke puluhan butir soal ujian dengan indikator visual langsung:
    • 🟢 Hijau: Soal telah dijawab dan tersimpan aman di server
    • ⚪ Abu-abu: Soal belum dikerjakan
    • 🟡 Kuning: Soal ditandai ragu-ragu untuk ditinjau ulang
  5. Cetak Kartu Peserta Ujian Resmi: Fasilitas cetak kartu ujian resmi lengkap dengan barcode NIS siswa siap pakai untuk verifikasi fisik di meja ujian.

Panduan Memilih Stack: Laravel vs. Java Spring Boot vs. Python FastAPI

Setelah membangun sistem yang sama persis di ketiga ekosistem teknologi tersebut, berikut adalah panduan perbandingan objektif bagi para engineering lead:

Skenario & KebutuhanStack yang DirekomendasikanAlasan & Landasan Teknis
Peluncuran Cepat / Solo DeveloperLaravel 13 + Livewire + FilamentKecepatan pengembangan tak tertandingi, panel admin bawaan lengkap, tanpa boilerplate rumit dalam satu repo.
Konkurensi Masif & Efisiensi RAMPython 3.12 FastAPI + React 19Event loop asinkron melayani ribuan koneksi I/O simultan dengan konsumsi RAM sangat hemat (~20MB); ideal untuk kontainer mikro.
Integrasi AI, NLP & Machine LearningPython 3.12 FastAPI + React 19Kompatibel langsung dengan ekosistem PyTorch, Hugging Face, LangChain, dan model penilaian esai otomatis.
Kepatuhan Enterprise & PerbankanJava 21 Spring Boot + React 19Keamanan tipe statis mutlak saat kompilasi, Virtual Threads (Project Loom), filter Spring Security deklaratif, dukungan LTS enterprise.
Ekosistem Aplikasi Mobile TerpisahFastAPI atau Spring Boot + ReactSpesifikasi OpenAPI yang terstruktur ketat untuk pembuatan otomatis SDK mobile (iOS/Android) dan web.

Repositori Open-Source

Ketiga edisi implementasi platform ini tersedia secara open-source di bawah lisensi MIT:


Menjalankan Proyek Secara Lokal

Anda dapat menjalankan seluruh lingkungan proyek Python FastAPI + React dengan Docker Compose dalam satu perintah praktis:

BASH

# Clone repositori
git clone https://github.com/mfarim/fastapi-elearning-react.git
cd fastapi-elearning-react

# Jalankan seluruh service (PostgreSQL, Redis, MinIO, Backend FastAPI, Frontend React)
docker compose up -d --build

Akses layanan yang berjalan:

Kredensial Akun Demo Bawaan:


Penutup & Langkah Selanjutnya

Membangun ulang platform Laravel E-Learning ke dalam Python FastAPI dan React membuktikan bagaimana arsitektur asinkron modern mampu menghadirkan skalabilitas masif dan pengalaman pengembangan yang sangat menyenangkan. Python kini bukan lagi sekadar bahasa skrip atau analisis data—dengan kombinasi FastAPI dan ASGI, Python telah menjelma menjadi mesin tangguh untuk aplikasi terdistribusi modern berkecepatan tinggi.

Pada Part 2, kita akan membedah secara spesifik proses migrasi skema database: dari model Eloquent Laravel menuju model asinkron SQLAlchemy 2.0 dan skrip migrasi Alembic. Sampai jumpa di artikel berikutnya!

Lanjut Membaca

Previous article thumbnail

← Artikel Sebelumnya

Rebuilding Laravel E-Learning to Java Spring Boot & React: Enterprise Architecture & Cloud-Ready Migration

Artikel Selanjutnya →

Fleet Management System Part 1: Introduction & System Architecture

Next article thumbnail