I Built a Full-Stack E-Learning Platform with AI Quiz Generation — Here's Everything I Learned

I Built a Full-Stack E-Learning Platform with AI Quiz Generation — Here's Everything I Learned

posted 4 min read

I'm Karim, a full-stack developer and AI/ML researcher from Cairo. Over the past year, I built Qodrateman — a production e-learning platform now live at qodrateman.com, serving Arabic-speaking students across 12+ courses.
This isn't a "look what I made" post. It's the full breakdown — stack decisions, what broke, how I fixed it, and what I'd do differently. Let's get into it.

What Is Qodrateman?
An e-learning platform built for the Arabic-speaking market with:

Structured courses with instructor/learner role separation
JWT authentication + Role-Based Access Control (RBAC)
AI quiz generation — instructors get a full quiz in under 30 seconds
12+ live courses, 500+ authenticated users
Deployed on Vercel (frontend) + Render (backend) with zero downtime

The Stack — and the Reasoning Behind Every Choice
React + TypeScript + Tailwind CSS
E-learning UI state is genuinely complex — active course, current lesson, user progress, enrollment status. React handles this cleanly. TypeScript caught dozens of bugs before they ever hit production. Tailwind made responsive design fast without drowning in custom CSS.
Django REST Framework
DRF gives you serializers, viewsets, and a built-in admin panel out of the box. I had a fully functional backend faster than I've seen with any other framework. For REST APIs, it's still my first pick.
PostgreSQL
E-learning data is deeply relational — users, courses, lessons, enrollments, quiz attempts, all interconnected. PostgreSQL handles this naturally. SQLite is fine locally, but never in production.
JWT Auth with simplejwt
pythonREST_FRAMEWORK = {

'DEFAULT_AUTHENTICATION_CLASSES': (
    'rest_framework_simplejwt.authentication.JWTAuthentication',
),

}
Access token in memory (not localStorage — that's a security risk). Refresh token handles silent re-authentication. Frontend and backend stay fully decoupled.
Vercel + Render
Zero-config deploys on every push with Vercel. Render for the Django backend — free tier works at early stage, scales easily when needed. Total setup time: under 2 hours.

The Feature I'm Most Proud Of: AI Quiz Generation
Instructors told me quiz creation was their biggest time sink — around 20 minutes per quiz. Writing quality multiple-choice questions is genuinely hard.
I built AI-assisted quiz generation that brings that down to under 30 seconds.
Here's how it works:

Instructor enters topic + difficulty level
System sends a structured prompt to the AI API
API returns a complete quiz in JSON — questions, options, correct answers
Platform parses and saves it directly to the database
Instructor reviews and publishes in one click

The structured prompt was the key engineering decision:
pythondef generate_quiz_prompt(topic, num_questions, difficulty):

return f"""
Generate a quiz about: {topic}
Number of questions: {num_questions}
Difficulty: {difficulty}

Respond ONLY with valid JSON in this exact format:
{{
    "questions": [
        {{
            "question": "question text",
            "options": ["A", "B", "C", "D"],
            "correct_answer": "A",
            "explanation": "why this is correct"
        }}
    ]
}}
Do not include any text outside the JSON.
"""

Forcing structured output and validating before saving eliminated the inconsistency problem entirely. Early versions failed whenever the model returned extra text around the JSON — strict format constraints fixed this.

RBAC: Two User Types, Zero Overlap
Instructors and learners have completely separate capabilities. Here's the core of how that's modeled:
python# models.py
class UserProfile(models.Model):

ROLES = [
    ('instructor', 'Instructor'),
    ('learner', 'Learner'),
]
user = models.OneToOneField(User, on_delete=models.CASCADE)
role = models.CharField(max_length=20, choices=ROLES)

permissions.py

class IsInstructor(BasePermission):

def has_permission(self, request, view):
    return (
        request.user.is_authenticated and
        request.user.userprofile.role == 'instructor'
    )

This permission class protects every instructor-only endpoint — course creation, quiz generation, student progress. Learners can read published content and submit their own quiz attempts. Nothing more.

What Broke in Production (and How I Fixed It)
The N+1 Query Problem
The course listing API was slow. Every course card needed the instructor name, lesson count, and enrollment count — three queries per course. With 12+ courses, that's 36+ database hits per page load.
Fix:
pythoncourses = Course.objects.select_related('instructor').annotate(

lesson_count=Count('lessons'),
enrollment_count=Count('enrollments')

)
One query. Page load time dropped by 70%.
Inconsistent AI Responses
Early quiz generation would sometimes return extra text around the JSON, breaking json.loads().
Fix — strip everything outside the JSON object before parsing:
pythondef parse_ai_response(response_text):

try:
    start = response_text.index('{')
    end = response_text.rindex('}') + 1
    return json.loads(response_text[start:end])
except (ValueError, json.JSONDecodeError):
    raise ValidationError("AI response could not be parsed")

Render Cold Starts
Render's free tier spins down inactive services. First request after inactivity: 30+ seconds. Terrible UX.
Fix: a lightweight ping endpoint that a Vercel cron job hits every 14 minutes. Cost: zero. Cold starts for active users: eliminated.

What I'd Do Differently
Add React Query from day one. Managing server state with raw useEffect and useState gets messy fast. React Query handles caching, refetching, and loading states far more cleanly. I retrofitted it partway through — not fun.
Map the permission model before writing code. I refactored RBAC twice because I hadn't thought through edge cases upfront. Can instructors see other instructors' content? Can learners preview unenrolled courses? Draw this out before touching code.
Use Docker locally from the start. "Works on my machine" is a real problem. Docker Compose makes the jump from local to production significantly smoother.

By the Numbers
MetricResultCourses live12+Authenticated users500+Quiz generation time~20 min → under 30 secPost-launch rollbacks0Downtime since launch0

What's Next
I'm also building Letra — a custom-trained bilingual OCR mobile app for iOS and Android using TensorFlow, Keras, PaddleOCR, and OpenCV. Currently at 98% English accuracy on 800+ labeled samples, deployed to TestFlight and Google Play Beta. Arabic OCR is in active iteration targeting 85%+.

The hardest part of building a real product isn't writing the code. It's the decisions you make before you write any — schema design, permission models, deployment architecture. Get those right and everything else follows.
If you're building on Django + React or have questions about the AI quiz generation approach, drop a comment. Happy to dig into any part of this.

Full-stack dev & AI/ML researcher based in Cairo
karimkhamis.com · qodrateman.com · github.com/karim-99-99

1 Comment

0 votes

More Posts

How I Built a React Portfolio in 7 Days That Landed ₹1.2L in Freelance Work

Dharanidharan - Feb 9

5 Web Dev Pitfalls That Are Silently Killing Your Projects (With Real Fixes)

Dharanidharan - Mar 3

I’m a Senior Dev and I’ve Forgotten How to Think Without a Prompt

Karol Modelskiverified - Mar 19

Breaking the AI Data Bottleneck: How Hammerspace's AI Data Platform Eliminates Migration Nightmares

Tom Smithverified - Mar 16

I Wrote a Script to Fix Audible's Unreadable PDF Filenames

snapsynapseverified - Apr 20
chevron_left

Related Jobs

Commenters (This Week)

4 comments
3 comments
1 comment

Contribute meaningful comments to climb the leaderboard and earn badges!