- TypeScript 73.9%
- CSS 20.8%
- Dockerfile 2.7%
- JavaScript 1.9%
- HTML 0.7%
| .forgejo/workflows | ||
| backend | ||
| frontend | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| docker-compose.yml | ||
| Dockerfile | ||
| README.md | ||
Pokémon Game Collection Tracker
Tracks your Pokémon core-series games: generation, edition, main console, other compatible consoles, release year, whether you own it, who plays it, and whether it's been completed.
Comes pre-seeded with every core-series game and remake through generation 9,
and a fixed player list (Freydis, Romy, Milena) — edit backend/seedData.js
before first run if you want to change either.
Local development
Requires Node.js 18+.
# terminal 1 - backend (API on :3000, also serves seed/db)
cd backend
npm install
npm run dev
# terminal 2 - frontend (dev server on :5173, proxies /api to :3000)
cd frontend
npm install
npm run dev
Open http://localhost:5173.
Production build (single process)
cd frontend && npm install && npm run build && cd ..
cd backend && npm install && npm start
Express serves the built React app and the API from the same port (3000 by
default, override with PORT).
Deploy with Docker
docker compose up -d --build
This builds the frontend and bundles it with the backend into one image, and
connects it to the pkmngames database on the shared Postgres stack (the
pg-shared external network, host pg-shared-db). Set
PKMNGAMES_DB_PASSWORD to the role's password (see below). The container
always listens on 3000 internally — put it behind your own reverse proxy /
firewall rules on the VPS, since there's no login built in.
Changing the host port
If 3000 is already taken on your server, set HOST_PORT to whatever's free
instead of editing docker-compose.yml:
cp .env.example .env
# edit .env, e.g. HOST_PORT=3001
docker compose up -d --build
In Portainer, set HOST_PORT and PKMNGAMES_DB_PASSWORD under the stack's
Environment variables instead of using a .env file.
API
| Method | Path | Description |
|---|---|---|
GET |
/api/health |
Health check ({"status":"ok","database":"connected"}, 503 if the DB check fails) |
GET |
/api/version |
Git commit hash baked in at build time ({"version":"<hash>"}, "unknown" outside Docker) |
GET |
/api/players |
List players |
GET |
/api/games |
List games (with owned/completed/player assignments) |
POST |
/api/games |
Create a game |
PATCH |
/api/games/:id |
Update owned/completed/player assignments |
Data
- Uses Postgres when
DATABASE_URLis set (always true when running viadocker-compose.yml) and SQLite otherwise — e.g. running the backend directly for local development. The SQLite file lives atDATA_DIR/pokemon.db(./data/pokemon.dblocally). - Seeding only runs when the
games/playerstables are empty, so your edits (owned/completed/players) are never overwritten on restart.
Migrating an existing deployment to German titles
If you already deployed before the UI was translated to German, your DB still has the old English edition names (Red, Blue, Sword, …) — a redeploy will not rename them, since seeding only runs on empty tables. Run this once against that deployment's volume to rename them in place (owned/completed/ player assignments are untouched):
docker exec -it <container-name> node migrate-de-titles.js
or locally, pointing at the volume's data directory:
cd backend && DATA_DIR=/path/to/pkmn-data node migrate-de-titles.js
Safe to run more than once — already-migrated rows simply won't match and are skipped.
CI/CD
.forgejo/workflows/ci.yml runs on every push to main and every PR:
- backend — installs backend deps (
npm ci). - frontend — installs frontend deps, lints (
oxlint), and builds (npm run build). - docker —
docker compose build, so a broken image build fails the workflow. - deploy — only on pushes to
main, after the above pass: POSTs to a webhook (secrets.DEPLOY_WEBHOOK_URL) so Portainer re-pulls this repo and rebuilds/redeploys the stack.
The deploy step needs the webhook URL set as a repo secret first (it's not committed anywhere):
tea actions secrets create --repo freydis/pkmn-games DEPLOY_WEBHOOK_URL "<your webhook url>"
or set it via the web UI: repo → Settings → Actions → Secrets.