Local Dev Setup
:::info Source
Sourced from services/search-service/LOCAL_DEV_SETUP.md in the documentation repo.
:::
Targets macOS, Linux, and Windows (via WSL2). Goal: clone → hot-reload in ≤ 5 minutes.
1. Prerequisites
| Tool | Version | Install |
|---|---|---|
| Node.js | 22 LTS | via fnm or nvm |
| pnpm | 9+ | npm i -g pnpm |
| Docker | 25+ | Docker Desktop / Podman Desktop |
| Docker Compose | v2 | bundled |
| mkcert | latest | brew install mkcert |
| Task | 3+ | brew install go-task |
| direnv | latest | optional but recommended |
| git-lfs | latest | for model artifacts |
macOS recommended: 8 CPU, 16 GB RAM. Windows: use WSL2 Ubuntu 22.04 backend.
2. First Run
git clone git@github.com:ghasi-edtech/platform.git
cd platform/services/search-service
pnpm install
cp .env.example .env.local
task dev:stack:up # boots local deps via docker-compose
task dev:seed # seed tenants + corpora
pnpm dev # hot-reload Fastify app
Open:
- API: http://localhost:8080/api/v1/search?q=demo
- Swagger UI: http://localhost:8080/docs
- OpenSearch Dashboards: http://localhost:5601
- pgAdmin: http://localhost:5050 (admin@local / admin)
- Jaeger: http://localhost:16686
3. Compose Stack (dev)
docker-compose.dev.yaml in repo root provides:
| Service | Port | Notes |
|---|---|---|
| postgres | 5432 | single instance, initial DB search |
| redis | 6379 | single instance |
| opensearch | 9200, 9300 | single node, no security |
| opensearch-dashboards | 5601 | UI |
| nats | 4222, 8222 | JetStream enabled |
| ai-gateway-mock | 9090 | deterministic hashed embeddings |
| otel-collector | 4317, 4318 | OTLP → Jaeger + Prometheus |
| jaeger | 16686 | traces UI |
| prometheus | 9091 | metrics |
Mock ai-gateway returns a deterministic 1024-dim vector from sha256(text) repeated/sliced. No network calls.
4. Environment Variables
| Name | Default | Purpose |
|---|---|---|
ROLE | api | api | indexer | jobs |
PORT | 8080 | HTTP port |
REGION | us-local | Region tag |
OPENSEARCH_URL | http://localhost:9200 | |
POSTGRES_URL | postgres://dev:dev@localhost:5432/search | |
REDIS_URL | redis://localhost:6379/0 | |
NATS_URL | nats://localhost:4222 | |
AI_GATEWAY_URL | http://localhost:9090 | |
JWT_JWKS_URL | http://localhost:3300/.well-known/jwks.json | |
OTEL_EXPORTER_OTLP_ENDPOINT | http://localhost:4318 | |
LOG_LEVEL | debug | |
FEATURE_QUERY_EXPANSION | false | |
FEATURE_RECOMMENDATIONS | true |
5. Task Runner Targets
task --list
| Task | What it does |
|---|---|
dev:stack:up | Boot compose stack |
dev:stack:down | Tear down stack + volumes |
dev:seed | Seed tenants + ~1k docs across all types |
dev:publish-event | Publish a fixture domain event to NATS |
dev:reindex | Trigger reindex for local-tenant-1 |
dev:logs | Tail all containers |
dev:nats:browse | NATS CLI into local JetStream |
dev:openapi:serve | Serve Swagger UI |
test:unit | pnpm test:unit |
test:integration | Boots testcontainers + runs integration |
test:e2e | Runs Playwright E2E (needs stack up) |
lint | ESLint + Prettier check |
typecheck | tsc --noEmit |
6. Seed Data
scripts/seed/index.ts loads:
- 3 tenants:
local-tenant-1/2/3. - 100 courses, 300 lessons, 1k blocks, 20 marketplace listings, 50 users, 80 assignments, 30 certificates per tenant.
- Embeddings computed via mock gateway (deterministic).
Run:
task dev:seed
# or partial
pnpm tsx scripts/seed/index.ts --tenants=1 --docs=50
7. Hot Reload
pnpm devusestsx --watchwith--ignore node_modules.- Changes to
.env.localrequire restart. - Changes to NATS subjects require
task dev:stack:restart natsif JetStream config changes.
8. Running a Single Use Case
pnpm tsx scripts/repl.ts
> const { searchUseCase } = await import('./src/application/use-cases/search-query');
> await searchUseCase.execute({ tenantId: 'local-tenant-1', q: 'algebra' });
REPL pre-loads a dev-grade service-account JWT.
9. Debugging
- VSCode launch config
Debug: search-apipre-configured. - Node
--inspect-brkexposed on port 9229. - Enable OTLP debug:
OTEL_LOG_LEVEL=debug. - For NATS wire debug:
NATS_DEBUG=1 task dev:publish-event.
10. Running Tests
pnpm test:unit # vitest unit
pnpm test:integration # testcontainers + vitest
pnpm test:contract # pact verifier
pnpm test:e2e # playwright
pnpm coverage # c8 coverage
Coverage gate enforced locally — fails at < 80% lines.
11. Lint, Format, Typecheck
pnpm lint # eslint
pnpm format # prettier --write
pnpm typecheck # tsc --noEmit
pnpm validate # all of the above
Pre-commit hook (lefthook) runs validate on staged files.
12. Troubleshooting
| Symptom | Fix |
|---|---|
ECONNREFUSED localhost:9200 | docker compose ps — opensearch not ready, wait 30s |
| OpenSearch OOM killed | give Docker 8GB RAM; alpine image uses 1GB heap |
| Test flaky on MacOS ARM | ensure platform: linux/arm64 in compose |
| Embeddings nondeterministic | you hit real ai-gateway; set AI_GATEWAY_URL to mock |
| Stale seed | task dev:stack:down && task dev:stack:up && task dev:seed |
| Kafka/NATS consumer lag | task dev:nats:browse and check consumer status |
13. Common Workflows
Adding a new projector
- Write schema contract under
src/interfaces/consumers/__contracts__/<subject>.json. - Write unit test for mapping function (RED).
- Implement mapping.
- Register consumer in
src/interfaces/consumers/index.ts. - Integration test publishing the event and asserting doc appears.
Adding a new filter
- Extend filter AST in
src/domain/ranking.ts. - Extend parser tests.
- Extend OS DSL translator.
- Add happy + failure API contract tests.
14. Code Style
See root ESLINT_CONFIG + Prettier. In short:
- 2-space indent.
- Single quotes, trailing commas.
- No
any. No implicitundefined. - Immutability by default;
Readonly<>everywhere domain. - One responsibility per file; max 400 lines.
15. Documentation
Each sub-module has a short README.md describing its role. Domain model docs live here; keep code references accurate.