MUHAMMAD
FARI
MADYAN
[ Press ESC or Click to Skip ]

Rebuild Ruby on Rails + React E-Learning

1

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

2

Part 2: Penerapan Clean Architecture di Ruby on Rails & React: Repository Pattern, Use Cases, Result Monad & Typed Services (PR #1)

Artikel ini tersedia dalam Bahasa Inggris

🇬🇧 Read in English
🇮🇩 Bahasa Indonesia📚 Rebuild Ruby on Rails + React E-Learning

Membangun Ulang Laravel E-Learning ke Ruby on Rails & React: Convention over Configuration, Multithreading Puma & Migrasi Cloud-Ready

Panduan lengkap membangun ulang platform monolitik Laravel E-Learning & CBT ke arsitektur multithreaded cloud-ready performa tinggi dengan Ruby 3.3, Ruby on Rails 8.1 API, React 19, Tailwind CSS v4, PostgreSQL 16, Redis 7, dan telemetri proctoring real-time Action Cable.

Muhammad Fari MadyanPenulis

15 menit baca

·

9 Oktober 2026


Latar Belakang & Motivasi: Kecepatan Developer Berpadu Efisiensi Multithreading

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 pendamping enterprise berbasis Java 21 Spring Boot & React dan Python 3.12 FastAPI & 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 lebih luas—seperti jaringan sekolah tingkat kabupaten/distrik, ujian bersama antarlembaga pendidikan, dan lonjakan konkurensi serentak, beberapa pertimbangan arsitektur mulai muncul ke permukaan:

  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. Keunggulan Konkurensi Multithreading Puma: Ruby on Rails 8 yang berjalan di atas Puma web server menggabungkan proses worker klaster dengan thread eksekusi konkuren (secara default 5 thread per worker). Model konkurensi hybrid ini memungkinkan Rails menangani banyak kueri database dan panggilan I/O secara bersamaan di dalam satu proses worker tunggal, menghemat konsumsi memori secara signifikan dibanding PHP-FPM.
  3. Filosofi Legendaris Convention over Configuration: Ruby on Rails adalah pelopor idiom arsitektur yang menginspirasi berbagai framework web modern—termasuk Laravel sendiri. Active Record adalah salah satu implementasi Object-Relational Mapping (ORM) paling ekspresif dan elegan dalam rekayasa perangkat lunak. Dengan mengaktifkan Rails 8 dalam mode API-only (--api), kita memangkas middleware aset, sesi, dan cookie yang tidak diperlukan untuk menghasilkan backend JSON yang sangat ramping dengan kecepatan pengembangan (developer velocity) tiada tanding.
  4. Telemetri Pengawasan Real-Time via Action Cable: Guru pengawas di ruang kontrol memerlukan telemetri langsung (sisa waktu ujian, status palet nomor soal yang telah dijawab, serta peringatan pelanggaran kecurangan seperti keluar layar penuh atau ganti tab). Rails menyediakan integrasi WebSocket kelas satu melalui Action Cable dan Redis Pub/Sub, menghadirkan komunikasi dua arah tanpa beban polling dari sisi klien.
  5. Ekosistem Multi-Platform yang Terpisah: Lembaga pendidikan modern memerlukan antarmuka web modern dan aplikasi mobile khusus (Android/iOS) yang mengakses endpoint REST API dan WebSocket yang sama dengan tipe data yang konsisten.

Untuk menjawab kebutuhan operasional tersebut, saya membangun edisi pendamping keempat dalam series ini: Ruby on Rails 8 + React E-Learning.

┌─────────────────────────────────────────────────────────────────────────────┐ │ RINGKASAN EVOLUSI ARSITEKTUR SISTEM │ ├──────────────────────────────────────┬──────────────────────────────────────┤ │ SUMBER: Stack Monolitik │ TARGET: Stack Rails API Terpisah │ │ (https://github.com/mfarim/ │ (https://github.com/mfarim/ │ │ laravel-elearning) │ ruby-rails-elearning-react) │ ├──────────────────────────────────────┼──────────────────────────────────────┤ │ • Laravel 13 & PHP 8.3 │ • Ruby 3.3+ & Ruby on Rails 8.1 API │ │ • Server-Side Blade & Livewire 3 │ • React 19 + TypeScript + Vite 6 │ │ • Filament 3 Admin Panel │ • Dashboard Kustom Tailwind CSS v4 │ │ • Stateful Sessions & Cookie CSRF │ • Stateless JWT (HS256) + RBAC │ │ • Laravel Reverb / Pusher WebSockets │ • Action Cable + Redis 7 Pub/Sub │ │ • Eloquent ORM & Migrasi Laravel │ • Active Record + PostgreSQL 16 │ │ • Model PHP-FPM Process-Per-Request │ • Server Multithreaded Puma Klaster │ │ • Database MySQL 8 │ • PostgreSQL 16 + Cache Redis 7 │ │ • PhpSpreadsheet Batch Import │ • Roo Streaming Excel (.xlsx/.csv) │ │ • Pest PHP Automated Testing │ • RSpec 7 + FactoryBot + DatabaseCl. │ └──────────────────────────────────────┴──────────────────────────────────────┘

Perbandingan Arsitektur: Monolit vs. Layanan Rails Terpisah

Berikut adalah perbandingan mendalam bagaimana setiap lapisan arsitektur bertransformasi dari monolit Laravel ke edisi terpisah Ruby on Rails 8 + React:

Aspek ArsitekturEdisi Monolitik LaravelEdisi Ruby on Rails 8 + React
Bahasa & RuntimePHP 8.3 (Interpreted / OPcache), model proses-per-request PHP-FPMRuby 3.3+ dengan YJIT, Puma multithreaded clustered server (5 thread/worker)
Framework BackendLaravel 13 MonolithRuby on Rails 8.1 (Mode API-only, middleware minimalis)
Framework FrontendTemplate Blade + Komponen reaktif Livewire 3React 19 SPA + TypeScript + Vite 6 + Tailwind CSS v4
Serialisasi DataLaravel API Resource (JsonResource)Serialisasi Active Record dengan konversi otomatis snake_case ke camelCase
OtentikasiStateful PHP session cookies + token CSRFStateless JWT Bearer token dengan Engine Impersonasi Administratif 1-Klik
Otorisasi (RBAC)Laravel Gate & Model Policy (#[Authorize])Guard Controller (require_admin!, require_teacher!, require_student!)
Database & MigrasiMySQL 8 dengan migrasi Artisan LaravelPostgreSQL 16 dengan migrasi Active Record (db:migrate)
Lapisan CachingCache Redis via fasad Cache LaravelRedis 7 via connection pool gem redis
Telemetri Real-TimeLaravel Reverb / Klien EchoAction Cable WebSocket (/cable) dengan adapter Redis Pub/Sub
Pemrosesan BatchAntrean Laravel + PhpSpreadsheetGem roo streaming batch parser untuk template .xlsx siswa & bank soal
Generasi DokumenPhpSpreadsheet / DomPDFGem caxlsx untuk kartu ujian peserta ber-barcode
Pengujian OtomatisPest PHP + Laravel TestCaseRSpec (rspec-rails 7.1) + FactoryBot + DatabaseCleaner
Standar Kualitas KodeLaravel Pint (PHP-CS-Fixer)RuboCop Rails Omakase (rubocop-rails-omakase)
Model DeploymentContainer tunggal / unit systemd Linux VPSMulti-container Docker Compose (Nginx, React, Rails API, Postgres, Redis)

Blueprint Arsitektur Sistem

Berikut adalah arsitektur cloud-ready yang diimplementasikan pada repositori Ruby on Rails 8 + React:

┌───────────────────────────────┐ │ LAPISAN KLIEN (SPA) │ │ React 19 + Tailwind CSS v4 │ │ Zustand 5 State Management │ └───────────────┬───────────────┘ │ HTTPS (REST) / WSS (Action Cable) │ ▼ ┌─────────────────────────────────────────────────────────────────────────────────────────────────┐ │ PUMA WEB SERVER (PORT 8000) │ │ Klaster Worker + Multithreaded │ │ │ │ ┌───────────────────────────────────────────────────────────────────────────────────────────┐ │ │ │ LAPISAN KEAMANAN & DISPATCH │ │ │ │ • Middleware Rack::Cors (Perlindungan Cross-Origin) │ │ │ │ • Service JsonWebToken: Decoder Token Bearer HS256 & Verifikasi Klaim │ │ │ │ • Api::V1::BaseController: authenticate_user!, require_admin!, camelize_keys │ │ │ │ • Impersonasi Akun 1-Klik (/api/v1/auth/impersonate/:user_id) │ │ │ └─────────────────────────────────────────────┬─────────────────────────────────────────────┘ │ │ │ │ │ ┌──────────────────────────────────────┼──────────────────────────────────────┐ │ │ ▼ ▼ ▼ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Controller │ │ Akademik & │ │ Ujian CBT & │ │ │ │ Auth & User │ │ Materi │ │ Pengawasan │ │ │ │ - Siswa │ │ - Kelas │ │ - Runner API │ │ │ │ - Guru │ │ - Materi │ │ - Anti-Cheat │ │ │ │ - Impersonate│ │ - Tugas │ │ - Telemetri │ │ │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌───────────────────────────────────────────────────────────────────────────────────────────┐ │ │ │ LAPISAN LOGIKA BISNIS & SERVICE │ │ │ │ • ExcelService (Roo Streaming Parser untuk Data Siswa & Bank Soal) │ │ │ │ • Engine Penilaian Ujian (Pilihan Ganda, Benar/Salah & Evaluasi Esai) │ │ │ │ • Generator Kartu Peserta Ujian Resmi Ber-Barcode via Caxlsx │ │ │ └─────────────────────────────────────────────┬─────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌───────────────────────────────────────────────────────────────────────────────────────────┐ │ │ │ ACTIVE RECORD ORM & PERSISTENSI │ │ │ │ • User, Student, Teacher, Classroom, Subject, Examination, Question, ExamAttempt │ │ │ │ • Connection Pool PostgreSQL (Gem pg, Prepared Statements, Transaksi Ketat) │ │ │ └───────────────────────────────┬───────────────────────────┬───────────────────────────────┘ │ └──────────────────────────────────┼───────────────────────────┼──────────────────────────────────┘ │ │ ▼ ▼ ┌────────────────────────────┐ ┌────────────────────────────┐ │ POSTGRESQL 16 │ │ REDIS 7 │ │ Persistensi Relasional │ │ Pub/Sub Action Cable │ │ B-Tree Foreign Keys │ │ Sesi & Telemetri │ └────────────────────────────┘ └────────────────────────────┘

Sorotan Teknis & Pembahasan Mendalam (Deep Dives)

Deep Dive 1: Otentikasi Stateless JWT & Engine Impersonasi 1-Klik

Pada operasional sekolah, administrator kerap kali perlu melakukan troubleshooting langsung saat siswa mengalami kendala di tengah sesi ujian atau memverifikasi tampilan materi dari sudut pandang guru. Alih-alih meminta kata sandi pengguna atau mengubah database secara manual, backend Rails menyediakan engine impersonasi administratif 1-klik:

RUBY

# backend/app/services/json_web_token.rb
class JsonWebToken
  SECRET_KEY = ENV.fetch("SECRET_KEY") { 
    Rails.application.secret_key_base || "ruby-rails-elearning-secret-key-2026-super-secure" 
  }

  def self.encode(payload, exp = 24.hours.from_now)
    payload[:exp] = exp.to_i
    JWT.encode(payload, SECRET_KEY, 'HS256')
  end

  def self.decode(token)
    decoded = JWT.decode(token, SECRET_KEY, true, { algorithm: 'HS256' })[0]
    HashWithIndifferentAccess.new(decoded)
  rescue JWT::DecodeError
    nil
  end
end

Saat admin mengklik tombol Impersonate di dashboard React, permintaan dikirim ke AuthController#impersonate. API Rails memastikan izin administratif, lalu menerbitkan JWT baru atas nama target pengguna dengan mencatat ID admin asli pada klaim impersonated_by:

RUBY

# backend/app/controllers/api/v1/auth_controller.rb
module Api
  module V1
    class AuthController < BaseController
      skip_before_action :authenticate_user!, only: [:login, :register]

      def impersonate
        require_admin!
        return if performed?

        target_user = User.find(params[:user_id])
        token = JsonWebToken.encode(user_id: target_user.id)
        payload = build_auth_payload(target_user, token, impersonated_by: current_user.id)
        json_response(data: payload, message: "Impersonation started successfully")
      end

      def stop_impersonate
        admin_user = User.find_by(role: 'ROLE_ADMIN') || current_user
        token = JsonWebToken.encode(user_id: admin_user.id)
        payload = build_auth_payload(admin_user, token)
        json_response(data: payload, message: "Returned to admin session")
      end

      private

      def build_auth_payload(user, token, impersonated_by: nil)
        {
          accessToken: token,
          refreshToken: token,
          tokenType: "Bearer",
          userId: user.id,
          name: user.name,
          email: user.email,
          roles: [user.role],
          active: user.active,
          impersonatedBy: impersonated_by || user.impersonated_by_id,
          token: token
        }
      end
    end
  end
end

Untuk menjembatani perbedaan konvensi penamaan antara model Ruby yang menggunakan snake_case dengan TypeScript frontend yang menggunakan camelCase, BaseController melakukan transformasi otomatis pada seluruh respon JSON:

RUBY

# backend/app/controllers/api/v1/base_controller.rb
def json_response(data: nil, message: "Success", status: :ok, errors: nil)
  camelized_data = camelize_keys(data.as_json)

  render json: {
    success: [:ok, :created, 200, 201].include?(status),
    message: message,
    data: camelized_data,
    errors: errors,
    timestamp: Time.current.iso8601
  }, status: status
end

def camelize_keys(obj)
  case obj
  when Hash
    obj.each_with_object({}) do |(k, v), result|
      result[k.to_s.camelize(:lower)] = camelize_keys(v)
    end
  when Array
    obj.map { |item| camelize_keys(item) }
  else
    obj
  end
end

Deep Dive 2: Engine Ujian CBT Konkurensi Tinggi & Pelacakan Anti-Cheat

Engine Computer Based Testing di ExamRunnerController mengelola sesi ujian serentak dengan penegakan timer otoritatif dari sisi server dan pencatatan pelanggaran integritas:

  1. Timer Otoritatif di Server: Sisa waktu ujian tidak pernah bergantung pada jam perangkat siswa (menghindari kecurangan manipulasi jam lokal). Saat siswa memulai atau me-refresh ujian, sisa waktu dihitung murni di server:

    RUBY

    elapsed = (Time.current - attempt.started_at).to_i
    remaining = [exam.duration_minutes * 60 - elapsed, 0].max
    
  2. Auto-Save Jawaban Seketika: Setiap kali siswa memilih opsi pada tampilan soal React, permintaan asinkron langsung menyimpan jawaban via find_or_initialize_by:

    RUBY

    # backend/app/controllers/api/v1/exam_runner_controller.rb
    def answer
      require_student!
      return if performed?
    
      attempt = ExamAttempt.find(params[:attempt_id])
      return render_error("Attempt is already submitted") if attempt.status != 'in_progress'
    
      question = Question.find(params[:questionId])
      ans = ExamAnswer.find_or_initialize_by(exam_attempt: attempt, question: question)
      ans.student_answer = params[:answer].to_s
      ans.save!
    
      json_response(data: nil, message: "Answer saved")
    end
    
  3. Pencatatan Pelanggaran Anti-Cheat: Ketika antarmuka React mendeteksi siswa beralih tab, meminimalkan browser, atau keluar dari mode layar penuh (fullscreen), pelanggaran langsung dicatat ke server:

    RUBY

    def violation
      require_student!
      return if performed?
    
      attempt = ExamAttempt.find(params[:attempt_id])
      attempt.increment!(:violations_count)
      json_response(data: { violations: attempt.violations_count }, message: "Violation recorded")
    end
    
  4. Pipeline Submit & Penilaian Otomatis: Saat waktu habis atau siswa mengklik tombol Selesai, soal pilihan ganda dan benar/salah dinilai secara instan. Jika terdapat soal esai, status ujian ditandai sebagai needs_grading agar guru dapat memberikan nilai esai secara manual:

    RUBY

    def submit
      require_student!
      return if performed?
    
      attempt = ExamAttempt.find(params[:attempt_id])
      exam = attempt.examination
    
      questions = exam.questions.index_by(&:id)
      answers = attempt.exam_answers.includes(:question)
    
      total_points = questions.values.sum(&:points)
      earned_points = 0.0
      has_essay = false
    
      answers.each do |ans|
        q = ans.question
        next unless q
    
        if q.question_type == 'multiple_choice' || q.question_type == 'true_false'
          is_correct = ans.student_answer.to_s.strip.downcase == q.correct_answer.to_s.strip.downcase
          ans.update!(
            is_correct: is_correct,
            points_awarded: is_correct ? q.points : 0.0,
            graded_at: Time.current
          )
          earned_points += q.points if is_correct
        elsif q.question_type == 'essay'
          has_essay = true
        end
      end
    
      score_pct = total_points > 0 ? (earned_points / total_points * 100.0).round(2) : 0.0
      passed = score_pct >= exam.passing_score
    
      attempt.update!(
        score: score_pct,
        passed: passed,
        finished_at: Time.current,
        status: has_essay ? 'needs_grading' : 'completed',
        remaining_seconds: 0
      )
    
      json_response(data: {
        score: attempt.score,
        passed: attempt.passed,
        status: attempt.status,
        totalQuestions: questions.size,
        answeredCount: answers.size
      }, message: "Exam submitted successfully")
    end
    

Deep Dive 3: Pemrosesan Batch Excel Streaming dengan Gem Roo

Mendaftarkan ratusan siswa baru dan mengimpor puluhan bank soal tidak boleh memblokir thread worker Puma. Dengan menggunakan gem roo, backend membaca file .xlsx, .xls, dan .csv secara efisien tanpa pemborosan memori:

RUBY

# backend/app/services/excel_service.rb
require 'roo'

class ExcelService
  def self.parse_students(file_path)
    spreadsheet = open_spreadsheet(file_path)
    header = spreadsheet.row(1).map { |h| h.to_s.strip.downcase.underscore }
    rows = []

    (2..spreadsheet.last_row).each do |i|
      row = Hash[[header, spreadsheet.row(i)].transpose]
      next if row['name'].blank? || row['nis'].blank?
      rows << row
    end

    rows
  end

  def self.parse_questions(file_path)
    spreadsheet = open_spreadsheet(file_path)
    header = spreadsheet.row(1).map { |h| h.to_s.strip.downcase.underscore }
    rows = []

    (2..spreadsheet.last_row).each do |i|
      row = Hash[[header, spreadsheet.row(i)].transpose]
      next if row['question_text'].blank?
      rows << row
    end

    rows
  end

  private

  def self.open_spreadsheet(file_path)
    ext = File.extname(file_path).downcase
    case ext
    when '.xlsx' then Roo::Excelx.new(file_path)
    when '.xls'  then Roo::Excel.new(file_path)
    when '.csv'  then Roo::CSV.new(file_path)
    else
      raise "Unsupported file format: #{ext}. Please upload .xlsx, .xls, or .csv"
    end
  end
end

Pada StudentsController, proses impor dijalankan di dalam blok transaksi PostgreSQL (User.transaction do ... end). Akun User dan profil relasional Student dibuat secara atomik sehingga mencegah terjadinya data yatim (orphaned records) apabila terdapat anomali pada baris data:

RUBY

# backend/app/controllers/api/v1/students_controller.rb
def import_excel
  require_admin!
  return if performed?

  file = params[:file]
  return render_error("No file uploaded") unless file

  rows = ExcelService.parse_students(file.tempfile.path)
  imported_count = 0

  User.transaction do
    rows.each do |r|
      user = User.find_or_create_by!(email: "#{r['nis']}@elearning.test") do |u|
        u.name = r['name']
        u.password = "password123"
        u.role = 'ROLE_STUDENT'
        u.active = true
      end

      student = Student.find_or_initialize_by(user: user)
      student.name = r['name']
      student.email = user.email
      student.nis = r['nis']
      student.gender = r['gender'].presence || 'M'
      student.classroom_id = params[:classroom_id] if params[:classroom_id].present?
      student.active = true
      student.save!

      imported_count += 1
    end
  end

  json_response(data: { importedCount: imported_count }, message: "Imported #{imported_count} students successfully")
end

Deep Dive 4: Frontend Modern Terpisah (React 19 + TypeScript + Vite 6)

Aplikasi klien frontend dibangun sebagai Single Page Application (SPA) modern menggunakan:

  • React 19: Memanfaatkan fitur hooks fungsional terbaru.
  • TypeScript 5: Menjamin type safety ketat di seluruh kontrak API dan DTO.
  • Tailwind CSS v4: Didukung oleh engine native @tailwindcss/vite untuk build super cepat.
  • Zustand 5: State management terpusat untuk sesi otentikasi dan runner ujian (useAuthStore).
  • Axios Interceptor: Penempelan token JWT otomatis dan pemulihan sesi saat impersonasi.

TYPESCRIPT

// frontend/src/services/api.ts
import axios from 'axios';
import { useAuthStore } from '../store/authStore';

const api = axios.create({
  baseURL: import.meta.env.VITE_API_URL || 'http://localhost:8000/api/v1',
  headers: {
    'Content-Type': 'application/json',
  },
});

api.interceptors.request.use((config) => {
  const token = useAuthStore.getState().token;
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

api.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response?.status === 401) {
      useAuthStore.getState().logout();
    }
    return Promise.reject(error);
  }
);

export default api;

Tampilan Runner Ujian Siswa (Mobile-First)

Komponen runner ujian siswa didesain responsif dengan fokus mobile-first:

  • Palet Soal Dinamis: Tombol nomor soal berwarna hijau (telah dijawab) dan abu-abu (belum dijawab).
  • Countdown Tersinkronisasi: Menjalankan auto-submit otomatis saat sisa waktu mencapai 00:00:00.
  • Listener Anti-Cheat: Menangkap event browser seperti document.addEventListener('visibilitychange') dan window.addEventListener('blur') untuk mengirim notifikasi pelanggaran ke server Rails.

Matriks Perbandingan 4 Framework Arsitektur

Dengan hadirnya empat edisi pada series ini, berikut adalah perbandingan komprehensif seluruh framework yang telah dibangun:

Parameter ArsitekturLaravel 13 MonolithJava 21 Spring Boot 3.4Python 3.12 FastAPIRuby on Rails 8.1 API
Pola ArsitekturServer-Side MonolithDecoupled REST + SPADecoupled Async REST + SPADecoupled REST + SPA
Model KonkurensiProcess-per-request (PHP-FPM)Virtual Threads (Project Loom)Non-blocking Event Loop (ASGI)Multithreaded Klaster (Puma)
Konsumsi RAM Worker40MB – 80MB200MB – 400MB (JVM Base)15MB – 35MB50MB – 90MB (5 thread)
Lapisan Akses DataEloquent ORM (Active Record)Spring Data JPA + HibernateSQLAlchemy 2.0 AsyncpgActive Record (Ruby)
Verifikasi Tipe DataDinamis / PHPStan Level 8Statis (Kompilasi Java)Runtime Pydantic v2 (Engine Rust)Dinamis + Kontrak RSpec
Protokol Real-TimeReverb / Pusher WebSocketsSTOMP via SockJS (/ws/cbt)Native ASGI WebSocketsAction Cable + Redis PubSub
Kecepatan Developer★★★★★ (Filament CRUD Instan)★★★☆☆ (Boilerplate Ketat)★★★★☆ (Routing Cepat Typed)★★★★★ (Convention over Config)
Kesesuaian KasusSatu sekolah, MVP cepatSistem enterprise kepatuhan tinggiLMS berbasis AI & data scienceStartup berkembang cepat

Panduan Menjalankan dengan Docker Compose

Seluruh arsitektur (PostgreSQL 16, Redis 7, Rails 8 API, dan Frontend React 19) dapat dijalankan dalam beberapa langkah mudah:

BASH

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

# 2. Salin konfigurasi environment
cp .env.example .env

# 3. Jalankan seluruh container
docker compose up -d --build

# 4. Pantau log backend Rails
docker compose logs -f backend

Setelah container aktif, buka di browser:

Kredensial Akun Uji Coba (Demo)

Peran (Role)EmailPassword
Administrator[email protected]password123
Guru (Teacher)[email protected]password123
Siswa (Student)[email protected]password123

Menjalankan Test Suite (RSpec)

Backend Rails dilengkapi rangkaian pengujian otomatis untuk memverifikasi otentikasi, alur ujian, dan transaksi Active Record:

BASH

cd backend
bundle install
bundle exec rspec

Kesimpulan & Langkah Selanjutnya

Membangun ulang platform Laravel E-Learning ke dalam Ruby on Rails 8 dan React membuktikan bahwa kecepatan pengembangan (developer happiness) tidak harus mengorbankan skalabilitas arsitektur:

  • Mode API Rails 8 menyediakan backend JSON yang bersih dan bebas dari beban pipeline monolitik lama.
  • Arsitektur multithreaded Puma menyerap lonjakan konkurensi ujian serentak dengan efisiensi memori tinggi.
  • Otentikasi stateless JWT dengan impersonasi administratif 1-klik mempermudah pengawasan sesi ujian siswa.
  • Active Record dan streaming batch Roo menjaga logika domain tetap bersih, ekspresif, dan mudah dirawat.

Pada seri berikutnya, kita akan membahas detail migrasi skema database, relasi kompleks Active Record, konfigurasi channel real-time Action Cable, hingga optimasi performa container di lingkungan produksi.

Kunjungi repositori open-source pendamping:

Lanjut Membaca

Previous article thumbnail

← Artikel Sebelumnya

Rebuilding Laravel E-Learning to Python FastAPI & React: High-Concurrency Async Architecture & Cloud-Ready Migration

Artikel Selanjutnya →

Fleet Management System Part 1: Introduction & System Architecture

Next article thumbnail