A full-stack scaffold combining a FastAPI backend with a React/Vite frontend, complete with database migrations, caching, rate-limiting, authentication, WebSocket support, metrics, code generation and an in-browser debug overlay.
- Prerequisites
- Installation & Setup
- Configuration
- Database Setup
- Authentication
- Metrics
- Caching & Rate Limiting
- WebSocket API
- Code Generation
- Debug Overlay
- Running in Development
- Running in Production (Docker)
- Project Structure
- CLI Scripts (Root-Level)
- Tips & Tricks
- Further Reading
- nvm/nvm-windows
- Node.js ≥ 18.x (see correspending nvm documentation for installation steps)
- pnpm
- uv
- Python 3.13
- sqlite3 (for SQLite) or Docker & Docker Compose (for Postgres)
- Git
-
Clone the project:
git clone https://github.com/dsi-icl/create-ove-demo.git cd create-ove-demo -
Install dependencies, build and link:
pnpm install pnpm run link
-
Create the project (can be run anywhere as create-ove-demo command is available globally after linking)
create-ove-demo cd <your new project>
-
Review and customize environment files:
backend/.env(generated by setup)- For production, create
backend/.env.production
Edit backend/.env (or .env.production) to control:
DATABASE_URL=…# e.g. sqlite+aiosqlite:///data/main.db
CACHE_HOST=…
CACHE_PORT=6379
CACHE_PASSWORD=…
CACHE_ENABLED=true
CACHE_EXPIRATION=60
DISABLE_AUTH=false
USE_LEGACY_AUTH=true
LEGACY_AUTH_KEY=<your-secret>
PROTECT_METRICS=true
METRICS_USERNAME=metrics
METRICS_PASSWORD=<your-pass>
TOKEN_EXPIRY=3600
FRONTEND_ORIGIN=http://localhost:5173
WEBSOCKET_ORIGIN=ws://localhost:5173
PORT=8000
INTERVAL=30# tick interval in secondsFrontend picks up VITE_ vars from backend/.env:
VITE_BACKEND=http://localhost:8000
VITE_ENABLE_DEBUG=trueNo extra setup—backend/data/main.db is created automatically.
To run future migrations:
cd backend
uv run alembic upgrade headdocker-compose up -d
cd backend
uv run alembic upgrade head-
Legacy OTP
GET /auth/redirect?security_token=<LEGACY_KEY>&to=<URL>
Issuessessioncookie, then redirects to<URL>. -
Cookie validation
GET /auth/validate→ returnstrueif cookie valid. -
API-Key flow
GET /auth/token+Authorization: Bearer <API_KEY>→ returns a one-time token for?security_token=<token>.
Respect DISABLE_AUTH, USE_LEGACY_AUTH and cookie expiry (TOKEN_EXPIRY).
Prometheus metrics exposed at GET /metrics (port 8000). Protected via HTTP Basic (METRICS_USERNAME/
METRICS_PASSWORD) unless DISABLE_AUTH=true.
Key metrics:
socket_active_connectionssocket_events_total{event="…"}socket_event_duration_seconds{event="…"}- Standard FastAPI HTTP counters
- HTTP caching: decorate routes with
@cached(expire=…)(Redis backend, JSON coder). Toggle withCACHE_ENABLED. - Rate limiting: decorate with
@limiter.limit("100/second"). Defaults inapp/core/rate_limiter.py.
Example:
@router.get("/example")
@limiter.limit("50/minute")
@cached
async def get_example(...):
return [...]Socket.IO mounted at /ws/socket.io under namespace /v1. Supply:
roomquery param- valid session cookie (or
DISABLE_AUTH=true)
Client example (TS):
import {createSocketClient} from "@/api/sockets-v1";
const client = createSocketClient(env.VITE_BACKEND, {
path: "/ws/socket.io",
withCredentials: true,
query: {room: "myroom"},
});
client.connect();
client.onConnect(() => console.log("connected"));
client.emitStart();
client.onTick(payload => console.log("tick", payload));
client.emitStop();
client.emitReset();Example events:
| Event | Direction | Payload | Description |
|---|---|---|---|
| get_state | client→server | none | Ack: returns { status, timestamp } |
| start | client→server | none | Begin periodic “tick” |
| stop | client→server | none | Stop ticking |
| reset | client→server | none | Reset state & timestamp |
| tick | server→clients | { timestamp } |
Broadcast every INTERVAL seconds |
Each physical Data Observatory maps to a unique room. All clients — whether controllers (which emit commands) or views (read-only pages) — connect to the same room and share state in real time.
-
Controllers connect:
import { createSocketClient } from "@/api/sockets-v1"; const ctrl = createSocketClient(env.VITE_BACKEND, { path: "/ws/socket.io", withCredentials: true, query: { room: "data-observatory" }, }); ctrl.connect(); // start the simulation for all viewers: ctrl.emitStart(); // later… ctrl.emitStop(); ctrl.emitReset();
-
Views connect:
import { createSocketClient } from "@/api/sockets-v1"; const view = createSocketClient(env.VITE_BACKEND, { path: "/ws/socket.io", withCredentials: true, query: { room: "data-observatory" }, }); view.connect(); view.emitGetState().then(state => renderState(state)); view.onTick(payload => renderTick(payload));
All events are scoped to "data-observatory". When a controller emits start, every connected view in that room begins receiving tick broadcasts and stays in sync.
After backend schema or API changes:
-
Generate JSON-Schemas & AsyncAPI
cd backend uv run scripts/schemas.pyOutputs:
backend/schemas/openapi.jsonbackend/schemas/asyncapi/v1.jsonbackend/schemas/entities/*.schema.json
-
Sync frontend types & clients
cd ../frontend pnpm run syncRuns:
cli/codegen/schemas.ts→src/api/schemas.tscli/codegen/api.ts→src/api/api.tscli/codegen/sockets.ts→src/api/sockets-v1.ts
Toggle the in-browser debug panel with Ctrl+K (or ⌘+K). It shows:
- Live Socket.IO events & payloads
- HTTP request/response log
- Application log entries
- Inline editing of client state via Zustand
- Toasts on errors or forbidden actions
Enable in production via VITE_ENABLE_DEBUG=true.
cd backend
uv run fastapi dev app/main.py --port 8000- OpenAPI UI →
http://localhost:$PORT/docs - AsyncAPI viewer →
http://localhost:$PORT/public/asyncapi.html
cd frontend
pnpm run devVisit http://localhost:5173
docker-compose up -dServices:
- on port 80
- redis for caching (port 6379)
docker build -t registry.example.com/<your project>:latest .
docker run -d \
--name <your project> \
-p 80:80 \
-v $(pwd)/backend/.env.production:/.env:ro \
-v $(pwd)/backend/data:/data \
registry.example.com/<your project>:latest├── backend │ ├── app/ # FastAPI app: auth, cache, sockets, api, db │ ├── data/ # SQLite DB or Postgres volume │ ├── migrations/ # Alembic configs & versions │ ├── scripts/ # Codegen: OpenAPI, AsyncAPI, JSON schemas │ ├── public/ # Static docs (asyncapi.html, docs.html) │ ├── .env(.production) # Env vars │ └── main.py # Entrypoint ├── frontend │ ├── src/ │ │ ├── api/ # Generated API clients & schemas │ │ ├── components/ # UI primitives (shadcn) │ │ ├── lib/ # sockets.ts, store.ts, logger.ts │ │ ├── hooks/ # custom React hooks │ │ └── Debug.tsx # in-app debug overlay │ ├── cli/ │ │ └── codegen/ # TS codegen scripts │ ├── public/ # Vite static assets │ ├── package.json # scripts, deps, pnpm lock │ └── vite.config.ts ├── docker-compose.yml ├── Dockerfile └── README.md
These npm scripts live in the root package.json (this CLI/scaffold tool):
-
build: bundle index.ts into dist/index.cjs, copy assets/.
-
check: run Prettier and ESLint fixes.
-
clean: remove dist/.
-
format: run Prettier.
-
lint: run ESLint.
-
link: build and globally link this CLI (pnpm link).
-
prepublishOnly: ensures a build before pnpm publish.
-
Keep backend/.env* out of Git — store secrets securely.
-
After backend/API changes, always run:
cd backend && uv run scripts/schemas.py
cd ../frontend && pnpm run sync-
Use React-Query DevTools in development.
-
Tune auth & cache via app/core/config.py.
-
Generate UI primitives with pnpx shadcn add button card ….
-
Monitor Prometheus metrics in Grafana or similar.