A single unified Express service that simulates a configurable number of virtual microservices with real dependency health monitoring, powered by proactive-deps.
Created for testing aspects of Depsera.
npm install
npm startThe server starts on port 5000 (default). Visit http://localhost:5000/ for a full service index.
Instead of running separate processes, the server generates a dependency tree of virtual services organized into 4 architectural layers:
| Layer | Category | Pool Size | Examples |
|---|---|---|---|
| 0 | data | 18 | user-db, order-db, session-cache, event-store, search-index, message-queue |
| 1 | domain | 18 | auth-svc, payment-svc, shipping-svc, fraud-detection-svc, loyalty-svc |
| 2 | api | 18 | catalog-api, checkout-api, analytics-api, webhook-api, marketplace-api |
| 3 | gateway | 12 | public-gateway, mobile-gateway, graphql-gateway, streaming-gateway |
Each layer has a pool of distinct, realistic service names with unique descriptions. Services are drawn from the pool in order; numeric suffixes are only appended if SERVICE_COUNT exceeds the total pool capacity (~66 services). Each virtual service gets its own Express router and DependencyMonitor instance. Services in higher layers depend on services in lower layers, with health checks flowing through HTTP just like real microservices.
Services are grouped into teams to model a realistic multi-team organization. The global service tree is generated first, then services are distributed across teams using deterministic round-robin. Cross-team dependencies are naturally preserved since the tree is built before partitioning.
Team names come from a fixed pool: platform, commerce, data-eng, infra, mobile, partner, growth, internal. If TEAM_COUNT exceeds the pool size, overflow names get numeric suffixes (e.g. platform-2).
| Env Var | Default | Purpose |
|---|---|---|
PORT |
5000 |
Server port |
SERVICE_COUNT |
8 |
Number of virtual services to generate |
TEAM_COUNT |
1 |
Number of teams to distribute services across |
BASE_URL |
http://localhost:<PORT> |
Base URL used in manifests and health check URLs |
CHECK_INTERVAL_MS |
15000 |
Monitor check interval |
CACHE_DURATION_MS |
60000 |
Monitor cache duration |
REFRESH_THRESHOLD_MS |
5000 |
Monitor refresh threshold |
OTLP_PUSH_URL |
(disabled) | Depsera OTLP endpoint URL (e.g. https://app.depsera.com/v1/metrics). Pusher only activates when both URL and API key are set. |
OTLP_API_KEY |
(disabled) | Bearer token for OTLP endpoint (e.g. dps_abc123). |
OTLP_PUSH_INTERVAL_MS |
30000 |
How often to push metrics to the OTLP endpoint (ms). |
Example with custom config:
SERVICE_COUNT=12 TEAM_COUNT=3 PORT=3000 npm start| Route | Description |
|---|---|
GET / |
Root index with all teams, services, and routes |
| Route | Description |
|---|---|
GET /<team>/manifest |
Depsera-compatible manifest JSON for this team (reflects any active drift mutations) |
GET /<team>/manifest/baseline |
Original unmodified manifest |
GET /<team>/manifest/overlay |
Debug: current drift overlay state |
POST /<team>/manifest/reset |
Reset manifest back to baseline |
| Route | Description |
|---|---|
GET /<team>/<key>/check |
Health check — returns 200 or 500 if failure inserted |
GET /<team>/<key>/health |
Dependency health statuses from this service's monitor |
GET /<team>/<key>/insert-failure |
Toggle simulated failure on this service's /check. Add ?cascade=true to propagate to all downstream dependants. |
GET /<team>/<key>/metrics |
Prometheus-format metrics |
Single service:
GET /platform/user-db/insert-failure— togglesuser-dbto return 500 on/checkGET /platform/auth-svc/health— showsuser-dbdependency as CRITICAL (ifauth-svcdepends onuser-db)GET /platform/user-db/insert-failure— toggle back to healthy
Cascade failure (?cascade=true):
GET /platform/user-db/insert-failure?cascade=true— failsuser-dbAND all services that depend on it (directly or transitively)- Every downstream dependant's
/checknow returns 500 GET /platform/user-db/insert-failure?cascade=true— toggle back, clearing failure on all affected services
The response includes the full list of affected services:
{
"service": "user-db",
"failureInserted": true,
"cascade": true,
"affected": ["user-db", "auth-svc", "user-api"]
}Failures also propagate naturally through the dependency graph via the health monitors, including across team boundaries.
Optionally push dependency health metrics to a Depsera-compatible OTLP endpoint. When both OTLP_PUSH_URL and OTLP_API_KEY are set, the server periodically collects dependency status data from all virtual services and POSTs it as OTLP JSON to the configured /v1/metrics endpoint.
OTLP_PUSH_URL=https://app.depsera.com/v1/metrics \
OTLP_API_KEY=dps_abc123 \
OTLP_PUSH_INTERVAL_MS=15000 \
npm startEach virtual service becomes a separate OTLP resource (identified by service.name, service.namespace, service.instance.id). Its dependency health statuses are pushed as gauge metrics:
| Metric | Source | Type |
|---|---|---|
dependency.health.status |
Status code (0=OK, 1=CRITICAL, 2=WARNING) | int |
dependency.health.healthy |
Healthy flag (1=true, 0=false) | int |
dependency.health.latency |
Check latency in ms | double |
dependency.health.code |
Status code | int |
dependency.health.check_skipped |
Skipped flag | int |
Each data point carries label attributes: dependency.name, dependency.type, dependency.impact, dependency.description, dependency.error_message.
These metric and attribute names match Depsera's default OTLP mappings, so no custom schema_config is needed on the Depsera side.
Each team's manifest supports dynamic mutation to test Depsera's sync drift policies (on_field_drift, on_removal, on_alias_removal, on_override_removal, on_association_removal). Mutations only affect the manifest — the underlying virtual services keep running.
All endpoints are relative to /<team>/manifest:
| Method | Path | Drift Scenario |
|---|---|---|
POST |
/services/:key/override |
Field drift — body: { "field": "name", "value": "New Name" } |
DELETE |
/services/:key |
Service removal |
POST |
/services |
Service addition — body: full service object |
DELETE |
/associations/:serviceKey/:linkedTeam/:linkedKey |
Association removal |
POST |
/associations |
Association addition — body: full association object |
DELETE |
/aliases/:alias |
Alias removal |
POST |
/aliases |
Alias addition — body: { "alias": "...", "canonical_name": "..." } |
DELETE |
/canonical-overrides/:canonicalName |
Override removal |
POST |
/canonical-overrides |
Override addition — body: full override object |
# 1. Override a service name
curl -X POST http://localhost:5000/platform/manifest/services/auth-svc/override \
-H 'Content-Type: application/json' \
-d '{"field":"name","value":"Renamed Auth"}'
# 2. Verify the manifest shows the drifted value
curl http://localhost:5000/platform/manifest
# 3. Reset back to baseline
curl -X POST http://localhost:5000/platform/manifest/reset
# 4. Verify manifest is back to original
curl http://localhost:5000/platform/manifest# Remove a service from the manifest (service itself keeps running)
curl -X DELETE http://localhost:5000/platform/manifest/services/auth-svc
# Verify it's gone from the manifest
curl http://localhost:5000/platform/manifest
# The service is still reachable
curl http://localhost:5000/platform/auth-svc/checkEach team's /<team>/manifest endpoint returns a JSON manifest conforming to the Depsera manifest schema. The manifest includes:
services— all virtual services belonging to this team withkey,name,health_endpoint,description,metrics_endpoint, andpoll_interval_msassociations— dependency relationships usinglinked_service_keyin namespacedteam_key/service_keyformat to unambiguously reference the target service across teamsaliases— short names for services (e.g.auth→auth-svc), auto-generated from category suffixes (-svc,-api,-db, etc.)canonical_overrides— contact and impact overrides for key services, auto-generated for the highest-layer service per team (and a second domain-layer service if the team has 4+ services)
For example, if auth-svc on the commerce team depends on user-db on the platform team:
{
"service_key": "auth-svc",
"dependency_name": "user-db",
"linked_service_key": "platform/user-db",
"association_type": "database"
}Association types are inferred from the dependency's service key:
| Dependency pattern | association_type |
|---|---|
*-db, *-store, *-index |
database |
*-cache |
cache |
*-queue |
message_queue |
| Everything else | api_call |
A React dashboard lives in web/ for visualizing the service graph, monitoring health, toggling failures, and testing manifest drift through a UI.
# Terminal 1: start the backend
npm start
# Terminal 2: start the frontend dev server
cd web
npm install
npm run devVisit http://localhost:5173/ to see the dashboard. The Vite dev server proxies API requests to the backend on port 5000.
| Route | Page | Description |
|---|---|---|
/ |
Overview | Stats bar, team cards grid |
/teams/:team |
Team Detail | Service table, SVG dependency graph, failure toggles |
/teams/:team/services/:key |
Service Detail | Dependency health statuses (15s auto-refresh), failure simulation controls |
/teams/:team/manifest |
Manifest & Drift | Tabbed manifest viewer, baseline diff, drift mutation forms, overlay inspector |
- Vite + React 19 + TypeScript
- Tailwind CSS v4
- React Router v7
- TanStack React Query v5
- Vitest + React Testing Library
# Backend
npm run typecheck # TypeScript compilation check
npm test # Run Jest tests
# Frontend
cd web
npm run build # Production build
npm test # Run Vitest tests
npm run dev # Dev server with HMRmulti-api/
├── src/
│ ├── index.ts # Express app bootstrap
│ ├── config.ts # Env-based configuration
│ ├── tree-generator.ts # Algorithmic service tree generation
│ ├── team-assigner.ts # Assigns services to teams via round-robin
│ ├── virtual-service.ts # Factory: Router + DependencyMonitor per service
│ ├── manifest.ts # Builds per-team manifest JSON (with aliases & overrides)
│ ├── manifest-state.ts # Mutable overlay on baseline manifest for drift testing
│ ├── manifest-router.ts # Express router for manifest drift mutation endpoints
│ └── otlp-pusher.ts # Optional OTLP metrics push to Depsera
├── tests/
│ ├── tree-generator.test.ts
│ ├── team-assigner.test.ts
│ ├── virtual-service.test.ts
│ ├── manifest.test.ts
│ ├── manifest-state.test.ts
│ ├── manifest-router.test.ts
│ └── otlp-pusher.test.ts
├── web/ # Frontend dashboard
│ ├── src/
│ │ ├── api/client.ts # Typed fetch wrapper for all backend endpoints
│ │ ├── types/ # TypeScript types mirroring backend responses
│ │ ├── hooks/ # React Query hooks (root data, health, manifest)
│ │ ├── lib/ # Utilities (cn, manifest diff)
│ │ ├── components/
│ │ │ ├── layout/ # Sidebar, PageShell, StatusBadge
│ │ │ ├── overview/ # StatsBar, TeamCard, OverviewPage
│ │ │ ├── team/ # ServiceRow, DependencyGraph, TeamDetailPage
│ │ │ ├── service/ # HealthStatusList, FailureControls, ServiceDetailPage
│ │ │ └── manifest/ # ManifestViewer, ManifestDiff, DriftControls, forms
│ │ └── tests/ # Component tests with Vitest
│ ├── package.json
│ └── vite.config.ts
├── package.json
├── tsconfig.json
└── jest.config.ts