LumenHealth is a monorepo. Development is organized around clinical milestones — each milestone adds a discrete set of functionality on top of a stable foundation.
The current foundation scope is authentication only. All other modules (clinic management, patient records, encounters, billing) are described in the README as future milestones and must not be partially implemented in advance.
apps/
api/ Express backend — modular monolith
web/ Next.js web application
mobile/ React Native workspace (scaffold)
stellar-service/ Stellar integration (scaffold)
packages/
types/ Shared TypeScript contracts (@lumen/types)
config/ Shared runtime configuration (@lumen/config)
docs/ Architecture and domain documentation
Requirements: Node.js 20+, npm 10+
git clone <repo>
cd lumenhealth
npm installCopy the environment template:
cp .env.example .envRun the API and web app in parallel:
npm run dev# All workspaces
npm test
# API only
npm run test -w apps/api
# Web only
npm run test -w apps/webTwo automated checks enforce boundaries. Run them before opening a PR:
npm run check:architecture
npm run check:boundariescheck:architecture— enforces the modular monolith layout inapps/apicheck:boundaries— enforces that workspaces do not import across forbidden boundaries
- TypeScript strict mode throughout
- No
anywithout a comment explaining why - Business logic lives inside a module (
src/modules/<name>/) - Shared infrastructure lives in
src/shared/— no business logic there - Tests live inside the module:
src/modules/<name>/__tests__/
When a new milestone is scoped:
- Create
apps/api/src/modules/<name>/with:router.ts,service.ts,repository.ts,validators.ts,types.ts,__tests__/ - Register the router in
apps/api/src/app.ts - Add shared types to
packages/types/src/ - Write tests before opening a PR — CI will reject untested modules
Do not create partial implementations or placeholder endpoints for future modules. Incomplete code in main confuses contributors and breaks CI.
All data in LumenHealth is scoped to a clinic. When implementing a new module follow these rules without exception:
-
Extract
clinicIdfromreq.auth.clinicId— never fromreq.bodyorreq.paramsalone. The JWT is the source of truth for which clinic a request belongs to. -
Pass
clinicIdas the first argument to every repository list method. No list query should ever return records from multiple clinics. -
On
findById, verifyrecord.clinicId === req.auth.clinicIdbefore returning. If the record belongs to a different clinic, returnundefined— let the controller respond with 404. -
Return 404 (not 403) for cross-clinic resource access. Returning 403 would confirm that the resource exists in another clinic, which is a data enumeration risk. A foreign resource must look identical to a missing one.
-
Apply
requireClinicScopeto any route with:clinicIdin the URL path. Import it fromsrc/shared/middleware/clinic-scope.tsand place it afterresolveAuthContext:router.get("/:clinicId", resolveAuthContext, requireClinicScope("clinicId"), handler);
-
Use
buildTwoClinicFixture()in your integration tests to verify your module enforces isolation. Import it fromsrc/modules/auth/tests/fixtures.ts.
- One PR per milestone scope
- All tests must pass
- All lint checks must pass
- PR description should reference the milestone being implemented