Popcornly is a fullstack mobile movie discovery app built with Expo + React Native. It combines TMDB content APIs with Firebase Auth + Firestore to deliver:
- Movie and TV browsing
- Global search across movie and TV
- Save-to-favorites per user account
- Trending content powered by Firestore search metrics
- AI recommendations via secure server-side OpenAI proxy
- Frontend: Expo, React Native, Expo Router, TypeScript
- Data Fetching: TanStack React Query
- Backend Services: Firebase Auth + Firestore
- External API: TMDB (The Movie Database)
- Lists/Media: FlashList, expo-image
- Validation/Config: Zod-based env parsing
- Authentication
- Home (trending + latest rails)
- Movies tab (trending/latest + infinite pagination)
- TV Shows tab (trending/latest + infinite pagination)
- Search (debounced, unified movie + TV)
- Details pages (movie + tv, in-app trailers, where-to-watch by country)
- Saved favorites per user
- Profile and account actions
- AI recommendations in Home (
For You) based on user favorites
The recommendation flow is intentionally split into two layers:
- Recommendation generation:
- App sends user context (favorites, optional recent searches, country).
- Backend proxy (Firebase Function) calls OpenAI and returns structured picks:
titlemediaType(movie|tv|any)reason
- Content enrichment:
- App resolves AI titles against TMDB search endpoints.
- It maps each recommendation to actual content cards (poster, id, route target).
- Home rendering:
For You (AI)appears for logged-in users.- If user has no favorites, it shows a guidance state.
- If AI is unavailable, it fails gracefully with fallback messaging.
- Secure AI architecture:
- Firebase Function OpenAI proxy (
functions/src/index.js) - Secret-based key handling (
OPENAI_API_KEY) for production mode
- Firebase Function OpenAI proxy (
- Client recommendation service:
services/recommendations.tsfor endpoint call + TMDB enrichment- Robust parsing of OpenAI response output structures
- Home integration:
- New
For You (AI)rail in Home - Favorite-driven recommendation queries
- New
- Details experience upgrades:
- In-app trailer playback
- YouTube fallback when embedded trailer is blocked
- Where-to-watch providers by country (stream/rent/buy)
High-level flow:
- Client app (Expo/React Native) handles UI and routing
- React Query handles caching and async state
- Firebase Auth handles identity (email/password + Google)
- Firestore stores
users,favorites,metrics, andtvMetrics - TMDB provides movie and TV metadata
See Architecture Notes.
- Install dependencies
npm install- Copy env template
copy .env.example .env-
Fill
.envwith your real credentials -
Run app
npx expo startUseful commands:
npm run lint
npx tsc --noEmit
npm run test:firestore-rules
npm run test:services
npm run test:integration
npm run android
npm run iosUse .env.example as the reference for required variables.
Never commit real secrets.
OpenAI recommendation endpoint (optional until deployed):
EXPO_PUBLIC_RECOMMENDER_ENDPOINT=Demo-only fallback (not for production):
EXPO_PUBLIC_ENABLE_CLIENT_AI_DEMO=false
EXPO_PUBLIC_OPENAI_API_KEY=Behavior by configuration:
EXPO_PUBLIC_RECOMMENDER_ENDPOINTset:- App uses server-side Firebase Function proxy (recommended).
EXPO_PUBLIC_RECOMMENDER_ENDPOINTempty +EXPO_PUBLIC_ENABLE_CLIENT_AI_DEMO=true:- App uses demo-only direct client OpenAI mode (for temporary situations).
- Neither configured:
- App still works fully, AI rail shows graceful no-data/fallback state.
Backend config files used by this app:
firestore.rulesfirestore.indexes.jsonfirebase.json
Current composite index:
- Collection:
favorites - Fields:
userIdascending +savedAtdescending - Purpose: user-scoped favorites list sorted by latest saved
firebase deploy --only firestore:rules
firebase deploy --only firestore:indexes- Start Firestore emulator:
firebase emulators:start --only firestore- Validate key scenarios:
- User can read/write only their own
users/{uid}. - User can only create/read/delete their own
favoritesdocs. metrics/tvMetricswrites require auth and valid schema.- Unknown collections are denied by default.
See detailed security checklist in Security Notes.
Rules are tested with @firebase/rules-unit-testing against the Firestore emulator.
Java is required for local emulator runtime.
Run locally:
npm run test:firestore-rulesWhat is validated:
- Favorites ownership checks (create/read/delete scope).
- User profile ownership + immutable field protection.
- Metrics schema checks and controlled
countincrements. - Unauthenticated write denial.
Service tests run with tsx + Node test runner and mocked fetch responses.
Run locally:
npm run test:servicesCovered modules:
services/api.tsservices/recommendations.ts
Validated behavior includes:
- TMDB mapping and defensive parsing (
resultsmissing, error paths). - Trailer key selection logic.
- Watch-provider fallback handling.
- AI recommendation parsing and enrichment flow (endpoint mode and demo mode).
Integration coverage runs against Firestore emulator with authenticated and unauthenticated contexts.
Run locally:
npm run test:integrationFlow validated:
- Authenticated user can create own profile doc.
- Authenticated user can add/list/delete own favorites.
- Cross-user favorites read is denied.
- Unauthenticated favorites write is denied.
This app uses a Firebase Function proxy so OpenAI keys never ship in the mobile client.
- Install function dependencies
cd functions
npm install- Set secret key for functions
firebase functions:secrets:set OPENAI_API_KEY- Deploy functions
firebase deploy --only functions- Copy deployed URL into app
.env
EXPO_PUBLIC_RECOMMENDER_ENDPOINT=https://<region>-<project>.cloudfunctions.net/recommendationsIf you cannot deploy Firebase Functions yet, you can enable client-side AI only for your own usage only but not recommended for production:
EXPO_PUBLIC_ENABLE_CLIENT_AI_DEMO=true
EXPO_PUBLIC_OPENAI_API_KEY=<your-openai-key>
EXPO_PUBLIC_RECOMMENDER_ENDPOINT=Then restart:
npx expo start -cImportant: this exposes your OpenAI key in the client bundle. Use only for temporary situations, then disable it and remove the key.
- Sign in with a user account.
- Save at least 3-5 titles to favorites.
- Open Home and verify
For You (AI)appears. - Tap a recommendation and confirm navigation to details works.
- Pull-to-refresh Home and confirm recommendation refresh behavior.
- Remove favorites and verify the empty guidance state.
Track progress in:
app/ # expo-router pages/routes
components/ # reusable UI components
constants/ # styles, assets maps, query keys
contexts/ # auth and favorites contexts
services/ # TMDB + Firestore service layer
config/ # env parsing and config
types/ # TypeScript models
scripts/ # project scripts
functions/ # Firebase Functions (OpenAI proxy)
docs/ # architecture, security, todo_checklist docs
This project demonstrates:
- Fullstack integration with Firebase and TMDB
- Real-time user data (favorites + metrics)
- Typed service layer + env validation
- Mobile-first UX with modern navigation and list performance
Personal portfolio project.











