A personal board game collection tracker. Search BoardGameGeek to populate game details automatically, or add games manually. Runs entirely on your local machine via Docker: the main API, a PostgreSQL database and a bgg-writer sidecar.
BoardGame Collection Insights lets you:
- Search BGG — type a game name, pick from live BGG search results, and import full details (cover art, player count, play time, rating, categories, mechanics) in one click
- Add manually — fill in a form yourself if you prefer not to use BGG
- Manage your collection — view all your games as a card grid, edit any details, or remove games
- Filter — search and filter your collection by name, category, or mechanic
- Switch view modes — choose from five display modes (large, medium, small, list, details) that persist across sessions
- Sort your collection — multi-level sort by name, year, rating, player count, or play time; persists across sessions
- Sync with BGG — BGG is the source of truth for your owned games. The app syncs your collection on startup and every 6 hours, including owned expansions. Adding or removing a game in the UI writes back to BGG automatically. A manual ↻ Sync BGG button is available in the header when a BGG username is configured.
- Track expansions — each game has an Expansions tab where you can add owned expansions (from a BGG known-expansions dropdown, a BGG search, or manually). Expansion count badges appear on game cards. Expansions are hidden from the main grid and only shown within their parent game.
- BGG availability detection — the app continuously monitors BGG reachability. If BGG is unavailable, BGG features are automatically disabled and a status banner is shown. Your collection continues to load from the local cache. When BGG comes back online, all features are restored automatically without a page refresh.
The backend follows Domain-Driven Design with two bounded contexts:
┌─────────────────────────────┐ ┌──────────────────────────────┐
│ GameCollection BC │ │ BggIntegration BC │
│ │ │ │
│ Domain / Application / │ │ Domain / Application / │
│ Infrastructure │ │ Infrastructure │
│ │ │ │
│ Owns the collection of │ │ Owns communication with │
│ games and all mutations │ │ the BGG XML API2 │
└─────────────────────────────┘ └──────────────────────────────┘
▲ │
│ Anti-Corruption Layer │
└────────────────────────────┘
BGG → GameCollection
translation happens here
| Layer | Technology |
|---|---|
| Frontend | Vanilla JS ES modules, HTML, CSS — bundled by Vite |
| API | ASP.NET Core Web API (.NET 10) |
| BGG Write Layer | Node.js + Playwright (Chromium + stealth) |
| CQRS | MediatR |
| Database | PostgreSQL via Dapper (stored functions) |
| Web Server | nginx (reverse proxy + static files) |
| Container | Docker / Docker Compose |
- Docker Desktop
- BGG account (optional) — required only for collection sync and write-back
- .NET 10 SDK
- Node.js 20+ — required for both the Vite frontend dev server and
bgg-writer - Docker Desktop —
dotnet testneeds it running, because the database tests start PostgreSQL through Testcontainers
No source code, .NET SDK, or Node.js required — just Docker Desktop.
1. Download the deployment files
From the latest GitHub Release, download:
docker-compose.ymlenv.example(GitHub hides the leading dot on release files)
Place both files in the same folder.
2. Configure your environment
cp env.example .envOpen .env and fill in your values:
DB_PASSWORD=choose-a-password # required, password of the PostgreSQL database
DB_NAME=bgci # optional, database name
DB_USER=bgci # optional, database user
BGG_BEARER_TOKEN=your-token-here # required for BGG search
BGG_USERNAME=your-bgg-username # required for collection sync
BGG_PASSWORD=your-bgg-password # required for collection write-back
DB_PASSWORDis required. The stack does not start without it.DB_NAMEandDB_USERdefault tobgci.
BGG_BEARER_TOKENrequires a formal application at boardgamegeek.com/applications, which may take several weeks to be approved. Without it, BGG search is disabled in the UI.
BGG_USERNAMEandBGG_PASSWORDare optional — without them the app works as a standalone manual collection tracker with no BGG sync.
BGG_WRITER_BASE_URLis pre-configured indocker-compose.yml— no manual entry needed.
3. Start the application
docker compose up -d4. Find the port and open the app
The web UI is assigned a random available port on your machine. To find it:
docker compose psLook for the port mapping under bgci-web, for example 0.0.0.0:49152->80/tcp. Then open http://localhost:49152 in your browser (substitute your actual port).
5. Stop the application
docker compose downData persistence
Game data is stored in PostgreSQL, in a Docker volume named bgci-pgdata. Your collection persists across container restarts. To wipe all data:
docker compose down -vUpgrading from 1.x
Version 2.0.0 stores the collection in PostgreSQL instead of SQLite. This is a breaking change:
- Existing SQLite data is not migrated.
- Games from BoardGameGeek return through sync.
- Games you added manually are not carried over.
- The old
bgci-datavolume is no longer used. Remove it withdocker volume rm bgci-data.
Requires Docker Desktop. The qa profile of the root docker-compose.yml builds the API and the web server from source and replaces BoardGameGeek and the bgg-writer sidecar with a mock server (e2e/mock-bgg). It also runs a PostgreSQL service that keeps its data in memory, so the database starts empty on every run. It is the stack the Playwright suite runs against; see docs/coding-standards-e2e.md.
docker compose --profile qa up --build -dOpen http://localhost:3000 (QA_WEB_PORT changes the port; copy .env.example to .env to set it).
docker compose --profile qa down -vTo run the production stack from the published images, copy .env.prod.example to .env.prod, set DB_PASSWORD and your BGG credentials, and run:
docker compose -f docker-compose.prod.yml --env-file .env.prod up -dThe dev profile of the root docker-compose.yml starts only a PostgreSQL service, for running the API from source:
docker compose --profile dev up -d- It listens on host port
DEV_DB_PORT(default 5433), on the loopback address 127.0.0.1 only, so other machines on your network cannot reach it. The database name, user and password are theDB_*values in.env.example(allbgciby default). - Its data stays in the
bgci-dev-datavolume until you rundocker compose --profile dev down -v. - It is separate from the QA database, so it can run at the same time as the
qaprofile.
cd src/Api
dotnet runThe API starts on http://localhost:5074 by default. Start the dev database first (see "Running the dev database"). Point the API at it with Database:Host=localhost, Database:Port=5433 (DEV_DB_PORT), Database:Name=bgci, Database:User=bgci and Database:Password=bgci, the values in .env.example. Put them in the Database section of your local src/Api/appsettings.Development.json. That file is git-ignored, so each developer keeps their own copy.
The frontend is built with Vite. Run the Vite dev server alongside the API — it proxies all /api requests to the .NET backend automatically, so no additional configuration is needed.
cd frontend
npm install # first time only
npm run devThe app is available at http://localhost:5173.
Note:
public/config.jsis no longer used for local development. The Vite dev server's built-in proxy handles API routing. Theconfig.jsmechanism is still supported if needed — createfrontend/public/config.js(gitignored) withwindow.API_OVERRIDE = 'http://localhost:5074/api'.
cd node/bgg-writer
cp .env.example .env
# fill in BGG_PASSWORD in .env
npm install
npx playwright install chromium
node src/index.jsThe .NET API's appsettings.Development.json already points BggWriter:BaseUrl at http://localhost:3001 — no additional configuration needed.
To debug with the Playwright Inspector (visible Chromium browser with step-through execution):
PWDEBUG=1 node src/index.jsTo attach a Node.js debugger (VS Code or similar):
node --inspect src/index.jsThen attach via the VS Code "Node.js: Attach" launch configuration on localhost:9229.
| Key | Description | Default |
|---|---|---|
Database:Host |
Host name of the PostgreSQL server | bgci-db in Docker |
Database:Port |
Port of the PostgreSQL server | 5432 |
Database:Name |
Database name | bgci |
Database:User |
Database user | bgci |
Database:Password |
Database password | (none, set DB_PASSWORD) |
Bgg:BearerToken |
BGG API bearer token. Without it, BGG search is disabled. Requires approval at boardgamegeek.com/applications. | (empty) |
Bgg:Username |
BGG username for collection sync. | (empty) |
BggWriter:BaseUrl |
Internal URL of the bgg-writer sidecar. Pre-configured in docker-compose.yml. |
(empty) |
appsettings.json (production / Docker):
{
"Database": {
"Host": "bgci-db",
"Port": 5432,
"Name": "bgci",
"User": "bgci"
}
}appsettings.Development.json (local development). This file is not in the repository. Create it yourself:
{
"Database": {
"Host": "localhost",
"Port": 5433,
"Name": "bgci",
"User": "bgci",
"Password": "bgci"
}
}Docker environment variable override:
environment:
- Database__Host=bgci-db
- Database__Port=5432
- Database__Name=bgci
- Database__User=bgci
- Database__Password=${DB_PASSWORD}Note the double underscore __ — this is the ASP.NET Core convention for nested configuration keys in environment variables.
Game data is fetched from the BoardGameGeek XML API2. This is the official BGG API — no HTML scraping is involved. BGG is the source of truth for your owned game collection.
The app monitors BGG reachability at runtime using a dedicated lightweight background service:
- Probes BGG immediately on startup, before serving the first request
- Polls every 2 minutes while BGG is unavailable (fast recovery detection)
- Polls every 15 minutes while BGG is available (low overhead)
- When BGG becomes unavailable: BGG search is disabled, the sync button is disabled, "View on BGG" badge links are deactivated, and a status banner is shown
- When BGG is restored: all features re-enable automatically without a page refresh, and an immediate sync is triggered to catch up any changes missed while BGG was down
- The collection always loads from the local PostgreSQL database regardless of BGG availability
BGG-sourced games display an Info button instead of Edit. Clicking it opens a read-only modal pre-populated with all game details and a direct link to the game's BGG page.
When BGG_USERNAME is configured, a ↻ Sync BGG button appears in the header. Clicking it triggers an immediate differential sync against your BGG collection (same logic as the automatic background sync). The background sync still runs on startup and every 6 hours regardless.
Why it exists
BGG's collection write endpoint (geekcollection.php) is protected by Cloudflare bot detection. Any plain HTTP client — regardless of headers or cookies — receives a 403 response with a JavaScript challenge page. This cannot be bypassed without a real browser that executes JavaScript.
How it works
bgg-writer launches a headless Chromium browser via Playwright with the stealth plugin active (masking the headless fingerprint). It navigates to the BGG login page, fills in the credentials, and submits the form. Once authenticated, it calls geekcollection.php via page.evaluate(fetch(...)) from within the browser context — Cloudflare sees a legitimate browser with valid JavaScript execution and session cookies, and allows the request through.
Architecture
bgg-writer runs as a separate Docker container alongside the main API. It is only reachable by bgci-api on the internal Docker network — no port is exposed externally. It holds BGG_PASSWORD directly; the main .NET API never sees it.