Filmterest
Platonic Tinder for movie people. I built this to actually learn React and Node from scratch, and it grew into a real social app that matches users by Letterboxd taste.
I'd never really built anything with React or Node before this, so I picked a project idea I'd actually want to use myself as an excuse to learn both properly. I made a social app that matches people by film taste using Letterboxd, since I already log everything I watch there. It started as pure practice and turned into something with real auth, a real database, and a matching algorithm I'm actually proud of.
Users log posts and reviews, see a feed of friends' recent Letterboxd activity, and can check a compatibility score against any Letterboxd username, comparing their own diary against someone else's. Under the hood it's a fairly standard full-stack shape, with React and Redux on the frontend, an Express API, and MongoDB via Mongoose. A couple of the smaller decisions ended up being the parts I learned the most from.
Filmterest is a fairly standard full-stack shape, a React and Redux frontend, an Express API, and MongoDB through Mongoose. Auth supports both an email and password flow that issues a JWT I sign myself, and Google Sign-In, which hands back Google's own ID token instead. The Letterboxd integration sits behind a small client wrapper that either calls the real API or falls back to a deterministic mock data layer, depending on whether credentials are configured.
The Letterboxd API requires application approval, and mine was still pending
while I was building this, so I wrote a mock data layer that generates a
deterministic fake "diary" for any username instead of stalling the project.
It's seeded from an FNV-1a hash of the username through a small seedable PRNG
called mulberry32, so the same username always gets the same fake watch history
and match score instead of random noise on every request. Every mocked response
is flagged with pending: true so the UI can show a banner about it,
and the real controller checks for an API key at request time, so once
Letterboxd approves access it starts returning live data with no code changes
on either side. Building against an API I didn't have yet made me think harder
about the interface between "real" and "mock" data than I would have otherwise.
I also didn't want the compatibility score to just count shared movies, since two people who've both seen a hundred popular films but rate everything oppositely shouldn't score as highly as two people with a smaller, pickier overlap who actually agree. So the score ended up half overlap, shared films over the union of both diaries, and half rating agreement, based on the average difference in their ratings on the films they've both logged. It took a couple of tries to land on weights that felt right when I tested it against my own account and a few friends'.
The auth middleware needs to tell my own JWTs apart from Google's ID tokens on every request, and the way it does that is genuinely a bit of a hack. My JWTs are short and Google's are long, so it branches on token length. It works, and it's a good example of a pragmatic shortcut I'd want to replace with something more principled, like a claims-based check, if this ever needed to be hardened.