Keep track of the games you own, how long you've played them, and what you thought of them.
Open the app — the API sleeps when nobody is using it, so the first request can take up to a minute.
Create an account and start adding games. Type a title and the app searches RAWG for it, then fills in the genre, platform, release year, and cover art — you only set the things that are yours: status, hours, rating, notes. Games you don't own yet go on the wishlist and stay out of your averages until you actually buy them.
The dashboard shows where your library stands at a glance. The stats page breaks it down further: completion rate, hours by genre, which platforms you actually play on, how generous you are with ratings, what you've added month by month, and a set of achievements that fill in as your library grows.
Libraries are private. If you want to show yours off, turn on a public profile and you get a link anyone can open. It shows your games, hours, and ratings, but never your notes. You can follow other people's public profiles and see their libraries side by side under Following.
React and Vite on the front end. Node, Express, and PostgreSQL on the back. Game data comes from the RAWG API.
You'll need Node 18+ and PostgreSQL 14+.
Create a database:
createdb game_libraryCopy the environment template and fill it in:
cd server
cp .env.example .envDATABASE_URL points at the database you just created. JWT_SECRET signs login tokens and should be long and random:
node -e "console.log(require('crypto').randomBytes(48).toString('hex'))"RAWG_API_KEY is free from rawg.io/apidocs. Without it the app still works, you just have to type game details in by hand.
Then, from the project root:
npm run install:all
npm run db:migrate
npm run devThe API runs on port 4000, the client on 5173. Vite proxies /api across, so nothing else needs configuring.
Four tables. users holds accounts. games is a shared catalogue — one row per game, no matter how many people own it. user_games sits between them and holds everything personal: status, hours, rating, notes. follows records who follows whom.
That split is what lets two people track the same game without stepping on each other. Delete a game from your library and the catalogue entry stays put for everyone else.
The database enforces its own rules rather than trusting the API to get it right: status has to be one of five values, ratings stay between 0 and 5, hours can't go negative, you can't add the same game to your library twice, and you can't follow yourself.
Achievements aren't stored. They're derived from the library every time the stats page renders, which means there's no state to keep in sync when a game is edited or deleted.
Everything lives under /api. All of it needs an Authorization: Bearer <token> header except registration, login, and public profiles.
| Method | Path | |
|---|---|---|
| POST | /auth/register |
Create an account |
| POST | /auth/login |
Sign in |
| GET | /auth/me |
Who am I |
| PATCH | /auth/me |
Make my profile public or private |
| GET | /games |
My library |
| POST | /games |
Add a game |
| PUT | /games/:id |
Update an entry |
| DELETE | /games/:id |
Remove an entry |
| GET | /search?q= |
Search RAWG |
| GET | /profiles/:username |
Someone's public library |
| GET | /follows |
People I follow |
| PUT | /follows/:username |
Follow someone |
| DELETE | /follows/:username |
Stop following |
A private profile and a username that doesn't exist return the same response, so you can't use the API to find out who has an account here.
The database runs on Neon, the API on Render, and the client on GitHub Pages.
Render builds from the server directory and runs npm start, which applies the schema before booting. It needs DATABASE_URL, JWT_SECRET, RAWG_API_KEY, and CORS_ORIGINS set in its dashboard.
The client is built with the API address baked in, then pushed to the gh-pages branch:
cd client
npm run build
npx gh-pages -d distVITE_API_URL comes from client/.env.production.
Public profiles use hash routing (#/u/username) because GitHub Pages serves static files and can't rewrite unknown paths to index.html.
This started as a plain HTML page with localStorage and grew a layer at a time — React, then an Express API, then PostgreSQL, then accounts, then everything else. The commit history follows that order if you want to see how it got here.
| v1 | HTML, CSS, JavaScript, localStorage |
| v2 | React and Vite |
| v3 | Express API, JSON file storage |
| v4 | PostgreSQL |
| v5 | RAWG search and cover art |
| v6 | Accounts and JWT auth |
| v7 | Charts, responsive layout, loading states |
| v8 | Public profiles |
| v9 | Wishlist, achievements, activity chart, following |


