A compact Fastify service written in strict TypeScript with two core endpoints:
- GET
/items— Fetches Skinport items with defaultapp_id/currency, returns the minimal tradable and non-tradable prices per item, and caches the response in Redis. - POST
/purchase— Processes a transactional purchase from the local database, records it, and returns the updated user balance.
- Node.js 20+
- PostgreSQL
- Redis
The public endpoint /v1/items is protected by Cloudflare Bot Management + JS challenge in 2025.
Even with a full set of browser headers, server-side requests receive a 403 response + the "Just a moment…" HTML page.
Therefore, a reliable fallback to current sample data has been implemented—the service always returns a 200 response and valid JSON.
If necessary, you can connect FlareSolverr / a headless browser in production or switch to their future paid API.
-
Install dependencies:
npm install
-
Configure the environment (defaults support local demos):
Copy-Item .env.example .env @' PORT=3000 DATABASE_URL=postgres://postgres:postgres@localhost:5432/skinport REDIS_URL=redis://localhost:6379 SKINPORT_API_URL=https://api.skinport.com/v1/items SKINPORT_USER_AGENT=skinport-purchase-api/1.0 (+https://github.com/user/skinport-purchase-api) USE_SKINPORT_FALLBACK=true ITEM_CACHE_TTL=300 USER_API_KEYS=demo_token:1 '@ | Set-Content .env
Security note:
SKINPORT_API_URLmust targethttps://api.skinport.com/v1/items; other hosts are rejected to avoid proxying to untrusted destinations.
Offline note: When
USE_SKINPORT_FALLBACKistrue(default), the API will return bundled sample prices if the live Skinport request fails (useful in CI or networks where the API is blocked).
-
Start dependencies (PostgreSQL + Redis):
docker compose up -d -
Apply the schema and seed demo data (idempotent thanks to unique constraints on usernames and product names):
Get-Content .\schema.sql | docker compose exec -T postgres psql -U postgres -d skinport
-
Run the API in development mode:
npm run dev
Interactive API docs: http://localhost:3000/docs
-
Build and run in production mode (do not run dev and prod servers simultaneously):
npm run build npm start
There are two helper scripts to automate starting dependencies and applying the DB schema.
-
PowerShell (already added):
scripts/setup-dev.ps1Usage (PowerShell):
# runs npm install, starts Docker services, applies schema and starts dev server .\scripts\setup-dev.ps1
-
Cross-platform Node script:
scripts/setup-dev.jsUsage (any shell with Node):
node ./scripts/setup-dev.js
The Node script performs the same steps but works on Windows, macOS and Linux — it uses the local
dockerandnpmcommands and starts the dev server as a detached process.
Run the unit test suite (uses mocked Redis/Skinport/DB dependencies so no services need to be running):
npm testSkinport requires Brotli compression for the /v1/items endpoint. Always send Accept-Encoding: br or the API will reply with 406 not_acceptable. A minimal fetch example:
const url = new URL('https://api.skinport.com/v1/items');
url.searchParams.set('app_id', '730');
url.searchParams.set('currency', 'EUR');
const response = await fetch(url, {
method: 'GET',
headers: {
'Accept-Encoding': 'br',
Accept: 'application/json'
}
});
if (!response.ok) {
throw new Error(`Skinport API responded with ${response.status}`);
}
const data = await response.json();Tip: Older cURL builds (notably on Windows) may not support Brotli. Use Node 20+ (which supports Brotli by default) or a cURL build with
--compressed+-H "Accept-Encoding: br"on platforms that support it.
Returns an array with minimal prices for tradable and non-tradable variants of each Skinport item. Responses are cached in Redis for ITEM_CACHE_TTL seconds.
PowerShell-ready example (note the explicit curl.exe to avoid the curl alias):
curl.exe --compressed `
-H "Accept-Encoding: br" `
-H "Authorization: Bearer demo_token" `
"http://localhost:3000/items"Headers:
Authorization: Bearer <token>— tokens are configured inUSER_API_KEYSand mapped to user IDs.
Body:
{ "productId": 2 }Performs a transactional purchase on behalf of the authenticated user, deducts the product price from the user balance, records the purchase, and responds with the updated balance.
PowerShell-ready examples:
-
Using
curl.exe(keep the JSON body in single quotes):curl.exe -X POST ` -H "Authorization: Bearer demo_token" ` -H "Content-Type: application/json" ` -d '{"productId":1}' ` "http://localhost:3000/purchase"
-
Using
Invoke-RestMethodwithout cURL:Invoke-RestMethod -Method Post -Uri "http://localhost:3000/purchase" ` -Headers @{ Authorization = "Bearer demo_token" } ` -ContentType "application/json" ` -Body '{"productId":1}'
Example setup for POST /purchase:
-
Create a new request with method POST and URL
http://localhost:3000/purchase. -
In the Headers tab, add
Authorizationwith valueBearer <token>(token must match a configured entry inUSER_API_KEYS). -
In the Body tab, choose raw → JSON and enter:
{ "productId": 2 } -
Send the request to receive the updated balance after the purchase is processed.
-
Dependencies:
Runnpm installafter pulling updates to ensure all new dependencies (e.g.,@vitest/coverage-v8,c8, Husky, lint-staged) are installed. -
Environment:
If you use CI or local scripts, ensure your.envmatches the new defaults.
Redis and PostgreSQL must be running for integration tests and development. -
Testing:
Integration tests now start the Fastify server automatically.
Redis and Postgres must be available locally (or via Docker).
For full isolation, consider using Testcontainers (see TODO). -
Security & Automation:
- Dependabot and CodeQL are enabled for automated security checks.
- Linting, formatting, and coverage enforcement are now part of CI.
- Pull the latest code.
- Run
npm install. - (Optional) Recreate
.envfrom.env.exampleif new variables are added. - Start dependencies:
docker compose up -d - Apply schema:
Get-Content .\schema.sql | docker compose exec -T postgres psql -U postgres -d skinport - Run tests:
npm run test:coverage - Review CI status and security alerts in GitHub.