Tell us one film you love. We'll find five more you'll adore.
CineMatch is a full-stack movie recommendation system — a FastAPI backend serving a content-based ML model, paired with a Streamlit frontend styled with a dark, cinematic UI. Pick any film, hit Discover, and receive five tailored recommendations with posters fetched live from TMDB.
CineMatch
├─ backend
│ ├─ app
│ │ ├─ main.py
│ │ └─ model
│ │ └─ model.pkl
| | |_ model_dict.pkl
│ └─ requirements.txt
└─ frontend
├─ app.py
├─ Dockerfile
└─ requirements.txt
User selects a movie
│
▼
Streamlit (app.py) ──POST /recommend──► FastAPI (main.py)
│
Looks up movie index
in movie_dict DataFrame
│
Computes top-5 similar
movies via cosine similarity
matrix (movie.pkl)
│
Fetches poster for each
from TMDB API
│
◄── JSON response ── Returns titles + poster URLs
│
▼
Streamlit renders 5 animated movie cards
The recommendation model is content-based filtering — similarity scores are pre-computed and stored in a pickled matrix, so inference is near-instant.
- Python 3.8+
- A free TMDB API key
# Navigate to the backend directory
cd backend
# Install dependencies
pip install fastapi uvicorn pandas requests gdown python-dotenv pydantic
# Create your .env file
echo "TMDB_API_KEY=your_tmdb_api_key_here" > .env
# Start the server
uvicorn main:app --reloadThe API will be live at http://localhost:8000.
Note: On first run,
main.pyautomatically downloads the two model files (movie.pklandmovie_dict.pkl) from Google Drive usinggdown. This may take a moment.
# Navigate to the frontend directory
cd frontend
# Install dependencies
pip install streamlit requests
# Run the app
streamlit run app.pyThe app will open at http://localhost:8501.
If running the backend locally, update
API_URLinapp.py:API_URL = "http://localhost:8000"
Base URL: https://movie-recommender-pn4m.onrender.com
Health check — used by Render to keep the service alive.
{ "status": "ok" }Returns all available movie titles.
{
"movies": ["The Dark Knight", "Inception", "Interstellar", "..."]
}Returns 5 recommended movies with poster URLs.
Request body
{ "movie": "Inception" }Response 200
{
"recommended_movies": [
{ "title": "The Prestige", "poster_url": "https://image.tmdb.org/t/p/w500/..." },
{ "title": "Memento", "poster_url": "https://image.tmdb.org/t/p/w500/..." },
{ "title": "Shutter Island", "poster_url": "https://image.tmdb.org/t/p/w500/..." },
{ "title": "Interstellar", "poster_url": "https://image.tmdb.org/t/p/w500/..." },
{ "title": "The Matrix", "poster_url": "https://image.tmdb.org/t/p/w500/..." }
]
}Response 404
{ "error": "Movie not found" }Create a .env file inside the backend/ directory:
TMDB_API_KEY=your_tmdb_api_key_here| Variable | Required | Description |
|---|---|---|
TMDB_API_KEY |
✅ Yes | API key from themoviedb.org |
- Push the backend to a GitHub repository
- Create a new Web Service on Render
- Set the build command:
pip install -r requirements.txt - Set the start command:
uvicorn main:app --host 0.0.0.0 --port 10000 - Add
TMDB_API_KEYas an environment variable in the Render dashboard - Deploy — model files are downloaded automatically on first boot via
gdown
- Push the frontend to a GitHub repository
- Go to share.streamlit.io and connect your repo
- Set
app.pyas the entry point - Deploy — no environment variables needed for the frontend
| Layer | Technology |
|---|---|
| Frontend | Streamlit + custom HTML/CSS |
| Backend | FastAPI + Uvicorn |
| ML Model | Content-based filtering (cosine similarity) |
| Data | Pandas DataFrame (pickled) |
| Poster Images | TMDB API |
| Model Storage | Google Drive via gdown |
| Hosting | Render (API) · Streamlit Cloud (UI) |
The movie list takes a long time to load The backend runs on Render's free tier and spins down after inactivity. The first request after a sleep period may take 30–60 seconds. Subsequent requests are fast.
TMDB_API_KEY errors on startup
Ensure your .env file exists in the same directory as main.py and contains a valid key. You can verify it loaded correctly with a quick print(os.getenv("TMDB_API_KEY")) in a Python shell.
gdown fails to download model files
Google Drive imposes a download quota. If it's exceeded, try again after a few hours — or host the .pkl files yourself and update the id= values in the gdown.download() calls inside main.py.
Posters show a placeholder image
TMDB returned a null poster_path for that film, or the movie ID in the dataset doesn't match TMDB's current catalogue. The frontend handles this gracefully with a fallback image.
This project is open source and available under the MIT License.
Made with ☕ and a love of cinema