MUHAMMAD
FARI
MADYAN
[ Press ESC or Click to Skip ]

Python FastAPI + React E-Learning Rebuild

1

Part 1: Introduction & Architecture: Monolith to Decoupled Async Micro-Services

2

Part 2: Domain Modeling & Async Database Schema with SQLAlchemy 2.0 & Alembic

3

Part 3: Stateless JWT Security, RBAC & 1-Click Impersonation Engine

4

Part 4: CBT Exam Engine, Anti-Cheat & Native WebSocket Live Proctoring

5

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

6

Part 6: High-Performance openpyxl Batch Processing & Docker Production Deployment

This article is available in Indonesian

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

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

A comprehensive guide on rebuilding a monolithic Laravel E-Learning & CBT platform into a high-concurrency, cloud-ready async architecture using Python 3.12, FastAPI, SQLAlchemy 2.0 Async, Pydantic v2, React 19, Tailwind CSS v4, Redis, and native WebSocket proctoring.

Muhammad Fari MadyanAuthor

12 min read

·

Oct 4, 2026


The Motivation: Scaling Beyond Synchronous Worker Limits

Earlier in this blog series, I documented the complete transformation of my 2017 legacy junior high school examination platform from Laravel 5.2 to modern Laravel 13, Filament 3, and Livewire 3 (open-sourced at mfarim/laravel-elearning), followed by an enterprise companion edition built with Java 21 Spring Boot & React.

The monolithic Laravel platform remains exceptionally productive:

  • Filament 3 enables administrative CRUD dashboards in minutes.
  • Livewire 3 brings reactive question timers without maintaining complex frontend build pipelines.
  • Pest PHP provides solid test coverage and IDOR vulnerability hardening.

However, when scaling high-stakes Computer Based Testing (CBT) and Learning Management Systems (LMS) across academic districts, province-wide standardized testing, and AI-assisted educational tooling, a new architectural challenge arises:

  1. The "Exam Rush Hour" Concurrency Bottleneck: When 2,000 to 10,000 candidates click "Start Exam" at precisely 08:00:00 AM, traditional synchronous PHP-FPM workers allocate one full operating system process per request (consuming 40MB–80MB RAM per worker). Spawning thousands of synchronous workers causes process exhaustion, OS context-switching thrashing, and high server costs.
  2. Persistent Asynchronous Real-Time Proctoring: Teachers supervising exam halls require continuous live telemetry (remaining timer, current question palette, anti-cheat tab-switch flags). Maintaining thousands of persistent real-time connections requires an asynchronous non-blocking event loop that handles thousands of concurrent connections with negligible memory overhead (~15MB–25MB).
  3. First-Class AI & Data Science Ecosystem: Modern academic platforms are rapidly integrating automated grading, NLP essay evaluation, semantic question similarity search, and automated difficulty calibration. Python is the undisputed lingua franca of the modern AI/ML and data science ecosystem.
  4. Decoupled Multi-Platform Client Ecosystem: Institutions demand progressive web apps and mobile apps (iOS/Android) consuming identical, strongly typed, self-documenting REST and WebSocket APIs.

To meet these demands, I engineered the third companion platform in this series: Python FastAPI + React E-Learning.

┌─────────────────────────────────────────────────────────────────────────────┐ │ ARCHITECTURAL EVOLUTION OVERVIEW │ ├──────────────────────────────────────┬──────────────────────────────────────┤ │ SOURCE: Monolithic Stack │ TARGET: Decoupled Async Stack │ │ (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 │ • Custom Tailwind CSS v4 Dashboards │ │ • Stateful Sessions & CSRF Cookies │ • Stateless OAuth2 Bearer JWT + RBAC │ │ • Laravel Reverb / Pusher WebSockets │ • Native ASGI WebSockets + Redis P/S │ │ • Eloquent ORM & Laravel Migrations │ • SQLAlchemy 2.0 Asyncpg + Alembic │ │ • FormRequests Validation │ • Pydantic v2 (Rust-backed engine) │ │ • MySQL 8 Database │ • PostgreSQL 16 + Redis 7 Cache │ │ • Maatwebsite / PhpSpreadsheet │ • openpyxl Memory-Optimized Batch │ └──────────────────────────────────────┴──────────────────────────────────────┘

Architectural Comparison: Monolith vs. Async Decoupled Architecture

Here is how each critical layer transitions between the two architectures:

Architectural ConcernLaravel Monolithic EditionPython FastAPI + React Edition
Language & RuntimePHP 8.3, PHP-FPM process-per-requestPython 3.12+ with Uvicorn (ASGI event loop, non-blocking async/await)
Backend FrameworkLaravel 13 MonolithFastAPI 0.111+ (High-performance micro-framework)
Frontend FrameworkBlade templates + Livewire 3 reactive componentsReact 19 SPA + TypeScript + Vite 6 + Tailwind CSS v4
Data Validation & DTOsLaravel FormRequest classesPydantic v2 (Rust-powered compilation, sub-millisecond parsing)
AuthenticationStateful PHP session cookies + CSRF tokensStateless OAuth2 Bearer JWT with 1-Click Impersonation Engine
AuthorizationLaravel Gate & Model PoliciesFastAPI Dependency Injection (Depends(require_roles([...])))
Database & MigrationsMySQL 8 with Laravel Artisan migrationsPostgreSQL 16 with SQLAlchemy 2.0 Async Engine + Alembic
Caching & Pub/SubRedis cache via Laravel Cache facadeRedis 7 via redis-py async connection pool & Pub/Sub channels
Real-Time TelemetryLaravel Reverb / Echo clientNative ASGI WebSockets (/ws/exams/{id}/monitor) with Redis broadcast
Batch ProcessingLaravel Queues + PhpSpreadsheetopenpyxl streaming reader for student & question .xlsx templates
API DocumentationScramble / Scribe OpenAPI generatorAutomated OpenAPI 3.1 with interactive Swagger UI (/docs) & ReDoc
Deployment ModelSingle container / Linux VPS systemd unitMulti-container Docker Compose (Nginx, React, FastAPI, Postgres, Redis, MinIO)

System Architecture Blueprint

Below is the decoupled cloud-ready architecture implemented in the Python FastAPI + React repository:

┌───────────────────────────────┐ │ CLIENT LAYER (SPA) │ │ React 19 + Tailwind CSS v4 │ └───────────────┬───────────────┘ │ HTTPS (REST) / WSS (WebSocket) │ ▼ ┌─────────────────────────────────────────────────────────────────────────────────────────────────┐ │ FASTAPI ASGI BACKEND (Uvicorn) │ │ │ │ ┌───────────────────────────────────────────────────────────────────────────────────────────┐ │ │ │ SECURITY & GATEWAY LAYER │ │ │ │ • OAuth2PasswordBearer + PyJWT (Stateless Bearer Validation) │ │ │ │ • Passlib BCrypt Password Hasher & Salt Verification │ │ │ │ • Dependency Injection RBAC: Depends(require_roles(["ROLE_ADMIN", "ROLE_TEACHER"])) │ │ │ │ • 1-Click Administrative Impersonation (/api/v1/admin/impersonate/{userId}) │ │ │ │ • Unified ApiResponse[T] Envelope Middleware with ISO-8601 Timestamps │ │ │ └─────────────────────────────────────────────┬─────────────────────────────────────────────┘ │ │ │ │ │ ┌──────────────────────────────────────┼──────────────────────────────────────┐ │ │ ▼ ▼ ▼ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Auth & Users │ │ Academic & │ │ CBT Exam & │ │ │ │ Router │ │ Materials │ │ Telemetry │ │ │ │ - Students │ │ - Classroom │ │ - Runner API │ │ │ │ - Teachers │ │ - Materials │ │ - Anti-Cheat │ │ │ │ - Impersonate│ │ - Assignment │ │ - WebSockets │ │ │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌───────────────────────────────────────────────────────────────────────────────────────────┐ │ │ │ BUSINESS LOGIC & SERVICE LAYER │ │ │ │ • ExcelService (openpyxl parser for bulk students & questions) │ │ │ │ • ExamRunnerService (Server-side timer, autosave answers, auto-grading engine) │ │ │ │ • WebSocketManager (Broadcaster to active proctoring rooms with Redis Pub/Sub) │ │ │ └─────────────────────────────────────────────┬─────────────────────────────────────────────┘ │ │ │ │ │ ┌──────────────────────────────────────┴──────────────────────────────────────┐ │ │ ▼ ▼ │ │ ┌──────────────────────────────┐ ┌─────────────────┐ │ │ │ ASYNC ORM LAYER │ │ CACHE & PUBSUB │ │ │ │ SQLAlchemy 2.0 (asyncpg) │ │ Redis 7 Cache │ │ │ │ Alembic Async Migrations │ │ Pub/Sub Broker │ │ │ └──────────────┬───────────────┘ └─────────┬───────┘ │ │ │ │ │ └─────────────────┼─────────────────────────────────────────────────────────────────────┼─────────┘ │ │ ▼ ▼ ┌────────────────────────┐ ┌────────────────────────┐ │ PostgreSQL 16 │ │ Teacher Proctoring │ │ Relational Database │ │ Live Telemetry Screen │ └────────────────────────┘ └────────────────────────┘

Key Engineering Highlights & Deep Dive

1. FastAPI Dependency Injection: Stateless JWT & Impersonation Engine

In monolithic applications, admin account impersonation typically relies on stateful server sessions (Auth::loginUsingId($userId)). In a decoupled Single Page Application (SPA), we require stateless JWT tokens that carry immutable security claims while supporting on-demand administrative impersonation with full audit trails.

In backend/app/api/v1/auth.py, the impersonation endpoint generates a short-lived token stamped with the target user's identity and the administrator's audit ID:

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}")

Seamless Session Recovery

When the administrator finishes diagnosing the issue, they trigger /api/v1/admin/stop-impersonate. The backend verifies the original_admin_id claim in the token, safely restores the administrator's original session, and revokes impersonated permissions without asking for re-authentication.


2. High-Precision CBT Exam Engine: Anti-Cheat & Native WebSocket Telemetry

The examination engine in backend/app/api/v1/exam_runner.py enforces strict server-side rules that cannot be tampered with via client-side system clock manipulation.

Server-Driven Timer Calculation

PYTHON

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

if attempt and attempt.status in ["in_progress"]:
    # Calculate exact elapsed duration from server-stamped start time
    elapsed = (now - attempt.started_at).total_seconds()
    remaining = max(0, int(total_duration_sec - elapsed))
    
    # Enforce hard deadline
    if remaining <= 0:
        attempt.status = "submitted"
        attempt.finished_at = now
        await db.commit()
        raise HTTPException(
            status_code=400, 
            detail="Time limit reached for this exam attempt"
        )

Anti-Cheat Proctoring & Native WebSocket Broadcasting

On the frontend (frontend/src/pages/exams/ExamRunner.tsx), the candidate's testing environment is strictly locked down:

  • Right-click and copy-paste events are intercepted.
  • Window blur and tab switching trigger HTML5 visibilitychange events.
  • Violations are pushed to the backend via /api/v1/exams/attempts/{id}/violation. When a candidate reaches 5 violations, the exam automatically finalizes.

PYTHON

# Server-side violation handler with instant broadcast
attempt.violations_count += 1
if attempt.violations_count >= 5:
    attempt.status = "submitted"
    attempt.finished_at = datetime.utcnow()

await db.commit()

# Broadcast event to teacher proctoring screen 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,
    }
)

The WebSocket manager broadcasts these events across connected teacher dashboards (ExamMonitor.tsx) in sub-millisecond latency:

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. Memory-Safe Batch Processing with openpyxl

When importing large student rosters (e.g., 2,000 enrolled candidates) or question banks with 200 items containing mathematical formulas and multiple choices, synchronous scripts often hit CPU timeout or memory limits.

In FastAPI, we use openpyxl to parse worksheets in memory, validate rows against Pydantic schemas, and perform bulk database inserts inside an asynchronous transaction:

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
    # Process rows safely without loading unnecessary XML DOM structures
    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
        
        # Provision User account + Student profile atomically
        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="Bulk student import completed")

4. SQLAlchemy 2.0 Async Engine & Pydantic v2 Type Safety

Unlike traditional ORMs that execute blocking I/O calls behind property getters, SQLAlchemy 2.0 requires explicit asynchronous query composition:

PYTHON

# Fully typed asynchronous join with relationship preloading
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()

Coupled with Pydantic v2, all request payloads and responses undergo compile-speed validation powered by Pydantic's underlying Rust engine (pydantic-core), delivering serialization speeds up to 5x to 15x faster than legacy Python frameworks.


Frontend Architecture: React 19, TypeScript & Tailwind CSS v4

The companion frontend application (frontend/) is engineered for responsiveness across desktop classrooms and mobile devices:

  1. Modern React 19 SPA: Built with Vite 6, utilizing fast Hot Module Replacement (HMR) and clean TypeScript interfaces.
  2. Emerald Design System: Clean slate-and-emerald palette (#059669, #10b981) using Tailwind CSS v4, matching the enterprise feel of modern educational platforms.
  3. Zustand State Store: Manages persistent authentication tokens, active user roles, and displays a global top banner when administrative impersonation is active.
  4. CBT Question Palette: Fast touch and keyboard navigation across 100+ questions with real-time visual status cues:
    • 🟢 Green: Answer saved and synchronized to server
    • ⚪ Gray: Unanswered question
    • 🟡 Yellow: Flagged question under review
  5. Printable Exam Cards: Automated generation of official CBT examination hall tickets with NIS barcodes.

When to Choose: Laravel vs. Java Spring Boot vs. Python FastAPI

Having rebuilt the exact same platform across all three technology stacks, here is an objective comparison for engineering leads:

Scenario / RequirementRecommended StackTechnical Rationale
Fastest Time to Market / Solo FounderLaravel 13 + Livewire + FilamentUnmatched developer velocity, built-in admin panel, zero boilerplate, full-stack in a single repo.
High Concurrency & Low Memory UsagePython 3.12 FastAPI + React 19Async event loop serves thousands of concurrent I/O connections on minimal RAM (~20MB); ideal for containerized microservices.
AI, NLP & Machine Learning WorkflowsPython 3.12 FastAPI + React 19Native integration with PyTorch, Hugging Face, scikit-learn, LangChain, and automated essay grading engines.
Strict Enterprise Compliance & Banking-Grade TypingJava 21 Spring Boot + React 19Compile-time static type guarantees, Virtual Threads (Project Loom), Spring Security declarative filters, enterprise LTS.
Decoupled Mobile App EcosystemFastAPI or Spring Boot + ReactStrongly typed OpenAPI specifications with automated SDK generation for iOS, Android, and web clients.

Open-Source Repositories

All three companion implementations are open-sourced under the MIT license:


Getting Started Locally

You can spin up the complete Python FastAPI + React environment with Docker Compose in a single command:

BASH

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

# Launch all services (PostgreSQL, Redis, MinIO, FastAPI Backend, React Frontend)
docker compose up -d --build

Access the running services:

Default Demo Credentials:


Conclusion & Next Steps

Rebuilding our monolithic Laravel platform into Python FastAPI and React demonstrates how modern asynchronous architectures unlock tremendous scalability and developer joy. Python is no longer just for scripting or data science notebooks—combined with FastAPI and ASGI, it is a world-class engine for high-concurrency, real-time distributed applications.

In Part 2, we will dive into the nuances of database migrations: transitioning from Laravel migrations to asynchronous SQLAlchemy 2.0 models and Alembic migration scripts. Stay tuned!

Continue Reading

Previous article

← Previous Article

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

Next Article →

Fleet Management System Part 1: Introduction & System Architecture

Next article