LOCAL_DEV_SETUP — bff-backoffice-service
Sibling: DEPLOYMENT_TOPOLOGY · TESTING_STRATEGY · DATA_MODEL · SYNC_CONTRACT
1. Prerequisites
| Tool | Version | Notes |
|---|---|---|
| Node.js | 20.x LTS | engines.node = "20.x" |
| pnpm | 9.x | Workspace package manager |
| Docker Desktop | latest | For Postgres + Redis + Pub/Sub emulator |
| Docker Compose | v2 | Bundled |
| Google Cloud SDK | latest | For impersonating SAs against staging |
mkcert | latest | Local TLS for DPoP / cookie / SSE |
gh CLI | latest | PR helpers |
| (optional) Electron desktop checkout | latest | To exercise full Electron ↔ BFF integration locally |
2. One-time setup
gh repo clone ghasitech/ghasi-melmastoon
cd ghasi-melmastoon
pnpm install --frozen-lockfile
mkcert -install
mkcert -cert-file ./certs/local.pem -key-file ./certs/local-key.pem \
'backoffice.local.melmastoon.test' localhost
cp services/bff-backoffice-service/.env.example services/bff-backoffice-service/.env.local
Edit .env.local:
NODE_ENV=development
PORT=8083
LOG_LEVEL=debug
DATABASE_URL=postgres://bff_backoffice:bff_backoffice@localhost:5443/bff_backoffice
REDIS_CACHE_URL=redis://localhost:6393/0
REDIS_SESSION_URL=redis://localhost:6394/0
PUBSUB_PROJECT_ID=local
PUBSUB_EMULATOR_HOST=localhost:8085
BFF_BACKOFFICE_PEPPER=dev-pepper-do-not-ship
BFF_BACKOFFICE_SSE_SIGNING_KEY=dev-sse-key-do-not-ship
UPSTREAM_IAM_BASE_URL=http://localhost:8089
UPSTREAM_TENANT_BASE_URL=http://localhost:8094
UPSTREAM_PROPERTY_BASE_URL=http://localhost:8092
UPSTREAM_RESERVATION_BASE_URL=http://localhost:8096
UPSTREAM_INVENTORY_BASE_URL=http://localhost:8095
UPSTREAM_PRICING_BASE_URL=http://localhost:8091
UPSTREAM_HOUSEKEEPING_BASE_URL=http://localhost:8101
UPSTREAM_MAINTENANCE_BASE_URL=http://localhost:8102
UPSTREAM_BILLING_BASE_URL=http://localhost:8098
UPSTREAM_LOCK_BASE_URL=http://localhost:8099
UPSTREAM_AI_BASE_URL=http://localhost:8100
UPSTREAM_NOTIFICATION_BASE_URL=http://localhost:8103
UPSTREAM_SYNC_BASE_URL=http://localhost:8104
UPSTREAM_ANALYTICS_BASE_URL=http://localhost:8105
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
3. Stand up dependencies
docker compose -f services/bff-backoffice-service/docker-compose.dev.yml up -d
# Brings up:
# postgres (5443 → 5432) seeded with sample tenants, devices, sessions
# redis-cache (6393 → 6379)
# redis-session (6394 → 6379)
# pubsub-emulator (8085)
# wiremock-iam (8089) — issues dev DPoP-compatible tokens
# wiremock-tenant (8094)
# wiremock-property (8092)
# wiremock-reservation (8096)
# wiremock-inventory (8095)
# wiremock-pricing (8091)
# wiremock-housekeeping (8101)
# wiremock-maintenance (8102)
# wiremock-billing (8098)
# wiremock-lock (8099) — simulates ttlock/salto/assa
# wiremock-ai (8100) — fake AI suggestions
# wiremock-notification (8103)
# wiremock-sync (8104) — simulates sync handshake
# wiremock-analytics (8105)
# otel-collector + jaeger (16686)
4. Run the service
pnpm --filter @ghasi/service-bff-backoffice dev
# starts in watch mode on https://backoffice.local.melmastoon.test:8083
Smoke check:
curl -k https://backoffice.local.melmastoon.test:8083/api/health/ready
# Mint a dev session via the wiremock-iam stub:
pnpm --filter @ghasi/service-bff-backoffice dev:dpop-session
# prints: ACCESS_TOKEN=... REFRESH_TOKEN=... DEVICE_ID=dev_dev_local
# Try a dashboard fetch:
curl -k \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "DPoP: $(pnpm --filter @ghasi/service-bff-backoffice dev:dpop-proof GET https://backoffice.local.melmastoon.test:8083/api/bff/backoffice/v1/dashboard)" \
-H "X-Device-Id: ${DEVICE_ID}" \
-H "X-App-Version: 1.4.2-dev" \
-H "X-App-Platform: linux" \
-H "X-Property-Id: prop_dev_01" \
https://backoffice.local.melmastoon.test:8083/api/bff/backoffice/v1/dashboard?propertyId=prop_dev_01
Trace appears in Jaeger UI at http://localhost:16686.
5. Common commands
| Command | Purpose |
|---|---|
pnpm --filter @ghasi/service-bff-backoffice dev | Run with hot reload |
pnpm --filter @ghasi/service-bff-backoffice test:unit | Vitest unit tests |
pnpm --filter @ghasi/service-bff-backoffice test:integration | Vitest + Testcontainers |
pnpm --filter @ghasi/service-bff-backoffice test:contract | Pact consumer + provider |
pnpm --filter @ghasi/service-bff-backoffice test:e2e:local | Playwright against local stack |
pnpm --filter @ghasi/service-bff-backoffice lint | ESLint + import-boundary rules |
pnpm --filter @ghasi/service-bff-backoffice typecheck | tsc --noEmit |
pnpm --filter @ghasi/service-bff-backoffice migrate | Drizzle migrations |
pnpm --filter @ghasi/service-bff-backoffice migrate:make <name> | New migration |
pnpm --filter @ghasi/service-bff-backoffice seed | Seed dev tenants + devices + sessions |
pnpm --filter @ghasi/service-bff-backoffice openapi | Regenerate OpenAPI |
pnpm --filter @ghasi/service-bff-backoffice dev:dpop-session | Mint dev DPoP session |
pnpm --filter @ghasi/service-bff-backoffice dev:dpop-proof <method> <url> | Sign single DPoP proof |
pnpm --filter @ghasi/service-bff-backoffice dev:mfa-attest <scope> | Mint dev MFA attestation token |
6. Working against real upstreams (staging)
gcloud auth application-default login
gcloud auth print-identity-token --impersonate-service-account=bff-backoffice-sa@melmastoon-stage.iam.gserviceaccount.com
# Override in .env.local:
UPSTREAM_RESERVATION_BASE_URL=https://reservation.staging.melmastoon.internal
# ... etc
USE_GOOGLE_ID_TOKEN=true
7. Working with the real Electron desktop
If you have @ghasi/app-desktop-backoffice checked out:
# In the desktop checkout:
pnpm install
pnpm dev:point-at-local-bff # rewrites the API base URL to https://backoffice.local.melmastoon.test:8083
pnpm electron:dev
The desktop main process will:
- Walk through device enrollment against
wiremock-iam(one-shot). - Persist refresh token + device key into the OS keychain (
keytar). - Sign DPoP proofs for every request.
- Open SSE to the local BFF.
You can now exercise full UI ↔ BFF flows.
8. Seed data
pnpm seed creates:
- 3 tenants (
tnt_kabul,tnt_herat,tnt_mazar) - 5 properties (3 in
tnt_kabul, 1 each in others) - 8 operators with varied roles
- 10 devices distributed across operators
- 25 reservations (today + tomorrow)
- 15 housekeeping tasks
- 6 maintenance work orders
- 8 active AI suggestions
- 5 raised alerts
- Pre-baked DPoP key pairs for dev sessions
- Pre-baked MFA TOTP secret (
pnpm dev:mfa-attest <scope>uses it)
9. Useful local URLs
| URL | Purpose |
|---|---|
https://backoffice.local.melmastoon.test:8083/api/health/ready | Readiness |
http://localhost:16686 | Jaeger trace explorer |
http://localhost:8089/__admin | wiremock-iam admin |
http://localhost:8099/__admin | wiremock-lock admin (toggle vendor outcomes) |
http://localhost:8104/__admin | wiremock-sync admin |
10. Resetting state
docker compose -f services/bff-backoffice-service/docker-compose.dev.yml down -v
docker compose -f services/bff-backoffice-service/docker-compose.dev.yml up -d
pnpm --filter @ghasi/service-bff-backoffice migrate
pnpm --filter @ghasi/service-bff-backoffice seed
11. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
MELMASTOON.IAM.DPOP_INVALID | Local DPoP cache stale | Restart compose or pnpm dev:dpop-session to mint fresh |
MELMASTOON.IAM.SESSION_EXPIRED | Access token TTL 15 min lapsed | Re-mint via pnpm dev:dpop-session |
MELMASTOON.BFF.BACKOFFICE.MFA_REQUIRED on lock revoke | No MFA token in body | pnpm dev:mfa-attest lock_revoke and pass mfaAttestationToken |
MELMASTOON.SYNC.VERSION_BLOCKED | Local desktop appVersion below floor | Edit bff-backoffice-flags Memorystore key to lower floor |
MELMASTOON.BFF.UPSTREAM_UNAVAILABLE from lock | wiremock-lock configured to fail | Reset via __admin/mappings |
| Trace missing | OTel collector not started | Restart compose; check Jaeger health |
| Pact verification fails locally | Stale broker pact | pnpm test:contract -- --pull-pacts |
Cookie not set on curl | Secure flag rejected on plain HTTP | Use https + mkcert cert |
keytar errors on Linux desktop dev | libsecret missing | sudo apt install libsecret-1-dev gnome-keyring |