-
Notifications
You must be signed in to change notification settings - Fork 3
Development
This guide covers the complete development workflow for the Model Hotel multi-provider LLM gateway.
| Tool | Version | Purpose |
|---|---|---|
| Go | 1.26+ | Backend runtime (required by go.mod) |
| Node.js | 24+ | Frontend build tooling (CI uses Node 24; Dockerfile uses node:26-alpine) |
| pnpm | 10.33.0 | Frontend package manager (specified in package.json) |
| PostgreSQL | 16+ | Database (Docker Compose uses postgres:16-alpine) |
| Docker & Docker Compose | Latest | Containerized database and full-stack deployment |
| golangci-lint | v2.11+ | Go linting (CI requirement) |
model-hotel/
βββ cmd/server/ # Go server entry point
β βββ main.go # Server setup, middleware, graceful shutdown
β βββ static.go # SPA static file serving
β βββ static/ # Embedded frontend build output
βββ internal/ # Backend packages (private)
β βββ admin/ # Admin token management (SHA-256)
β βββ api/ # HTTP handlers (REST + SSE)
β βββ auth/ # AES-256-GCM encryption, key caching
β βββ config/ # Environment configuration
β βββ ctxkeys/ # Type-safe context keys
β βββ db/ # PostgreSQL connection + migrations
β βββ debuglog/ # Structured logging wrapper
β βββ events/ # SSE event bus (pub/sub)
β βββ failover/ # Failover group routing
β βββ model/ # Model entity + repository
β βββ provider/ # Provider CRUD + model discovery
β βββ proxy/ # OpenAI-compatible proxy
β βββ ratelimit/ # Token-bucket rate limiting
β βββ settings/ # DB-backed settings with cache
β βββ util/ # Helpers (Docker stats, HTTP utils)
β βββ virtualkey/ # Virtual key CRUD (SHA-256 hashed)
β βββ webauthn/ # WebAuthn/FIDO2 passkey sessions + credentials
βββ web/ # React + TypeScript frontend
β βββ src/
β β βββ api/ # API client + TypeScript types
β β βββ components/ # Reusable UI components
β β βββ context/ # React contexts (Theme, Toast, Event)
β β βββ hooks/ # Custom React hooks
β β βββ i18n/ # i18next setup + locale files (repo is source of truth; translated by hand)
β β βββ pages/ # Dashboard, Providers, Models, etc.
β β βββ utils/ # Formatting, SSE, model utils
β βββ public/ # Static assets (favicon, icons)
β βββ index.html # HTML entry point
β βββ vite.config.ts # Vite configuration
β βββ tailwind.config.js # Tailwind CSS config
β βββ tsconfig.json # TypeScript config
β βββ package.json # Dependencies + scripts
βββ .github/workflows/
β βββ ci.yml # CI pipeline (test, lint, build)
βββ docker-compose.yml # Production stack (app + db)
βββ docker-compose.test.yml # Ephemeral test database (port 5433)
βββ Dockerfile # Multi-stage build (Node β Go β Alpine)
βββ Makefile # Build/test/lint commands
βββ go.mod # Go module definition
git clone https://github.com/hugalafutro/model-hotel.git
cd model-hotel# Backend
go mod tidy
# Frontend
cd web
pnpm install
cd ..cp .env.example .envGenerate required secrets:
# Master encryption key (AES-256-GCM)
MASTER_KEY=$(openssl rand -base64 32)
# Admin authentication token
ADMIN_TOKEN=$(openssl rand -hex 16)
# Database password
POSTGRES_PASSWORD=$(openssl rand -hex 16)Edit .env with your generated values. Never commit .env - it is gitignored.
# Option A: Docker (recommended)
make docker-up
# Option B: Local PostgreSQL
createdb modelhotel
# Set DATABASE_URL in .env to point to localhost# Build and run
make run
# Or build only
make build
./bin/serverThe server starts on http://localhost:8081 (configured via HOST_PORT in .env).
cd web
pnpm devThe Vite dev server runs on http://localhost:5173 with hot module replacement.
Note: When running the frontend dev server, you still need the backend running for API calls. The dev server proxies API requests to the backend via the
api/client.tsconfiguration.
The backend uses Go 1.26 with the module github.com/hugalafutro/model-hotel. All internal packages live under internal/ and are not importable by external modules.
Key packages:
| Package | Responsibility |
|---|---|
cmd/server/ |
Entry point, middleware chain, graceful shutdown |
internal/api/ |
HTTP handlers for REST endpoints |
internal/proxy/ |
OpenAI-compatible /v1/chat/completions proxy |
internal/provider/ |
Provider API clients, model discovery |
internal/db/ |
PostgreSQL connection pool, migrations |
internal/events/ |
SSE event bus for real-time UI updates |
# Development run (builds then runs)
make run
# Build only
make build
./bin/server
# Direct Go run (no binary)
go run ./cmd/server/# All tests
make test
# Single package
go test ./internal/proxy/...
# Verbose output
go test -v ./...
# CI-equivalent (requires test database)
go test -timeout 5m ./...Integration tests require a PostgreSQL database. Use the ephemeral test database:
# Start test database (port 5433)
make test-db-up
# Run tests
make test
# Stop and remove test database
make test-db-downThe test database uses docker-compose.test.yml which creates an isolated testdb database with no persistent volumes.
Test patterns:
- Unit tests: No database required, use mocks
- Integration tests: Use
internal/db/testdb.gohelpers - Handler tests: Use
newTestHandler()frominternal/api/handler_integration_test.go
β οΈ No skipped tests: Tests must pass or fail β nevert.Skip/it.skip/describe.skip/.only/it.todo, and no environment-gated skips that silently no-op in CI. If a test needs an external dependency (PostgreSQL,pg_dump, docker), CI must provide it rather than skip it. (Policy: PR #340.)
Example test structure:
func TestSomething(t *testing.T) {
// Arrange
db := testdb.New(t)
repo := NewRepository(db.Pool())
// Act
result, err := repo.Create(ctx, entity)
// Assert
assert.NoError(t, err)
assert.NotNil(t, result.ID)
}# Run all linters (CI requirement)
golangci-lint run ./...
# Format imports and run go fmt
make fmt
# Go vet
go vet ./...The make fmt command runs both gci (import formatting) and go fmt on all Go source files.
The CI pipeline runs golangci-lint v2.11 with the following linters enabled:
-
gci- import ordering -
govet- standard Go vet checks - Additional linters configured in
.golangci.yml
The backend uses internal/debuglog for structured logging. Control logging via the DEBUG_LOG environment variable:
# Enable debug logging
DEBUG_LOG=true ./bin/server
# Disable (production default)
DEBUG_LOG=false ./bin/serverLog levels: Info, Warn, Error. Never use fmt.Println in production code.
cd web
pnpm installpnpm dev- Runs on
http://localhost:5173 - Hot module replacement enabled
- Proxies API calls to backend (configured in
vite.config.ts)
pnpm buildOutput is written to web/dist/ and embedded into the Go binary at cmd/server/static/.
The frontend uses both ESLint and Biome. Both must pass for pre-commit checks.
# ESLint
pnpm lint
# Biome (format + lint)
pnpm biome check --write src/components/Foo.tsx
# Biome (check only, no writes)
pnpm biome check src/components/Foo.tsx
# Run all checks
pnpm lint && pnpm biome check
β οΈ Critical: Biome MUST run from theweb/directory with paths relative toweb/.
- β
pnpm biome check --write src/components/Foo.tsx- β
pnpm biome check --write web/src/components/Foo.tsx
Running from the project root causes Biome to fail with "configuration resulted in errors" due to the nested biome.json structure.
# Run tests once
pnpm test
# Watch mode
pnpm test:watch
# Run with coverage (full suite, ~85 seconds)
cd web && pnpm vitest run --coverage 2>&1 | tail -200Tests use Vitest with jsdom for DOM simulation. Test files are co-located with source files (*.test.ts).
β οΈ Coverage Command: Use the exact command above for coverage. Do NOT modify it (no grep pipes, no2>/dev/null). The2>&1 | tail -200is essential to capture the coverage table after Node's experimental warnings. Takes ~85 seconds with full CPU utilization.
| Directory | Purpose |
|---|---|
src/api/ |
Fetch wrapper (client.ts), TypeScript types (types.ts) |
src/components/ |
Reusable UI components |
src/pages/ |
Top-level pages (Dashboard, Providers, Models, etc.) |
src/context/ |
React contexts (Theme, Toast, Event, Storage) |
src/hooks/ |
Custom hooks (useLocalStorage, useModels, etc.) |
src/utils/ |
Helpers (formatting, SSE, model utils) |
Key patterns:
-
API client:
api/client.ts- fetch wrapper with error handling -
Type safety: All API responses typed via
api/types.ts -
State management: React Context +
useReducerfor complex state - Styling: Tailwind CSS v4 with custom theme
The docker-compose.yml defines two services:
| Service | Description | Ports | Volumes |
|---|---|---|---|
app |
Full Model Hotel application | 8081:8080 |
./.data:/data, Docker socket (ro, commented out by default) |
db |
PostgreSQL 16 | (not exposed to host) | ./.data/pgdata:/var/lib/postgresql/data |
# Stop all services
docker compose down
# Stop and remove volumes (destructive)
docker compose down -v
# View logs (follow mode)
make docker-logsThe make docker-logs command is a convenience wrapper around docker compose logs -f to stream logs from all services.
The frontend is embedded in the Go binary. Any change to web/src/ requires a full rebuild:
# Rebuild and restart
docker compose build app
docker compose up -d app
β οΈ Important:docker compose restart appdoes NOT pick up frontend changes. You must rebuild.
Ask before rebuilding - other developers may be working on the stack.
Docker Compose reads from .env at the project root. Key variables:
| Variable | Description | Default |
|---|---|---|
MASTER_KEY |
AES-256-GCM encryption key | required |
POSTGRES_PASSWORD |
Database password | required |
ADMIN_TOKEN |
Admin auth token | Auto-generated |
HOST_PORT |
External port | 8081 |
POSTGRES_USER |
Database user | modelhotel |
POSTGRES_DB |
Database name | modelhotel |
POSTGRES_HOST |
Database host (internal) | db |
DEBUG_LOG |
Enable debug logging | false |
RATE_LIMIT_ENABLED |
Enable rate limiting | true |
CORS_ORIGINS |
Allowed CORS origins | http://localhost:5173,http://localhost:8081 |
See .env.example for the full list.
The CI pipeline (.github/workflows/ci.yml) runs on every push and pull request:
| Job | Description |
|---|---|
go-test |
Runs go test -timeout 5m ./... against PostgreSQL 16, enforces an 80% coverage threshold, uploads to Codecov |
go-lint |
Runs golangci-lint v2.11 |
go-vet |
Runs go vet ./...
|
frontend |
Runs pnpm lint, pnpm vitest run --coverage (80% threshold, Codecov upload), pnpm build
|
docker-build |
Verifies Docker image builds successfully |
i18n-check |
Verifies all 28 locales are in sync with en.json: no missing keys, {{placeholder}} parity, no non-allowlisted English values |
Before committing changes:
- β
go test ./...- All Go tests pass - β
golangci-lint run ./...- Go linting passes - β
cd web && pnpm lint- ESLint passes - β
cd web && pnpm biome check- Biome passes
Note: Do NOT run the full Vite build for pre-commit checks.
lint+go testis sufficient.
Locale files in web/src/i18n/locales/ are the single source of truth (the project previously synced with Crowdin; that integration was removed). The workflow when adding user-facing strings:
- Add the key to
en.jsonAND to all 28 other locales. -
Translate the new keys by hand into each locale, keeping
{{placeholders}},<tags>, acronyms, and brand names verbatim. The quickest correct way is a one-off script that reusestools/i18n-translate/translate.py'sload_locale/set_path/save_localehelpers (preserves nesting + formatting). - Intentionally-English values (brand names, loanwords like "Failover", or a word genuinely identical in some language) belong in
tools/i18n-translate/allow-english.json.
make i18n-check is the CI gate: it runs offline (no network) and fails on missing keys, broken {{placeholder}} parity, or non-allowlisted English values. Translation corrections are welcome as plain PRs against the locale files.
- Make changes to backend or frontend code
-
Backend: Run targeted tests
go test ./internal/proxy/... -
Frontend: Run Biome + ESLint
cd web pnpm biome check --write src/components/Changed.tsx pnpm lint -
Integration test: Run full test suite if changes affect multiple packages
make test-db-up go test -timeout 5m ./... make test-db-down -
Docker test: Rebuild and test in container
docker compose build app docker compose up -d app # Test via http://localhost:8081
-
Enable debug logging:
DEBUG_LOG=true ./bin/server
-
Check app logs via API:
curl http://localhost:8081/api/logs/app?limit=50 -
Database inspection:
docker compose exec db psql -U modelhotel -d modelhotel -
Test proxy endpoints: See "Testing Proxy Endpoints" in AGENTS.md for curl examples.
- React DevTools: Install browser extension for component inspection
- Network tab: Monitor API calls in browser dev tools
- Console logs: Check for JavaScript errors
-
SSE events: Monitor
/api/eventsfor real-time updates
| Issue | Solution |
|---|---|
| Biome fails with config errors | Ensure running from web/ directory with relative paths |
| Tests fail with "connection refused" | Start test database: make test-db-up
|
| Frontend changes not appearing | Rebuild Docker: docker compose build app
|
| Admin token lost | Check .data/admin-token file or regenerate by deleting it |
| Provider discovery fails | Check DEBUG_LOG=true logs, verify API key encryption |
- Open an issue to discuss large changes before implementation
-
Create a feature branch from
master - Follow the pre-commit checklist above
- Write tests for new functionality
- Update documentation for user-facing changes
- Submit a pull request
All contributions are licensed under the MIT License.
# Build Docker image
docker build -t model-hotel:latest .
# Run with production environment
docker compose up -d-
Development:
DEBUG_LOG=true,ALLOW_HTTP_PROVIDERS=true(for local Ollama) -
Production:
DEBUG_LOG=false,ALLOW_HTTP_PROVIDERS=false, enable rate limiting
Database backups are stored in ./.data/pgdata/. Use PostgreSQL tools:
# Backup
docker compose exec db pg_dump -U modelhotel modelhotel > backup.sql
# Restore
docker compose exec -T db psql -U modelhotel modelhotel < backup.sqlThe application also supports periodic backup with son/father/grandfather rotation via the Settings UI (Database Backup section). When enabled, backups are created automatically at a configurable interval and old backups are pruned according to daily/weekly/monthly retention tiers. See Configuration for the full list of backup settings.
Last updated: June 2026 (v0.9.49)
Go version: 1.26
Node version: 24 (CI) / 26 (Docker image)
pnpm version: 10.33.0
Last synced from hugalafutro/model-hotel@3b28720 on 2026-07-08 20:04 UTC. Edit these pages under wiki/ (and images under docs/screenshots/) in the main repo, not here.
- π¨ Home
- βοΈ Configuration
- π Development
- π Virtual Keys
- π API Reference
- π Request Logging
- π Model Discovery
- π Failover and Hotel Routing
- π Alerting
- π¨ High Availability