A full-stack movie recommendation engine combining content-based TF-IDF filtering with the TMDB API — delivering intelligent, visually rich film discovery in real time.
🚀 Live Demo · 📸 Screenshots · 🏗️ Architecture · ⚙️ Installation · 📡 API Reference · 🤝 Contributing
| Service | URL |
|---|---|
| 🎨 Frontend | movie-recommender-system-complete.streamlit.app |
| ⚡ Backend API | movie-recommender-system-3n2v.onrender.com |
| 📖 API Docs | /docs · /redoc |
Note: The backend is hosted on Render's free tier and may take 30–60 seconds to wake up on first request.
| Feature | Description | |
|---|---|---|
| 🔍 | Smart Search | Real-time movie search powered by TMDB with instant results |
| 🤖 | Content-Based Filtering | TF-IDF cosine similarity on movie plots and metadata |
| 🎭 | Genre Discovery | Discover top-rated films within the same genre via TMDB |
| 🎬 | Rich Movie Details | Posters, backdrops, ratings, genres, overview, and release info |
| ⚡ | Async FastAPI Backend | High-performance REST API with full OpenAPI documentation |
| 🎨 | Cinematic UI | Dark-themed Streamlit frontend with hero banners and card grids |
| 🔄 | Parallel Fetching | Concurrent TMDB poster resolution for fast recommendation rendering |
| 📦 | Cached Responses | st.cache_data on all API calls for snappy repeat queries |
┌─────────────────────────────────────────────────────────┐
│ USER BROWSER │
└─────────────────────┬───────────────────────────────────┘
│ HTTP
▼
┌─────────────────────────────────────────────────────────┐
│ STREAMLIT FRONTEND (app.py) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌────────────────────┐ │
│ │ Home │ │ Search │ │ Details / Recs │ │
│ └──────────┘ └──────────┘ └────────────────────┘ │
│ │
│ ThreadPoolExecutor — parallel API calls │
└────────────────────────┬────────────────────────────────┘
│ REST (JSON)
▼
┌─────────────────────────────────────────────────────────┐
│ FASTAPI BACKEND (app/main.py) │
│ │
│ ┌──────────────────┐ ┌──────────────────────────┐ │
│ │ TMDB Service │ │ TF-IDF Engine │ │
│ │ (httpx async) │ │ (scikit-learn + numpy) │ │
│ └────────┬─────────┘ └──────────┬───────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────────────┐ │
│ │ TMDB API │ │ Pickle Data Store │ │
│ │ (external) │ │ df · indices · │ │
│ └──────────────┘ │ tfidf · matrix │ │
│ └──────────────────────┘ │
└─────────────────────────────────────────────────────────┘
User Query
│
├──► TMDB Search ──► Best Match ──► Movie Details
│ │
├──► TF-IDF Engine ▼
│ │ Poster + Backdrop
│ ▼ Genres, Overview
│ Cosine Similarity
│ │
│ ▼
│ Top-N Similar Titles
│ │
│ ▼
│ Parallel TMDB Poster Fetch (ThreadPoolExecutor)
│
└──► Genre Discovery ──► TMDB /discover/movie ──► Genre Grid
movie-recommendation/
│
├── app/ # FastAPI backend
│ ├── api/
│ │ ├── home.py # /home endpoint — trending/popular feeds
│ │ ├── movie.py # /movie/id and /movie/search endpoints
│ │ ├── recommend.py # /recommend/tfidf and /recommend/genre
│ │ └── search.py # /tmdb/search endpoint
│ │
│ ├── core/
│ │ ├── config.py # Environment config (pydantic-settings)
│ │ ├── startup.py # Pickle loading on startup
│ │ └── state.py # Global in-memory state (df, matrix, etc.)
| |
| ├── data/ # Pre-computed ML artifacts (not in git)
│ | ├── df.pkl # Processed movie DataFrame
│ | ├── indices.pkl # Title → matrix index mapping
│ | ├── tfidf.pkl # Fitted TfidfVectorizer
│ | └── tfidf_matrix.pkl # Sparse TF-IDF feature matrix
| |
│ ├── schemas/
│ │ └── schemas.py # Pydantic request/response models
│ │
│ ├── services/
│ │ ├── tmdb_services.py # Async TMDB API client (httpx + retry)
│ │ └── tfidf_services.py # TF-IDF cosine similarity logic
│ │
│ └── main.py # FastAPI app, CORS, router registration
│
├── images/ # Application screenshots
│ ├── cotent_recommendations.png
│ ├── genre_recommendations.png
│ ├── home.png
│ └── search.png
|
├── notebooks/ # Data preparation and model training
| ├── movies.ipynb
| └── movies_metadata.csv
|
├── app.py # Streamlit UI
│
├── .env.example
├── .python-version
├── LICENCE
├── README.md
├── requirements.txt
└── runtime.txt
- Python 3.11+
- A free TMDB API key
- The four
.pkldata files (see Data Setup)
git clone https://github.com/sharif-abusad/movie-recommendation-system.git
cd movie-recommendation-systempython -m venv venv
# Windows
venv\Scripts\activate
# macOS / Linux
source venv/bin/activatepip install -r requirements.txtcp .env.example .envOpen .env and fill in your values:
TMDB_API_KEY=your_tmdb_api_key_here
API_HOST=0.0.0.0
API_PORT=8000
DEBUG=False
ALLOWED_ORIGINS=http://localhost:8501Place the pre-computed pickle files in the data/ directory:
data/
├── df.pkl
├── indices.pkl
├── tfidf.pkl
└── tfidf_matrix.pkl
To regenerate these files from scratch, run the notebook in
notebooks/.
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000| Resource | URL |
|---|---|
| API Base | http://localhost:8000 |
| Swagger UI | http://localhost:8000/docs |
| ReDoc | http://localhost:8000/redoc |
streamlit run frontend/app.pyThe app will open at http://localhost:8501.
http://localhost:8000
| Method | Endpoint | Description | Key Parameters |
|---|---|---|---|
GET |
/health |
Health check | — |
GET |
/home |
Trending / popular / top-rated feed | category, limit |
GET |
/tmdb/search |
Full-text TMDB movie search | query, page |
GET |
/movie/id/{tmdb_id} |
Movie details by TMDB ID | tmdb_id |
GET |
/movie/search |
Complete bundle: details + TF-IDF + genre recs | query, tfidf_top_n, genre_limit |
GET |
/recommend/tfidf |
TF-IDF recommendations (titles + scores only) | title, top_n |
GET |
/recommend/genre |
Genre-based discovery via TMDB | tmdb_id, limit |
# Get trending movies
curl "http://localhost:8000/home?category=trending&limit=12"
# Search for a movie
curl "http://localhost:8000/tmdb/search?query=inception"
# TF-IDF recommendations
curl "http://localhost:8000/recommend/tfidf?title=Inception&top_n=10"
# Full recommendation bundle
curl "http://localhost:8000/movie/search?query=The+Dark+Knight&tfidf_top_n=12&genre_limit=12"Example Response — GET /recommend/tfidf
[
{ "title": "Batman Begins", "score": 0.612 },
{ "title": "The Dark Knight Rises", "score": 0.589 },
{ "title": "Watchmen", "score": 0.431 }
]Example Response — GET /movie/id/{tmdb_id}
{
"tmdb_id": 155,
"title": "The Dark Knight",
"overview": "Batman raises the stakes in his war on crime...",
"release_date": "2008-07-18",
"poster_url": "https://image.tmdb.org/t/p/w500/qJ2tW6WMUDux911r6m7haRef0WH.jpg",
"backdrop_url": "https://image.tmdb.org/t/p/w1280/hkBaDkMWbLaf8B1lsWsKX7Ew3Xq.jpg",
"genres": [
{ "id": 28, "name": "Action" },
{ "id": 80, "name": "Crime" },
{ "id": 18, "name": "Drama" }
]
}- Push your repo to GitHub
- Create a new Web Service on Render
- Set the following:
| Setting | Value |
|---|---|
| Build Command | pip install -r requirements.txt |
| Start Command | uvicorn app.main:app --host 0.0.0.0 --port $PORT |
| Environment Variables | TMDB_API_KEY, DEBUG=False |
- Connect your GitHub repo at share.streamlit.io
- Set Main file path to
frontend/app.py - Add secrets under Settings → Secrets:
API_BASE = "https://your-render-backend.onrender.com"
TIMEOUT = "60"- FastAPI — async REST API framework
- Uvicorn — ASGI server
- HTTPX — async HTTP client for TMDB calls
- Pydantic — data validation and serialisation
- Scikit-learn — TF-IDF vectoriser
- NumPy — cosine similarity computation
- Pandas — movie DataFrame
- Streamlit — interactive UI framework
- Requests — REST API client
- concurrent.futures — parallel poster fetching
- TMDB API — movie data, posters, and genre discovery
- Collaborative filtering (user-based / item-based)
- Hybrid recommendation (TF-IDF + collaborative)
- User authentication and profiles
- Watchlist and favourites
- Server-side recommendation caching (Redis)
- Docker + docker-compose setup
- CI/CD pipeline (GitHub Actions)
- Unit and integration tests (pytest)
- Structured logging and monitoring (Sentry / Grafana)
- Mobile-responsive PWA
Contributions are welcome and appreciated.
# 1. Fork the repository
# 2. Create a feature branch
git checkout -b feature/your-feature-name
# 3. Commit your changes
git commit -m "feat: add your feature"
# 4. Push and open a Pull Request
git push origin feature/your-feature-namePlease follow Conventional Commits for commit messages and open an issue before starting large changes.
This project is licensed under the MIT License — see the LICENSE file for details.



