# Watch Party (Frontend + Backend) Watch Party is a small full-stack app: - **Frontend:** Vite + React + TypeScript, served by **Nginx** - **Backend:** Go (Gin) API + PostgreSQL - **Orchestration:** One **docker-compose** that keeps FE/BE split on the same internal network - **Routing:** Your Debian Nginx is the public entrypoint and reverse-proxies to the Mac mini (where this stack runs) ``` router → Debian nginx (TLS) → Mac mini :3000 → [web nginx ↔ api ↔ db] ``` --- ## Repository Layout ``` . ├── docker-compose.yml # single compose for web + api + db + migrate ├── .env # stack configuration (see template below) ├── backend/ # Go API + migrations │ ├── cmd/ │ │ ├── migrate/ # one-off migration binary │ │ └── server/ # API server (Gin) │ ├── db/migration/ # SQL migrations (0001_init.sql, etc.) │ ├── Dockerfile # builds server + migrate │ └── go.mod / go.sum └── frontend/ # Vite + React + TS ├── Dockerfile # Vite build → Nginx runtime (envsubst for proxy) ├── ops/ # container runtime bits │ ├── entrypoint.sh │ ├── nginx.conf # (optional/manual) │ └── nginx.conf.template # uses BACKEND_ORIGIN ├── src/, public/, vite.config.ts, package*.json, tsconfig*.json ``` --- ## Services (Compose) - **web**: Nginx serving the built SPA, and proxying `/api/*` to `api:8082` (Docker DNS) - **api**: Go server (Gin), listens on `:8082`, health at `/healthz` - **migrate**: one-off job that runs the `migrate` binary, applies SQL from `backend/db/migration` - **db**: PostgreSQL 16 (internal only) Only **web** is published to your LAN (port `3000` by default). `api` and `db` are internal to the compose network. --- ## .env (template) ```dotenv ################## ## Frontend env ## ################## WEB_PORT=3000 PUBLIC_BASE_PATH=/watch-party/ BACKEND_ORIGIN=http://api:8082 ################# ## Backend env ## ################# # Postgres (container) POSTGRES_DB=watchparty POSTGRES_USER=admin POSTGRES_PASSWORD=change-me TZ=Asia/Tokyo COMPOSE_PLATFORM=linux/arm64/v8 # Backend server ADDR=:8082 GIN_MODE=release PGHOST=db POSTGRES_PORT=5432 PGSSLMODE=disable ``` > **Why mixed names?** The current Go code builds its DSN from `PGHOST`, `POSTGRES_PORT`, `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB`, and `PGSSLMODE`. --- ## Run (Production-like) ```bash docker compose build docker compose up -d ``` Open: `http://:3000/watch-party/` Health checks: - Web (nginx): `curl http://:3000/` - API through web proxy: `curl http://:3000/api/healthz` - API direct (inside network only): `curl http://api:8082/healthz` (from another container) Logs: ```bash docker compose logs -f migrate # should start, apply SQL, and exit 0 docker compose logs -f api docker compose logs -f web docker compose logs -f db ``` --- ## Debian Nginx (public reverse proxy) Terminate TLS on Debian and forward to the Mac mini: ```nginx # Serve the SPA under /watch-party/ location /watch-party/ { proxy_pass http://:3000/watch-party/; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $remote_addr; } # Proxy API via the web container (keeps same-origin from browser POV) location /api/ { proxy_pass http://:3000/api/; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $remote_addr; } ``` Security headers & HSTS on Debian are recommended. --- ## Local Development (hot reload) You can dev each side outside Docker: - **Frontend** ```bash cd frontend npm ci npm run dev # http://localhost:5173 ``` Configure your FE to call the API (e.g., via a local `.env` or vite config). - **Backend** ```bash cd backend go run ./cmd/server ``` Make sure Postgres is reachable (either from the compose `db` or your local). For a dev-only compose with Vite HMR, create an override (not included here). --- ## Notes & Conventions - **Only `web` is exposed** to the LAN. `api` and `db` are `expose`d internally. - The frontend **does not** use `host.docker.internal`; it proxies to `api:8082` by Docker DNS. - Backend healthcheck probes `http://localhost:8082/healthz` and the server binds `ADDR=:8082`. - Migrations live in `backend/db/migration/` and are applied by the **migrate** container at startup. - Vite base path is controlled by `PUBLIC_BASE_PATH` (e.g., `/watch-party/`). --- ## Quick Test Endpoints ```bash # Latest health: curl http://:3000/api/healthz # Current show (if table seeded and current_ep=true): curl http://:3000/api/current ``` --- ## Troubleshooting - **Migrate “hangs” / API starts instead** Ensure the backend image uses `CMD` (not `ENTRYPOINT`) and compose sets: ```yaml migrate: command: ["/app/migrate"] api: command: ["/app/server"] ``` - **404 for deep links** under `/watch-party/` Confirm SPA fallback is active in `frontend/ops/nginx.conf.template` and Vite `base` matches `PUBLIC_BASE_PATH`. - **DB not reachable** Check `.env` values match the mixed DSN env names. `PGHOST=db`, `POSTGRES_PORT=5432`, etc. - **CORS issues** There shouldn’t be any—browser hits `web`, which proxies to `api`, so it’s same-origin. --- ## License MIT