Backend API for the Quizly frontend. The project creates quizzes from YouTube videos by downloading audio, transcribing it locally with Whisper, and generating quiz data with Gemini.
- Python
- Django
- Django REST Framework (DRF)
- Simple JWT
- JWT in HTTP-only cookies
- django-cors-headers
- python-dotenv
- yt-dlp
- FFmpeg
- OpenAI Whisper
- Google GenAI SDK (Gemini)
- SQLite (development)
- User registration and login
- Authentication via JWT in HTTP-only cookies
- Refresh token rotation and blacklist support
- Quiz generation from YouTube videos
- Local audio transcription with Whisper
- AI-based quiz generation with Gemini
- Quiz CRUD for the authenticated user
- Nested quiz questions in API responses
- Django admin integration for users, quizzes, and questions
- The user sends a YouTube URL to the backend.
- The backend validates and normalizes the URL.
yt-dlpdownloads the audio.- Whisper transcribes the local audio file.
- Gemini generates structured quiz JSON.
- The backend validates the generated data.
- The quiz and its questions are stored in the database.
- The created quiz is returned in the API response.
http://127.0.0.1:8000/api/
Request body:
{
"username": "your_username",
"email": "your_email@example.com",
"password": "your_password",
"confirmed_password": "your_password"
}Success response:
{
"detail": "User created successfully!"
}Request body:
{
"username": "your_username",
"password": "your_password"
}Success response:
{
"detail": "Login successfully!",
"user": {
"id": 1,
"username": "your_username",
"email": "your_email@example.com"
}
}Auth required.
Success response:
{
"detail": "Log-Out successfully! All Tokens will be deleted. Refresh token is now invalid."
}Uses the refresh token from the HTTP-only cookie.
Success response:
{
"detail": "Token refreshed"
}Auth required. Returns all quizzes of the authenticated user.
Auth required.
Request body:
{
"url": "https://www.youtube.com/watch?v=example"
}Note:
- Short YouTube URLs such as https://youtu.be/... are also supported.
Success response:
{
"id": 1,
"title": "Quiz title",
"description": "Short quiz description",
"created_at": "2026-04-08T12:41:19.100776Z",
"updated_at": "2026-04-08T12:41:19.100804Z",
"video_url": "https://www.youtube.com/watch?v=example",
"questions": [
{
"id": 1,
"question_title": "Question text",
"question_options": ["Option 1", "Option 2", "Option 3", "Option 4"],
"answer": "Option 1",
"created_at": "2026-04-08T12:41:19.106387Z",
"updated_at": "2026-04-08T12:41:19.106475Z"
}
]
}Notes:
- The current frontend expects
answerto be included in the quiz response. - Quiz generation can take some time, especially for longer videos.
- If Gemini is temporarily overloaded, the backend returns
503 Service Unavailable.
Auth required.
200if the quiz belongs to the current user403if the quiz exists but belongs to another user404if the quiz does not exist
Auth required.
Only title and description can be updated.
Request body example:
{
"title": "Updated title",
"description": "Updated description"
}Auth required.
Returns 204 No Content on success.
This project uses JWT with HTTP-only cookies. That means:
- the frontend must send requests with credentials
- the backend handles access and refresh tokens via cookies
- logout blacklists the current refresh token
- refresh rotates the refresh token and issues a new access token
- custom Django user model
- unique email
- username-based login
- belongs to one user
- stores title, description, normalized
video_url - has many questions
- belongs to one quiz
- stores
question_title,question_options,answer
quizly-backend/
├─ core/
│ ├─ __init__.py
│ ├─ settings.py
│ ├─ urls.py
│ ├─ asgi.py
│ └─ wsgi.py
│
├─ users/
│ ├─ migrations/
│ ├─ api/
│ │ ├─ __init__.py
│ │ ├─ authentication.py
│ │ ├─ serializers.py
│ │ ├─ urls.py
│ │ ├─ utils.py
│ │ └─ views.py
│ ├─ admin.py
│ ├─ apps.py
│ └─ models.py
│
├─ quizzes/
│ ├─ migrations/
│ ├─ api/
│ │ ├─ __init__.py
│ │ ├─ gemini_service.py
│ │ ├─ serializers.py
│ │ ├─ transcription_service.py
│ │ ├─ urls.py
│ │ ├─ utils.py
│ │ ├─ views.py
│ │ └─ youtube_service.py
│ ├─ tests/
│ │ ├─ __init__.py
│ │ ├─ test_happy.py
│ │ └─ test_unhappy.py
│ ├─ admin.py
│ ├─ apps.py
│ └─ models.py
│
├─ manage.py
├─ .env
├─ .gitignore
├─ README.md
└─ requirements.txt
git clone https://github.com/codebySaschaHeinze/quizly-backend.git
cd quizly-backend
Windows (PowerShell):
python -m venv .venv
.\.venv\Scripts\Activate.ps1
macOS/Linux:
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
FFmpeg must be available in your system PATH.
On Windows, verify it with:
ffmpeg -version
If this command fails, Whisper and audio processing will not work.
Create a local .env file in the project root.
Example:
SECRET_KEY='your_new_django_secret_key'
DEBUG=True
GEMINI_API_KEY='your_gemini_api_key'Notes:
- keep quotes if your key contains
# - do not commit
.env - after changing
SECRET_KEY, old JWT tokens become invalid
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser
python manage.py runserver
Backend base URL:
http://127.0.0.1:8000/api/
If you use the provided frontend with Live Server:
- make sure the Django server is running
- make sure the frontend origin is included in
CORS_ALLOWED_ORIGINS - make sure the same host style is used consistently (
127.0.0.1vslocalhost)
Typical allowed origins in development:
CORS_ALLOWED_ORIGINS = [
'http://127.0.0.1:5500',
'http://localhost:5500',
'http://127.0.0.1:3000',
'http://localhost:3000',
]If your Live Server runs on a different port, add that exact origin to both:
CORS_ALLOWED_ORIGINSCSRF_TRUSTED_ORIGINS
Run all quiz tests:
python manage.py test quizzes.tests
Run only happy path tests:
python manage.py test quizzes.tests.test_happy
Run only unhappy path tests:
python manage.py test quizzes.tests.test_unhappy
- Quiz generation depends on external services and local tools.
- Gemini may temporarily return
503during periods of high demand. - Longer videos take noticeably more time to process.
- Temporary audio files are deleted after quiz generation.
- The development server is for local development only.
Educational / internal project. Adjust if needed.