Building a book recommendation engine that doesn't just show bestsellers
Most book discovery apps have the same problem. You open them looking for something to read, and they show you the same fifty titles you've already seen everywhere. Colleen Hoover. James Clear. The latest Reese's Book Club pick. If you've already read the obvious stuff, these apps have nothing for you. They're popularity engines disguised as recommendation engines.
I ran into this when building Book Buddy. The easy version of a book app is to pull trending lists and call it a day. But the people who actually need a recommendation tool are the ones who've already burned through the popular picks. They want something specific that they can't quite articulate. Something that matches a feeling, not a genre.
Mood as the entry point
Genre is a terrible proxy for what someone wants to read. "Fiction" contains multitudes. Even sub-genres like "literary thriller" lump together books with completely different energy. So I built the discovery flow around mood. When you open Book Buddy, you pick from prompts like "something light and funny," "something that'll wreck me emotionally," "something I can't put down," or "something that makes me think." It's loose and human in a way that genre dropdowns aren't.
This gives me a preference signal before the user has done anything else in the app, which is important for cold start.
How matching works
Each book in the system has a mood vector -- weighted scores across dimensions like intensity, pacing, emotional tone, accessibility, and a few others. I build these vectors from three sources: genre metadata and book descriptions, sentiment analysis on reader reviews, and manual tagging for the seed dataset. The manual pass was tedious. I tagged about 2,000 books myself over a few weeks. But it gave me ground truth to validate the automated pipeline against. Without it, the sentiment analysis drifted in weird directions. Reviews are noisy. People write five-star reviews that are mostly complaints, and one-star reviews that are basically love letters with a single gripe.
When a user picks a mood, the app maps it to a target region in the mood vector space and returns books closest to that region, filtered by what they haven't seen.
Messy data
I pull from the Google Books API and Open Library. Both are useful. Neither is clean. Google Books has better cover images and descriptions but inconsistent genre tagging. Open Library has richer metadata for older and more obscure titles but gaps in newer releases. Author names don't always match across the two. ISBNs should be universal identifiers, but in practice, different editions fragment what's clearly the same book into multiple entries. I spent more time on data normalization and deduplication than on the actual recommendation algorithm.
"Cozy" means different things to different people
This was a problem I didn't anticipate. Mood is subjective. When one person says they want something cozy, they mean a gentle romance set in a small town. Another person means a murder mystery where the detective drinks tea and has a cat. Both are valid. Both are "cozy." But the books are nothing alike.
Initial mood selection gets you in the right neighborhood. After that, the app learns from your ratings. Every thumbs up or thumbs down adjusts the mood vector weights for that specific user. Over a few weeks of use, your version of "cozy" diverges from everyone else's, which is the point. The system stops recommending based on what the average user means by a word and starts recommending based on what you mean by it.
The feature I almost cut
Late in development, I had a "surprise me" button on the chopping block. Felt gimmicky. The button picks a book that's deliberately outside your established pattern -- if you always read contemporary fiction, it might surface a graphic novel or a translated novella or a popular science book with a similar emotional profile.
Left it in mostly because it was already built. Turns out it's the most used discovery feature in the app. People tap it when they're in a rut, and because it's anchored to mood similarity rather than genre similarity, the suggestions feel adventurous without feeling random. A few users have told me it's the reason they stuck around. People say they want more of what they like, but they stick around for well-chosen surprises.
-- Daniel