Movie discovery & watchlist platform
A full-stack platform where users discover films, manage personal watchlists across four states, rate with stars, and analyse their viewing habits — a React SPA over a Node.js REST API with PostgreSQL persistence.
Problem statement
Personal film tracking tends to scatter across notes, spreadsheets, and streaming apps: what you want to watch, what you're watching, and what you've finished all live in different places — and nobody keeps the stats. CinePulse collapses that into one system with an explicit watchlist state model and an analytics view that makes the data useful.
My role
Sole engineer — designed the data model, built the REST API and authentication, implemented the React frontend, and owns deployment and schema migrations end to end.
System architecture
Illustrative architecture of the deployed system.
Technology decisions
- PostgreSQL + Prisma over a document database
- Users, films, list entries, and ratings are related data with real integrity requirements — one user rates one film once. A normalised relational schema with migrations keeps that consistent, and Prisma gives typed queries plus a versioned schema history.
- JWT in httpOnly cookies, not localStorage
- Tokens in localStorage are readable by any injected script. Storing the JWT in an httpOnly cookie with SameSite restrictions keeps XSS from exfiltrating credentials while the API stays stateless.
- Zod at the API boundary
- Every write endpoint validates its payload with Zod, so malformed input dies at the edge and the client/server contract stays explicit — the same shapes drive form validation in the UI.
- React SPA over a plain REST API
- The frontend is a fast Vite-served SPA; the API is a conventional REST surface. The separation keeps the backend portable and lets the client ship UI iterations without touching server code.
Challenges & solutions
- Four-state watchlist semantics
- Planned → watching → completed, plus dropped, is a small state machine rather than a boolean. Transitions are guarded server-side so invalid jumps (e.g. dropped → completed directly) can't corrupt analytics.
- Search that matches how people look for films
- Full-text search over titles combined with structured filters (genre, rating, watch state) in a single query path — ranked text results, filtered by the same facets, without loading the catalogue client-side.
- Analytics from raw watch history
- Completion rates, state distributions, and rating trends are SQL aggregates over the normalised tables, served as one dashboard payload so the client renders a single request instead of N.
- Cross-origin cookie authentication
- Cookies don't travel cross-origin by default — the fetch client sends credentials explicitly, and the API responds with the right SameSite/CORS headers so the SPA stays authenticated across requests.
What I'd improve next
- Cache catalogue responses and invalidate on demand
- Unit and E2E test coverage for the watchlist state machine
- Infinite scroll and prefetching for search results
- Rate limiting and abuse protection on write endpoints
- Offline-friendly PWA shell for the watchlist
Links
Source code will be linked here once the repository is public.