TypeScript + Bun server for querying IGDB game data using Steam IDs with persistent SQLite caching.
- 🎮 Query game data from IGDB using Steam IDs
- 💾 Persistent SQLite caching (permanent until force refresh)
- ⚡ Fast responses via in-memory + disk cache
- 🔄 OAuth token management with auto-refresh
- 📊 Partial success responses (found/notFound/errors)
- 🌐 Multi-language support for game names, genres and themes
- 🚀 Built with Bun for optimal performance
- Bun v1.0+
- Twitch Developer Account (for IGDB API access)
curl -fsSL https://bun.sh/install | bash- Go to https://dev.twitch.tv/console
- Click "Register Your Application"
- Fill in details:
- Name: "IGDB Service" (or any name)
- OAuth Redirect URL:
http://localhost - Category: Application Integration
- Copy Client ID and Client Secret
cp .env.example .envEdit .env and add your credentials:
TWITCH_CLIENT_ID=your_actual_client_id
TWITCH_CLIENT_SECRET=your_actual_client_secret
PORT=3000bun install# Development (with hot reload)
bun run dev
# Production
bun run startServer will start at http://localhost:3000
Use PM2 to run the service in the background with auto-restart.
npm install -g pm2# Build standalone executable
bun run build
# First time: start service
bun run pm2:start
# After code changes: rebuild and restart
bun run deploy# Stop service
bun run pm2:stop
# Restart service
bun run pm2:restart
# View logs
bun run pm2:logs
# Check status
bun run pm2:status# Generate startup script
pm2 startup
# Save current process list
pm2 savePOST /api/games
{
"steamIds": [730, 570, 440],
"forceRefresh": false,
"language": "en"
}Parameters:
steamIds(required): Array of Steam app IDs (max 100)forceRefresh(optional): Skip cache and fetch fresh data from IGDBlanguage(optional): Language code for game names/genres/themes (default:en)
{
"games": [
{
"steamId": 730,
"name": "Counter-Strike: Global Offensive",
"localizedName": "反恐精英:全球攻势",
"summary": "Counter-Strike: Global Offensive...",
"url": "https://www.igdb.com/games/counter-strike-global-offensive",
"cover": {
"url": "https://images.igdb.com/igdb/image/upload/t_cover_big/...",
"width": 264,
"height": 352
},
"screenshots": [
{
"image_id": "scii5z",
"url": "https://images.igdb.com/igdb/image/upload/t_screenshot_big/scii5z.jpg",
"width": 1920,
"height": 1080
}
],
"artworks": [
{
"image_id": "ar47zf",
"url": "https://images.igdb.com/igdb/image/upload/t_1080p/ar47zf.jpg",
"width": 2560,
"height": 1440,
"artwork_type": 3
}
],
"videos": [
{
"name": "Trailer",
"video_id": "abc123",
"youtube_url": "https://www.youtube.com/watch?v=abc123"
}
],
"first_release_date": 1345075200,
"aggregated_rating": 85.5,
"total_rating": 88.2,
"game_status": "Released",
"age_ratings": [
{
"organization": "ESRB",
"rating": "Mature",
"synopsis": "..."
}
],
"platforms": [
{ "name": "PC (Microsoft Windows)" }
],
"game_modes": [
{ "name": "Multiplayer" }
],
"genres": [
{ "name": "Shooter" }
],
"themes": [
{ "name": "Action" }
],
"language_supports": [
{
"language": "English",
"support_type": "Audio"
}
],
"similar_games": [
{
"name": "Counter-Strike",
"cover": { "url": "..." }
}
],
"developers": [
{ "name": "Valve Corporation" }
],
"publishers": [
{ "name": "Valve Corporation" }
]
}
],
"notFound": [],
"errors": []
}Response Fields:
games: Successfully fetched games (from cache or IGDB)name: Original game name (always in English)localizedName: Localized name (only present when found and different fromname)screenshots: Array of game screenshotsartworks: Array of official artworks (key art, concept art, logos, etc.)videos: Array of game videos with YouTube links
notFound: Steam IDs with no IGDB mappingerrors: Steam IDs that failed to fetch (with reasons)
The artwork_type field indicates the type of artwork:
| ID | Name | Description |
|---|---|---|
| 1 | Artwork | General artwork |
| 2 | Key art without logo | Key art without game logo |
| 3 | Key art with logo | Key art with game logo |
| 4 | Concept art | Concept artwork |
| 5 | Game logo (white) | White version of game logo |
| 6 | Game logo (black) | Black version of game logo |
| 7 | Game logo (color) | Color version of game logo |
| 8 | Infographic | Infographic image |
Filter Key Art:
const keyArts = game.artworks.filter(a => a.artwork_type === 2 || a.artwork_type === 3);Fetch multiple games:
curl -X POST http://localhost:3000/api/games \
-H "Content-Type: application/json" \
-d '{"steamIds": [730, 570, 440]}'Force refresh cached data:
curl -X POST http://localhost:3000/api/games \
-H "Content-Type: application/json" \
-d '{"steamIds": [730], "forceRefresh": true}'Fetch with Chinese translations:
curl -X POST http://localhost:3000/api/games \
-H "Content-Type: application/json" \
-d '{"steamIds": [730], "language": "zh-CN"}'Health check:
curl http://localhost:3000/healthThe service supports localized game names, genres and themes.
| Code | Language | Game Names | Genres/Themes |
|---|---|---|---|
en |
English (default) | ✅ | ✅ |
zh-CN |
Simplified Chinese | ✅ | ✅ |
zh-TW |
Traditional Chinese | ✅ | ❌ |
zh |
Chinese | ✅ | ❌ |
ja |
Japanese | ✅ | ❌ |
ko |
Korean | ✅ | ❌ |
pt-BR |
Brazilian Portuguese | ✅ | ❌ |
Game names are fetched from IGDB using two sources (in priority order):
- game_localizations - Official localized names by region
- alternative_names - Alternative titles with language comments
If no localized name is found, the original English name is returned.
Note: IGDB's localized name coverage varies by game. Many games may not have Chinese or other language names available.
To add or update genre/theme translations, use the translation generator script:
bun run generate-translations --lang zh-CN,ja,koRequired Environment Variables:
AI_API_KEY=your-api-key
AI_BASE_URL=https://your-api-endpoint.com/v1
AI_MODEL=gpt-4o-miniThe script will:
- Fetch all genres and themes from IGDB
- Translate them using AI
- Save to
src/i18n/genres.jsonandsrc/i18n/themes.json
Run the test script:
./igdb_service/test-requests.shTests cover:
- Valid requests with known Steam IDs
- Cache behavior
- Force refresh
- Invalid IDs
- Error cases
- Edge cases
┌─────────────┐
│ Client │
└──────┬──────┘
│ POST /api/games
▼
┌─────────────────┐
│ HTTP Server │
│ (Bun.serve) │
└────────┬────────┘
│
▼
┌─────────────────┐ ┌──────────────┐
│ GameService │────▶│ CacheManager │
└────────┬────────┘ └──────────────┘
│ │
│ ┌─────▼──────┐
│ │ SQLite DB │
│ └────────────┘
▼
┌─────────────────┐
│ IGDBClient │
└────────┬────────┘
│
▼
┌─────────────────┐
│ IGDB API │
│ (Twitch OAuth) │
└─────────────────┘
Flow:
- Client sends Steam IDs to
/api/games - Service checks SQLite cache
- For cache misses, queries IGDB:
- Maps Steam IDs → IGDB IDs (external_games)
- Fetches game details (games endpoint)
- Transforms and caches results
- Returns partial success response
View cached games:
bun --eval "
const { Database } = require('bun:sqlite');
const db = new Database('./data/cache.db');
const games = db.query('SELECT steam_id, cached_at FROM games').all();
console.table(games);
db.close();
"Clear cache:
rm ./data/cache.dbCache will be recreated on next request.
IGDB free tier: 4 requests/second
The service implements:
- Batch processing (10 Steam IDs per request)
- 250ms delay between batches
- Permanent caching to minimize API calls
"Failed to get OAuth token"
- Verify
TWITCH_CLIENT_IDandTWITCH_CLIENT_SECRETin.env - Check credentials at https://dev.twitch.tv/console
"IGDB API error: 429"
- Rate limit exceeded
- Wait a few seconds and retry
- Check for excessive forceRefresh usage
"No mapping found for Steam ID"
- Steam game not in IGDB database
- Steam ID incorrect or game not released
- Check Steam store page for correct app ID
igdb_service/
├── src/
│ ├── index.ts # HTTP server + main entry
│ ├── service.ts # Game service orchestration
│ ├── igdb-client.ts # IGDB API client + OAuth
│ ├── cache.ts # SQLite cache manager
│ ├── transformer.ts # Data transformation
│ ├── types.ts # TypeScript definitions
│ ├── enums.ts # IGDB enum mappings
│ └── i18n/
│ ├── index.ts # Translation loader
│ ├── genres.json # Genre translations
│ └── themes.json # Theme translations
├── scripts/
│ └── generate-translations.ts # AI translation generator
├── data/
│ └── cache.db # SQLite database (generated)
├── .env # Environment variables
├── package.json
├── tsconfig.json
└── README.md
MIT