A self-hosted API that fetches chess statistics from Chess.com and Lichess and returns styled SVG image cards — ready to embed in GitHub profile READMEs, websites, or anywhere that renders images.
- Stats card with ratings, win/loss/draw counts, and a donut chart
- Elo history line chart with optional multi-mode overlay (bullet + blitz + rapid)
- 6 built-in themes
- In-memory caching (5 min for stats, 15 min for history)
- Pure SVG output — no canvas, no headless browser
git clone https://github.com/yourname/chess-stats
cd chess-stats
npm install
# Recommended: use Bun to run TypeScript directly
# (the package scripts use `bun --watch` / `bun --hot`)
npm start
# or for development with hot-reload
npm run devIf you don't have Bun, you can build and run with Node (Node >= 18):
npm run build
# then run the compiled output (adjust path if your tsconfig writes elsewhere)
node dist/src/index.js| Variable | Default | Description |
|---|---|---|
PORT |
3000 |
Port the server listens on |
DEFAULT_THEME |
dark |
Theme applied to all endpoints when ?theme= is not specified |
OTEL_ENABLED |
false |
Enable OpenTelemetry instrumentation |
OTEL_EXPORTER_OTLP_ENDPOINT |
http://localhost:4318 |
OTLP endpoint for traces and metrics |
OTEL_SERVICE_NAME |
chess-stats |
Service name for OpenTelemetry |
OTEL_ENVIRONMENT |
development |
Environment name for OpenTelemetry |
Run with Bun (recommended) or set env vars before running the built Node app:
DEFAULT_THEME=nord PORT=8080 bun src/index.ts
# or after building
DEFAULT_THEME=nord PORT=8080 node dist/src/index.jsOpenTelemetry provides distributed tracing and metrics for monitoring your chess-stats service. To enable:
- Set
OTEL_ENABLED=trueor configureOTEL_EXPORTER_OTLP_ENDPOINT - Point to your OTLP-compatible backend (Jaeger, Tempo, Grafana, etc.)
# Example with Jaeger locally
OTEL_ENABLED=true OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 bun src/index.tsThe service will automatically instrument HTTP requests, external fetch calls, and Express routes.
docker build -t chess-stats .
docker run -p 3000:3000 chess-statsWith environment variables:
docker run -p 3000:3000 -e DEFAULT_THEME=nord -e PORT=3000 chess-statsOr with Docker Compose — create a compose.yaml:
services:
chess-stats:
build: .
ports:
- "3000:3000"
environment:
- DEFAULT_THEME=dark
restart: unless-stoppeddocker compose up -dWhen running the server in Docker (or when converting SVG to PNG on the host), the rasterizer (resvg) needs access to system fonts. If fonts are missing you may see images render correctly but text is absent in the generated PNGs.
Recommended options:
- Alpine-based Docker image (the project's default): add the following to the
Dockerfilebefore installing dependencies:
# Install fonts required for SVG->PNG text rendering (resvg needs system fonts)
RUN apk add --no-cache fontconfig ttf-dejavu- Debian / Ubuntu (host or Debian-based container):
sudo apt-get update
sudo apt-get install -y fontconfig fonts-dejavu-core- Fedora / RHEL (host or container):
sudo dnf install -y fontconfig dejavu-sans-fontsAfter installing fonts, restart your container or server so the renderer can pick up the new font installations.
If you prefer not to modify the host or image, another option is to embed a webfont inside the SVG (via an @font-face with a base64-encoded WOFF/WOFF2) prior to conversion — this is more portable but increases SVG size.
The API exposes several SVG-producing endpoints. All image responses are image/svg+xml and the endpoints also support ?format=json to return raw JSON when useful.
Returns an SVG stats card with ratings, game counts, and a W/L/D donut.
| Param | Values | Default |
|---|---|---|
platform |
chessdotcom, lichess |
— |
username |
player username | — |
?theme |
see Themes | dark |
?format |
svg, json |
svg |
Examples:
/stats/chessdotcom/hikaru
/stats/lichess/DrNykterstein?theme=nord
/stats/chessdotcom/hikaru?format=json
Returns an SVG line chart of Elo rating over time. Supports overlaying multiple modes in a single chart.
| Param | Values | Default |
|---|---|---|
platform |
chessdotcom, lichess |
— |
username |
player username | — |
?mode |
bullet, blitz, rapid — or comma-separated |
blitz |
?months |
1–12 |
6 |
?theme |
see Themes | dark |
?format |
svg, json |
svg |
Examples:
/history/chessdotcom/hikaru?mode=blitz&months=6
/history/lichess/DrNykterstein?mode=bullet&months=3&theme=dracula
/history/chessdotcom/hikaru?mode=bullet,blitz,rapid&months=6
Returns a single unified SVG containing the full stats card with a small inline mini-chart (stats + history combined).
Optional query params: ?mode, ?months, ?theme, ?format (same semantics as above).
Returns an animated SVG that toggles between the stats card and the history chart (CSS animation). Same query params as /combined.
Returns { "status": "ok" }.
| Name | Description |
|---|---|
dark |
GitHub dark (default) |
light |
Clean white |
monokai |
Monokai |
nord |
Nord |
solarized |
Solarized dark |
dracula |
Dracula |
Set a global default via the DEFAULT_THEME env var, or pass ?theme= on any request to override it.

src/
├── index.ts # Express server, routing, caching
├── logger.ts # pino logger wrapper
├── types.ts # shared TypeScript types
├── providers/
│ ├── chessdotcom.ts # Chess.com stats + history fetchers
│ ├── lichess.ts # Lichess stats + history fetchers
│ └── mappers/ # provider-specific response mappers
│ ├── chess.mapper.ts
│ └── lichess.mapper.ts
├── render/
│ ├── stats.ts # SVG stats card renderer
│ ├── chart.ts # SVG line chart renderer
│ ├── combined.ts # combined card + chart renderer
│ ├── blink.ts # animated toggle renderer
│ └── themes.ts # Theme definitions
- Bun (used for running TypeScript in the repo's scripts)
- TypeScript — codebase is written in
.ts - Express — HTTP server
pino/pino-http— loggingflag-icons— small flag icons used in cards- Pure SVG string generation — no canvas or headless browser required