← Back to Projects
Catalogie cover
Live Product · Full-Stack Web App

Catalogie

Keep track of everything you watch, play and read, with movies, TV, games and books all in one place, running on a PERN stack that we host ourselves.

Role
  • Concept
  • Design
  • Engineering
Team
3 people, collaborative
Type
  • Media tracker
  • Web app + PWA
Stack
  • React
  • Express 5
  • PostgreSQL
  • TypeScript
Status
Live & ongoing since Jun 2026
Version
v1.2.0 - 6 releases shipped

01Overview

Catalogie is a media tracker that brings movies, TV series, games and books together in one catalog. Each type of media is tracked in the way that actually makes sense for it, so you log TV by the episode, movies by the watch, games by the hours you’ve played and books by the page.

It’s a real product with real people using it. The three of us built it and run it ourselves on our own hardware, with an API written in strict TypeScript, and we’re still adding to it. We’ve shipped six versions since launching in June 2026, and each one came with a public dev diary. This page covers both parts of the project: the app itself at app.catalogie.com and the marketing site at catalogie.com.

Product problem

We needed to track four kinds of media that measure progress in completely different ways, all in one library, without any of them feeling like an afterthought stuck onto a TV app.

Data problem

There’s no single catalog that covers all four. Each one comes from a different provider with its own login method, usage limits and licence terms, and none of those keys could ever be allowed to reach the browser.

Platform problem

People only trust a tracker if their history is safe. That meant real accounts, a real database, backups and infrastructure we control, rather than just saving things in the browser.

Team problem

Three of us work on the same codebase, and there’s also a native app in a separate repository. So the API had to be a properly documented contract that everyone could rely on, not just an internal detail.

02Key Features

One library, four media

Each type of media has statuses that use the right words, like watching, playing, reading, wish and TBR, along with favourites and custom filters.

Progress at the natural unit

Episodes are grouped inside seasons, pages inside books and hours inside a playthrough. You can mark a whole season at once or log a session with the date it happened.

Ratings on your scale

You can choose five stars with half stars, or a ten-point scale, separately for each type of media. You can also rate individual episodes, and see outside scores alongside an anonymous average from the community.

What’s next

Your watchlist, backlog and to-be-read pile all live in one queue. There’s also a release calendar, and you can turn on push notifications for new seasons and launches if you want them.

Stats & Yearly Wrap

See how much time you spend on each type of media, your favourite genres, a heatmap of your activity with streaks, more than 35 badges with different tiers, and a yearly wrap-up for any year you choose.

Social layer

Follow other people, control exactly what’s visible on your profile down to individual titles, and leave threaded comments with reactions and images.

Importers

Bring across years of watch history from other services. Your data is read, matched against our catalog and shown to you for review before anything is added.

Reader-contributed books

If a book is missing, you can describe it once, and after a quick moderation check it becomes a shared entry that everyone can track.

03Screenshots

Screens taken from the real product. Tap any of them to see it full size.

04Architecture

Catalogie is a modular monolith on the PERN stack, which means one Express process with fourteen feature modules and background jobs running inside it. It’s hosted on an Oracle Ampere A1 virtual machine (4 OCPUs, 24 GB of memory, arm64) behind Caddy. Caddy serves the app at app.catalogie.com and forwards API calls to api.catalogie.com, which keeps the login cookies first-party.

The progress model

A library_entries row stores the details that every type of media shares, like status, favourite, rating and notes, with one row per user per title. Everything that’s specific to a type of media goes into event tables that are only ever added to: episode_watches, title_watches, play_sessions and reading_logs. That way, rewatching a show or rereading a book adds a new row instead of overwriting the old one.

One activity view

All four event tables are combined into one SQL view called activity_events. That view powers the streak counter, the heatmap and the yearly wrap-up, so finishing a book and beating a game both count towards the same streak.

Federated metadata

Data about films and TV, games, books, ratings and release dates is fetched through typed clients that only run on the server. The API keys live only in environment variables, requests can only go to an approved list of hosts, each one times out after 10 seconds with a single retry, and results are cached in memory and in Postgres so we stay within the shared usage limits.

Layered API

Routers read the request, services make the decisions and repositories run the queries. Zod checks the data at every boundary, ownership always comes from the logged-in session, and the API is documented with OpenAPI so both the web and native apps can use it.

Auth for two clients

Login uses better-auth with email and password or Google, and sessions are stored in the database. The website uses first-party SameSite=Lax cookies, while the native app uses the bearer token plugin, because it doesn’t run in a browser.

Jobs in Postgres

pg-boss runs scheduled jobs inside the database itself: syncing episodes, updating release dates, refreshing ratings within the usage budget, cleaning up uploads, sending emails from an outbox and delivering push notifications. Every job is safe to run more than once and gets retried if it fails, and there’s no need for Redis.

05Tech Stack

Client

React 18ViteTypeScriptPWA · service worker

API

Node 24Express 5Strict TypeScriptZodbetter-authOpenAPI

Data & jobs

PostgreSQL 17Drizzlepg-boss36 SQL migrations

Infra & quality

Oracle Ampere A1CaddyGHCR imagesGitHub ActionsVitest · Supertestgitleaks

Marketing site

React 18ViteFramer MotionLenisGitHub Pages

06A Collaborative Build

Catalogie started as my idea, and now the three of us design, build and look after it together. Work moves from personal branches to a reviewed dev branch and then to main. Along the way, CI lints, type-checks, tests and builds both the app and the site, and also runs npm audit and a gitleaks scan over the full history.

Ruchira Edirisinghe

I came up with the concept and look after the product direction and UI design. I also built the React client and the TypeScript backend, all the way from the database schema to deployment.

Nimna Niwarthana

Nimna works on design and engineering across the client, the API and the infrastructure, and also built the admin panel.

Thaanu Perera

Thaanu builds the native iOS and Android apps on top of the same API, which is the reason we designed everything API-first from the very start.

07Release History

It isn’t finished, and it isn’t meant to be. Every version comes with a dev diary written in plain English, including the bugs we got wrong.

v1.0.0 · 13 Jun 2026

The first launch, with movies, TV, games and books in one library, progress tracked by episode, page or hours played, a stats dashboard, and accounts that are private by default.

v1.1.0 · 14 Jun 2026

Added following and public profiles, comments and reactions, optional push notifications and Founding Member badges.

v1.1.1 · 15 Jun 2026

Fixed a sign-up bug that could lock people out of their accounts, resent verification emails automatically and added a new date picker for when you watched something.

v1.1.2 · 16 Jun 2026

Made sign-in clearer with links to the Terms and Privacy pages, switched to self-hosted fonts so the layout stopped jumping around, and added the Dev Diaries page.

v1.1.3 · 17 Jul 2026

Cut the size of the library data in half, made posters load only when needed, kept counts live across all your devices, and finally showed movie watch dates.

v1.2.0 · 25 Jul 2026

Added books contributed by readers, separate rating scales for each type of media with community averages, ratings for individual episodes, more than 35 badges and completely rebuilt Yearly Wraps.

08Engineering Challenges

ChallengeThe product that was live at first was a plain JavaScript prototype that kept user data in the browser’s localStorage, used separate tables for each type of media, and had no login system and no backups.
SolutionWe built the new strict TypeScript API one module at a time, right alongside the old one, and only switched over once it was ready. Every commit left the live site working, so there was never a big risky rewrite and no downtime.
ChallengeThe four types of media measure progress in completely different ways, so a single progress column would track all of them badly.
SolutionShared details live on one library row, each type of media gets its own event tables that are only ever added to, and a single combined view handles streaks and stats.
ChallengeOn a public app, uploads like profile pictures and comment images are an easy target for attacks.
SolutionEvery upload is checked by its actual file signature, re-encoded through sharp to strip out EXIF data and anything hidden inside, saved under a random UUID and served with nosniff from a folder that can’t run code.
ChallengeWhen traffic picked up, the API slowed down, and the first instinct was to add a caching layer.
SolutionLoad tests on a single CPU core showed that the real limit was Node using up the CPU, not Postgres. Trimming the heaviest field from the list responses and compressing the responses fixed it.
ChallengeDuring testing, we used up a catalog API key, and that broke a real feature for real users.
SolutionNow we test against our own internal endpoints, use fake catalog responses and keep development keys separate from production ones. This is written into the project’s working rules.

09Results & Learnings

4
media in a single library
6
versions shipped since launch
14
feature modules behind one API
82
test suites (62 server, 20 client)
  • A live tracker that we host ourselves, with real accounts. It covers four types of media with four ways of tracking progress, lets you rate on your own scale, and includes a social layer, stats, importers, a release calendar and push notifications.
  • Most tests run against a real Postgres database, plus a permissions matrix that checks user A can never touch user B’s entries, comments or uploads.
  • Encrypted backups are copied off the server every night and kept for 30 days, and we practise restoring from them once a month.
  • It’s free forever, private by default and you can export your data for free. The first 1,000 accounts become Founding Members, and catalogie.com shows a live count straight from the production API.
  • Next up: the native iOS and Android apps on the same API, more importers, and a second server region behind a CDN.

What I learned

  • Shared usage limits on outside services are part of your production setup. They need to be treated as carefully as a database, not like something to play around with in tests.
  • You can rebuild a product while it’s live, as long as the new system grows next to the old one and every commit could be shipped.
  • Measure before you optimise. The bottleneck turned out to be the CPU, not the database that everyone suspected.
  • Keeping a log of our decisions and writing a dev diary for every release keeps the three of us on the same page, and it’ll help us later on too.