mirror of
https://github.com/nprasad2077/NBA_Go.git
synced 2026-09-22 14:05:13 +00:00
396 lines
18 KiB
Markdown
396 lines
18 KiB
Markdown
# NBA_Go
|
|
|
|
A high-performance NBA statistics REST API built with Go (Fiber), PostgreSQL, and NGINX. Data is scraped from Basketball Reference and served through a load-balanced, containerized stack with dual-stack IPv4/IPv6 support, read/write database splitting, and built-in Prometheus & Grafana observability.
|
|
|
|
---
|
|
|
|
## Live Production Endpoints
|
|
|
|
| Service / Endpoint | URL | Description |
|
|
| :--- | :--- | :--- |
|
|
| **API Base URL** | `https://nba.turbo-data.com` | Production API root (redirects to Swagger UI) |
|
|
| **Swagger UI Docs** | `https://nba.turbo-data.com/swagger/index.html` | Interactive API documentation & sandbox |
|
|
| **Player Totals** | `https://nba.turbo-data.com/api/playertotals` | Season totals with pagination & sorting |
|
|
| **Games & Box Scores** | `https://nba.turbo-data.com/api/games` | Game schedules, box scores, line scores |
|
|
| **Advanced Stats** | `https://nba.turbo-data.com/api/playeradvancedstats` | Advanced metrics (PER, WS, VORP, BPM) |
|
|
| **Shot Charts** | `https://nba.turbo-data.com/api/playershotchart` | Shot coordinate & outcome data |
|
|
| **Liveness Check** | `https://nba.turbo-data.com/health/live` | Process liveness probe |
|
|
| **Readiness Check** | `https://nba.turbo-data.com/health/ready` | Database readiness probe |
|
|
| **Metrics** | `https://nba.turbo-data.com/metrics` | Prometheus metrics endpoint |
|
|
| **Coolify Dashboard** | `https://turbo-data.com` | Infrastructure & deployment management |
|
|
|
|
---
|
|
|
|
## Architecture
|
|
|
|
```
|
|
┌──────────────────────────────────────────────────────────────┐
|
|
│ Traefik (Coolify Ingress Proxy :80 / :443 SSL) │
|
|
└──────────────────────────────┬───────────────────────────────┘
|
|
│
|
|
▼
|
|
┌──────────────────────────────────────────────────────────────┐
|
|
│ NGINX (Reverse Proxy, API Cache, Round-Robin Load Balancer) │
|
|
│ - Container port 8080 (Mapped to Host :8081) │
|
|
│ - Dual-stack IPv4/IPv6 upstream resolution │
|
|
│ - 30s response caching for /api/* with stale-while-revalidate│
|
|
├──────────────────────────────────────────────────────────────┤
|
|
│ API Instances x3 (Fiber on [::]:5000 Dual-Stack) │
|
|
│ ┌──────────┐ ┌─────────────┐ ┌───────────────────────┐ │
|
|
│ │ Routes │→ │ Controllers │→ │ GORM DBResolver (R/W) │ │
|
|
│ └──────────┘ └─────────────┘ └───────────┬───────────┘ │
|
|
├─────────────────────────────────────────────┼────────────────┤
|
|
│ Database Layer (PostgreSQL Cluster) │ │
|
|
│ ┌──────────────────────────────────────────┴─────────────┐ │
|
|
│ │ HAProxy Write Ingress (:5437) ➔ Primary DB (:5434) │ │
|
|
│ │ HAProxy Read Ingress (:5438) ➔ Read Replicas (:5435/6) │ │
|
|
│ └────────────────────────────────────────────────────────┘ │
|
|
├──────────────────────────────────────────────────────────────┤
|
|
│ Observability │
|
|
│ - Prometheus (Scrapes /metrics on each instance, Host :9091)│
|
|
│ - Grafana (Pre-provisioned dashboards, Host :3001) │
|
|
└──────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Key Architectural Highlights:
|
|
1. **Dual-Stack Networking**: The Go Fiber backend listens on `[::]:5000` (`net.Listen("tcp", ":5000")` with `app.Listener`), accepting incoming connections seamlessly over both IPv6 and IPv4 within Docker and Coolify networks.
|
|
2. **Read/Write DB Splitting**: GORM uses the `dbresolver` plugin to automatically route all write operations to the PostgreSQL Primary via HAProxy port 5437, while load balancing read queries across Read Replicas via HAProxy port 5438.
|
|
3. **Multi-Layer Caching & Rate Limiting**: NGINX provides an in-memory cache (`api_cache`) for 30 seconds, while Fiber middleware enforces per-client rate limiting (20 req/min per instance, ~60 req/min effective across 3 replicas).
|
|
|
|
---
|
|
|
|
### Project Structure
|
|
|
|
```
|
|
.
|
|
├── main.go # Application entry point (API server or import-data mode)
|
|
├── import.go # Data import orchestration
|
|
├── config/
|
|
│ └── database.go # Database connection & DBResolver R/W setup
|
|
├── models/ # GORM models (Game, PlayerAdvancedStat, PlayerTotalStat, etc.)
|
|
├── controllers/ # HTTP handlers, DTOs, health checks, pagination
|
|
│ ├── game_controller.go
|
|
│ ├── health_controller.go # /health/live and /health/ready handlers
|
|
│ ├── player_advanced_controller.go
|
|
│ ├── player_shot_chart_controller.go
|
|
│ └── player_total_controller.go
|
|
├── routes/ # Route registration grouped by domain
|
|
├── services/ # Scrapers (Basketball Reference via goquery)
|
|
├── utils/
|
|
│ ├── middleware/ # Rate limiter, Prometheus metrics, API key auth
|
|
│ ├── metrics/ # Prometheus counter & histogram definitions
|
|
│ └── security/ # API key hashing & generation
|
|
├── nginx/ # NGINX reverse proxy & cache configuration
|
|
├── prometheus/ # Prometheus scrape configuration
|
|
├── grafana/ # Pre-provisioned dashboards & datasources
|
|
├── docker-compose.yml # Production deployment configuration (Coolify)
|
|
├── docker-compose.local.yml # Local development configuration (includes local Postgres)
|
|
└── docker-compose.override.yml # Override for remote DB development
|
|
```
|
|
|
|
---
|
|
|
|
## API Endpoints
|
|
|
|
| Method | Path | Description | Public |
|
|
| :--- | :--- | :--- | :---: |
|
|
| `GET` | `/` | Redirects to `/swagger/index.html` | Yes |
|
|
| `GET` | `/swagger/*` | Interactive Swagger UI documentation | Yes |
|
|
| `GET` | `/api/games` | Paginated games with box scores, line scores, team/player stats | Yes |
|
|
| `GET` | `/api/playeradvancedstats` | Advanced metrics (PER, WS, VORP, BPM, etc.) | Yes |
|
|
| `GET` | `/api/playertotals` | Season totals (points, rebounds, assists, etc.) | Yes |
|
|
| `GET` | `/api/playershotchart` | Shot chart coordinates and made/missed outcomes | Yes |
|
|
| `GET` | `/health/live` | Process liveness probe | Yes |
|
|
| `GET` | `/health/ready` | Database connection readiness probe | Yes |
|
|
| `GET` | `/metrics` | Prometheus metrics scrape endpoint | Yes |
|
|
| `POST`| `/admin/keys` | Create API key (requires `X-Admin-Secret` header) | Admin |
|
|
| `POST`| `/admin/keys/:id/revoke` | Revoke API key (requires `X-Admin-Secret` header) | Admin |
|
|
|
|
---
|
|
|
|
### Query Parameters (Data Endpoints)
|
|
|
|
| Parameter | Type | Default | Description |
|
|
| :--- | :--- | :--- | :--- |
|
|
| `page` | `int` | `1` | Page number |
|
|
| `pageSize` | `int` | `20` | Results per page (max 100) |
|
|
| `sortBy` | `string` | varies | Field to sort by (e.g., `points`, `date`, `winShares`) |
|
|
| `ascending` | `bool` | `false` | Sort ascending (`true`) or descending (`false`) |
|
|
| `season` | `int` | - | Filter by season year (e.g., `2024`, `2025`) |
|
|
| `team` | `string` | - | Filter by team abbreviation (e.g., `LAL`, `BOS`, `GSW`) |
|
|
| `playerId` | `string` | - | Filter by player ID (e.g., `jamesle01`, `curryst01`) |
|
|
| `isPlayoff` | `bool` | `false` | Filter for playoff games or stats |
|
|
|
|
#### Additional Parameters for `/api/games`:
|
|
- `date`: Filter by exact date (`YYYY-MM-DD`).
|
|
- `gameId`: Filter by specific game ID (e.g., `202501010LAL`).
|
|
- `include`: Comma-separated associations to preload (`lineScores`, `playerGameBasicStats`, `playerGameAdvStats`, `teamGameBasicStats`, `teamGameAdvStats`).
|
|
|
|
---
|
|
|
|
### Example Requests
|
|
|
|
#### Production (Live API)
|
|
```bash
|
|
# Get top scorers for the 2025 season
|
|
curl "https://nba.turbo-data.com/api/playertotals?season=2025&sortBy=points&pageSize=10"
|
|
|
|
# Get a specific game with full box score preloaded
|
|
curl "https://nba.turbo-data.com/api/games?gameId=202501010LAL&include=lineScores,playerGameBasicStats,teamGameBasicStats"
|
|
|
|
# Get LeBron James' advanced stats across career
|
|
curl "https://nba.turbo-data.com/api/playeradvancedstats?playerId=jamesle01&sortBy=season&ascending=true"
|
|
|
|
# Get Stephen Curry's shot chart data for 2024
|
|
curl "https://nba.turbo-data.com/api/playershotchart?playerId=curryst01&season=2024"
|
|
|
|
# Check API readiness
|
|
curl "https://nba.turbo-data.com/health/ready"
|
|
```
|
|
|
|
#### Local Development
|
|
```bash
|
|
# Query local NGINX load balancer (:8081)
|
|
curl "http://localhost:8081/api/playertotals?season=2025&pageSize=5"
|
|
|
|
# Query local Prometheus metrics
|
|
curl "http://localhost:8081/metrics"
|
|
```
|
|
|
|
---
|
|
|
|
## Response Format
|
|
|
|
All data endpoints return structured JSON with pagination metadata:
|
|
|
|
```json
|
|
{
|
|
"data": [
|
|
{
|
|
"playerId": "curryst01",
|
|
"playerName": "Stephen Curry",
|
|
"season": 2024,
|
|
"team": "GSW",
|
|
"points": 1956,
|
|
"assists": 379,
|
|
"rebounds": 330,
|
|
"threeP": 357,
|
|
"fieldPercent": 0.450
|
|
}
|
|
],
|
|
"pagination": {
|
|
"total": 450,
|
|
"page": 1,
|
|
"pageSize": 20,
|
|
"pages": 23
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Rate Limiting & Caching
|
|
|
|
- **Rate Limiting**: Configured per client IP at **20 requests per minute per instance** (~60 req/min across the 3-instance cluster). If exceeded, the API responds with `HTTP 429 Too Many Requests`.
|
|
- **Custom Limits**: Configurable via `RATE_LIMIT_MAX` (default `20`) and `RATE_LIMIT_EXPIRATION_SECONDS` (default `60s`).
|
|
- **Internal Benchmark Bypass**: If `BENCHMARK_KEY` is configured in the environment, passing matching token in `X-Benchmark-Key` header skips rate limiting for automated benchmarks.
|
|
- **Multi-Layer Caching**:
|
|
- **Edge CDN (Cloudflare)**: Caches GET requests with `s-maxage=86400` (24h) and `stale-while-revalidate=600`.
|
|
- **NGINX Reverse Proxy**: Provides an in-memory cache (`api_cache`) for **30 seconds** (`keys_zone=api_cache:10m`).
|
|
- Responses include `CF-Cache-Status` (`HIT`/`MISS`) and `X-Cache-Status` (`HIT`/`MISS`) headers.
|
|
|
|
---
|
|
|
|
## Getting Started
|
|
|
|
### Prerequisites
|
|
- Docker & Docker Compose
|
|
- Go 1.23+ (for local development)
|
|
|
|
### Environment Variables
|
|
Create a `.env` file in the project root:
|
|
|
|
```env
|
|
DB_HOST=178.105.149.129
|
|
DB_USER=your_user
|
|
DB_PASSWORD=your_password
|
|
DB_NAME=appdb
|
|
DB_PORT=5432
|
|
ADMIN_SECRET=your_admin_secret
|
|
BENCHMARK_KEY=your_internal_benchmark_token
|
|
RATE_LIMIT_MAX=20
|
|
```
|
|
|
|
### Running Locally with Docker
|
|
|
|
```bash
|
|
# Start full local stack (Postgres, 3 API replicas, NGINX, Prometheus, Grafana)
|
|
docker compose -f docker-compose.local.yml up --build -d
|
|
|
|
# Or using Makefile
|
|
make up
|
|
```
|
|
|
|
Local service ports:
|
|
- **API (via NGINX)**: [http://localhost:8081](http://localhost:8081)
|
|
- **Swagger Docs**: [http://localhost:8081/swagger/index.html](http://localhost:8081/swagger/index.html)
|
|
- **Prometheus**: [http://localhost:9091](http://localhost:9091)
|
|
- **Grafana**: [http://localhost:3001](http://localhost:3001) (`admin` / `testing`)
|
|
- **API Direct Instances**: `http://localhost:5001`, `5002`, `5003`
|
|
|
|
### Initial Data Import
|
|
|
|
To run the initial schema migrations and scrape Basketball Reference:
|
|
|
|
```bash
|
|
docker compose -f docker-compose.local.yml run --rm db-init
|
|
```
|
|
|
|
This launches the container in one-off `import-data` mode:
|
|
1. Executes GORM `AutoMigrate` against the Primary database.
|
|
2. Scrapes player advanced stats, regular season totals, playoff totals, and shot charts.
|
|
3. Upserts all records into PostgreSQL.
|
|
|
|
### Stopping Services
|
|
|
|
```bash
|
|
docker compose -f docker-compose.local.yml down
|
|
# or
|
|
make down
|
|
```
|
|
|
|
---
|
|
|
|
## Production Deployment (Coolify)
|
|
|
|
The application is deployed via Coolify on branch `shooting`:
|
|
- Uses Traefik as the edge reverse proxy with automated Let's Encrypt SSL.
|
|
- Bridges into the `coolify-shared` network to communicate with external databases and proxy services.
|
|
- Configured with multi-container services (`api1`, `api2`, `api3`, `nginx`, `prometheus`, `grafana`, `db-init`).
|
|
|
|
To trigger a redeployment from the command line on the server:
|
|
```bash
|
|
docker compose -f /data/coolify/applications/<app_uuid>/docker-compose.yaml up -d --build
|
|
```
|
|
|
|
---
|
|
|
|
## Observability
|
|
|
|
### Prometheus Metrics
|
|
Exposed at `/metrics`:
|
|
- `nba_http_requests_total` — Counter partitioned by `method`, `endpoint`, and `status`.
|
|
- `nba_http_request_duration_seconds` — Histogram tracking request latencies.
|
|
- `nba_db_operations_total` — Counter tracking database read/write queries.
|
|
|
|
### Grafana Dashboards
|
|
Pre-provisioned dashboards in `grafana/dashboards` visualize:
|
|
- Request throughput and error rates per endpoint.
|
|
- NGINX cache hit ratio.
|
|
- Latency percentiles (p50, p95, p99).
|
|
- Database read/write distribution across Primary and Replicas.
|
|
|
|
---
|
|
|
|
## Swagger API Documentation
|
|
|
|
To regenerate Swagger documentation after modifying controllers or annotations:
|
|
|
|
```bash
|
|
swag init -g main.go -o docs
|
|
```
|
|
|
|
---
|
|
|
|
## Testing & Load Testing
|
|
|
|
### Unit & Integration Tests
|
|
```bash
|
|
go test -v ./...
|
|
```
|
|
|
|
### Advanced Load Testing (`test/loadtest.go`)
|
|
The built-in load testing engine simulates high-volume, realistic traffic patterns across edge caches, reverse proxies, and the origin PostgreSQL database.
|
|
|
|
See the complete [Load Testing Guide](docs/loadtesting.md) for full details.
|
|
|
|
#### Key Capabilities:
|
|
- **Dynamic Query Randomization (`-varyParams`, `-complexity`)**: Permutes combinations of seasons, teams, sort columns, ascending/descending, page sizes, playoff filters, and heavy database relation joins (`include=lineScores,playerGameBasicStats,teamGameAdvStats`).
|
|
- **Cold-Cache Stress Testing (`-cacheBust`)**: Injects unique request nonces to ensure a 100% cache-miss rate for benchmarking raw database execution.
|
|
- **Cache Telemetry Split**: Inspects `CF-Cache-Status` (`HIT`/`MISS`) and `Age` headers to provide separate latency profiles for Edge Cache Hits vs. Origin / Database Misses.
|
|
- **Latency Percentiles**: Reports **Min**, **p50 (Median)**, **p75**, **p90**, **p95**, **p99**, **Max**, and **Average** latency.
|
|
- **Distributed Client Simulation (`-rotateIPs`)**: Injects rotating `X-Real-IP` and `X-Forwarded-For` headers to accurately simulate thousands of distinct clients.
|
|
- **Benchmark Token Support (`-benchmarkKey`)**: Passes `X-Benchmark-Key` header to bypass rate limits during testing.
|
|
- **Multi-Endpoint Traffic Mix (`-endpointMix`)**: Distributes traffic across `/api/playeradvancedstats`, `/api/playertotals`, `/api/games`, and `/api/playershotchart`.
|
|
|
|
#### Load Test CLI Flags:
|
|
|
|
| Flag | Default | Description |
|
|
| :--- | :--- | :--- |
|
|
| `-url` | `http://localhost:5000/api/playeradvancedstats` | Target endpoint or base URL |
|
|
| `-n` | `100` | Number of requests to send |
|
|
| `-c` | `10` | Concurrency level (worker goroutines) |
|
|
| `-varyParams` | `false` | Enable query parameter & filter randomization |
|
|
| `-complexity` | `standard` | Query complexity: `standard` or `high` |
|
|
| `-cacheBust` | `false` | Force 100% cold-cache misses on CDN/proxy |
|
|
| `-benchmarkKey` | `$BENCHMARK_KEY` | Token for `X-Benchmark-Key` header |
|
|
| `-rotateIPs` | `false` | Rotate synthetic client IPs |
|
|
| `-endpointMix` | `""` | Multi-endpoint traffic mix (e.g. `playeradvancedstats:40,playertotals:30,games:30`) |
|
|
| `-pageMix` | `""` | Weighted page mix (e.g. `1-3:60,4-10:30,11-20:10`) |
|
|
| `-retryOnRateLimit` | `false` | Retry 429 responses with backoff |
|
|
| `-log` | `loadtest.log` | Output log file destination |
|
|
|
|
#### Example Usage:
|
|
|
|
```bash
|
|
# 1. Realistic Traffic with Parameter Variance (Recommended)
|
|
go run ./test/loadtest.go \
|
|
-url 'https://nba.turbo-data.com/api/playeradvancedstats' \
|
|
-n 3000 \
|
|
-c 20 \
|
|
-varyParams \
|
|
-complexity high \
|
|
-rotateIPs \
|
|
-benchmarkKey 'your-benchmark-token' \
|
|
-pageMix '1-3:60,4-10:30,11-20:10' \
|
|
-log ./test/results.log
|
|
|
|
# 2. Pure Cold-Cache / Origin Database Benchmark (100% Cache Misses)
|
|
go run ./test/loadtest.go \
|
|
-url 'https://nba.turbo-data.com/api/playeradvancedstats' \
|
|
-n 1000 \
|
|
-c 15 \
|
|
-varyParams \
|
|
-cacheBust \
|
|
-benchmarkKey 'your-benchmark-token' \
|
|
-log ./test/results.log
|
|
|
|
# 3. Multi-Endpoint Traffic Mix
|
|
go run ./test/loadtest.go \
|
|
-url 'https://nba.turbo-data.com' \
|
|
-endpointMix 'playeradvancedstats:40,playertotals:30,games:30' \
|
|
-varyParams \
|
|
-n 2000 \
|
|
-c 20 \
|
|
-benchmarkKey 'your-benchmark-token' \
|
|
-log ./test/results.log
|
|
```
|
|
|
|
---
|
|
|
|
## Tech Stack
|
|
|
|
| Layer | Technology |
|
|
| :--- | :--- |
|
|
| **Language** | Go 1.24 |
|
|
| **HTTP Framework** | Fiber v2 (`valyala/fasthttp`) |
|
|
| **Database** | PostgreSQL 17 (Primary + Read Replicas) |
|
|
| **ORM & Routing** | GORM + `dbresolver` (R/W splitting) |
|
|
| **Reverse Proxy / Cache** | NGINX Stable (API caching & round-robin) |
|
|
| **Edge Routing & SSL** | Traefik v3 (Let's Encrypt automated TLS) |
|
|
| **Monitoring** | Prometheus + Grafana |
|
|
| **Scraping** | `goquery` (HTML parsing) |
|
|
| **Documentation** | Swagger 2.0 (`swaggo/swag`) |
|
|
| **Deployment** | Docker, Docker Compose, Coolify |
|
|
|