GraphQL + REST API over Elasticsearch for the Kids First and INCLUDE portals.
Two HTTP surfaces under one process:
- REST routes at the app root (
/status,/statistics,/sets,/phenotypes,/upset,/venn,/transcriptomics/*, etc.) — most are Keycloak-gated. - GraphQL endpoint at
/<projectId>/graphql— backed by a vendored SQON → ES query/aggregation pipeline (src/sqon/).
Reads Elasticsearch across 7 entity _centric indices (participants, files, biospecimens, variants, genes, studies, specimen tree). Sets persistence is delegated to a separate users-api service over HTTP.
Boot is fail-fast: a missing or unreachable ES_HOST exits at startup. Per-route env vars (e.g. a missing suggestion index) only block their own route.
- Node 24+ — matches
engines.nodeand the prod image - Docker + Docker Compose — for the containerized dev workflow
- Reachable Elasticsearch cluster, Keycloak realm, and users-api instance
cp .env.example .env
# fill in ES_HOST, KEYCLOAK_*, USER_API_URL, etc.docker compose up app # dev mode, hot reload
docker compose run --rm test # test suiteFor an interactive shell with the project mounted (poke around, run any script manually):
docker run --rm -it --network host -v "$PWD:/app" -w /app node:24-alpine3.22 shInside the shell: npm install && npm run dev (or any other script). --network host lets the container reach host-bound services like SSH tunnels at localhost:<port>.
npm install
npm run dev # tsx watch — hot reload
npm run test # vitest run
npm run lint # biome check
npm run lint:fix # biome check --writeSee .env.example for the keys to set and src/env.ts for defaults and which are boot-fatal vs route-fatal.
The critical knobs:
ES_HOST— boot-fatal if unreachableKEYCLOAK_URL/KEYCLOAK_REALM/KEYCLOAK_CLIENT— must match the FE's KC client so token audience validatesUSER_API_URL— sets persistencePROJECT_ID— GraphQL mount prefix
This one codebase is deployed for two portals — INCLUDE and Kids-First (KF) — which differ in ES_HOST, KEYCLOAK_URL/KEYCLOAK_REALM, USER_API_URL, DATALAKE_S3_URL, PROJECT_ID, and the suggestion index names (INCLUDE uses singular gene_suggestions/variant_suggestions; KF uses plural genes_suggestions/variants_suggestions).
For local dev, keep a complete env file per portal and select it with ENV_FILE instead of hand-editing .env:
cp .env.example .env.include # fill in INCLUDE values
cp .env.example .env.kf # fill in KF values
npm run dev:include # ENV_FILE=.env.include npm run dev
npm run dev:kf # ENV_FILE=.env.kf npm run dev
npm run dev # plain .env (fallback, unchanged).env, .env.include, and .env.kf are all gitignored. src/env.ts loads process.env.ENV_FILE || '.env'.
src/app.ts— REST routes, auth gates, mount pointssrc/index.ts— boot orchestrationsrc/graphql/— GraphQL schema + Apollo serversrc/sqon/— in-tree SQON → ES query/aggregation buildersrc/endpoints/— REST route handlersops/— release-time ES alias rotation helpers (see each file's header)
GET /status returns 200 with a minimal JSON sanity payload (Keycloak URL, ES host, users-api URL). No auth required. Use it as your orchestrator's liveness probe.
Apache 2.0