/api/v1 routes.frontend/ Angular 22 SPA (Material + Tailwind) backend/ Fastify API that proxies Steam Web API and Store appdetails.env:STEAM_API_KEY Steam Web API key (required; never expose to the browser) FRONTEND_ORIGIN CORS origin of the UI ADMIN_API_KEY Shared secret for X-Admin-Key on cache-clear / library-refresh RATE_LIMIT_MAX Max requests per window RATE_LIMIT_WINDOW_MS Rate-limit windowFRONTEND_ORIGIN to the origin you actually open in the browser:docker compose (dev): http://localhost:4200http://localhost:8080https://steamstats.quantumcode.dev (or your real HTTPS origin)/api to http://localhost:3000 (frontend/proxy.conf.dev.json)..env as above first.tsx watch + ng serve:frontend/proxy.conf.js (/api → http://backend:3000).127.0.0.1:8080, backend not published:/api reverse-proxied by nginx). The host bind is loopback-only so a Cloudflare Tunnel (or SSH) can reach it without exposing HTTP on the WAN.backend/src/services/api/steam/ with this.client.request({ host, path, params }). Extra methods on an existing class do not need a new plugin entry.backend/src/plugins/steam.plugin.ts and hang it on fastify.steam.backend/src/routes/ that calls fastify.steam.*. Register a new file in backend/src/routes/index.ts.backend/src/schemas/ is the HTTP contract (request/response per route, not one file per Steam service). Attach schema.body / params / response, plus tags and operationId.backend/src/types/steam-api.types.ts. The SPA never imports those.frontend/src/app/api/openapi.json — dumped specfrontend/src/app/api/generated/ — Api plus functions such as getProfile / getLibrarythis.api.invoke(getProfile, { steamId }). Do not add a hand-written *.api.service.ts or call HttpClient yourself for /api/v1. Short names (OwnedGame, SteamProfile) live in frontend/src/app/interfaces/api.ts. Retries (5xx / network) are in the HTTP interceptor./api/v1./api/v1/profile/resolve Resolve SteamID64, profile URL, or vanity name GET /api/v1/profile/:steamId Normalized player profile GET /api/v1/library/:steamId Owned games and library stats GET /api/v1/library/:steamId/random Random owned game GET /api/v1/library/:steamId/recent Games played in the last 2 weeks GET /api/v1/library/refresh Refresh cached Steam app list (X-Admin-Key) GET /api/v1/games/:appId Steam Store details for one app GET /api/v1/cache/clear Clear backend cache (X-Admin-Key) GET /health LivenessSTEAM_API_KEY is used only by the backend.FRONTEND_ORIGIN.trustProxy is on for Cloudflare / nginx).X-Frame-Options: DENY, nosniff, Referrer-Policy, HSTS, Permissions-Policy) are set in frontend/nginx.conf. SPA routes use a named @spa location so those headers stay on index.html (nginx add_header does not inherit). CSP img-src allows https: so the browser can load Steam CDN artwork. connect-src stays 'self' because /api is same-origin. There is no Angular service worker (same as Snippy); leftover ngsw clients unregister on load.GET /api/v1/cache/clear and GET /api/v1/library/refresh require X-Admin-Key matching ADMIN_API_KEY. In production those routes return 404 if the key is unset.http://127.0.0.1:8080 (host) or http://frontend:8080 (Docker network) FRONTEND_ORIGIN https://your-domain in Portainer, not localhostcurl -sI https://steamstats.quantumcode.dev must show CSP, HSTS, X-Frame-Options, and nosniff after deploy.curl -sI https://your-domain and the original tools. Also cover:npm run check:audit; Dependabot or osv-scanner for moderate issues)nginx:*-alpine, node:22-alpine).env)/api/v1 (authz, CORS, XSS via Store HTML, rate limits, TRACE/PUT/DELETE)Posted Sep 27, 2026
Developed SteamStats, a web app to explore Steam profiles and libraries using Angular and Fastify.
0
1