Skip to main content

Local Setup

Getting the backend running against a local Postgres. If you only want to read the API, the API Reference section is generated from a committed spec and needs nothing running.

Prerequisites

  • Node.js 22 or newer (CI currently runs 24.11).
  • PostgreSQL 15+ — local install or a container.
  • npm 9+.

Option A — Docker Compose

compose.yaml brings up the API, a Postgres, and pgAdmin together:

git clone git@github.com:hisprwanda/hlmis-backend.git
cd hlmis-backend
cp .env.example .env # then edit — see below
docker compose up -d

The DB password is read from db/password.txt (a Compose secret) — create that file before the first up.

Option B — Local npm

cp .env.example .env # point DATABASE_HOST at your local Postgres
npm install
npm run build # required before migrations — see the gotcha below
npm run migration:run
npm run start:dev # watch mode

The .env file

Only infrastructure settings live here — everything integration-related (DHIS2 URLs, TRVST, feature flags) is in the database configuration table, not here. See Architecture.

The ones you actually have to set:

VariableNotes
DATABASE_HOST / PORT / USER / PASSWORD / NAMElocalhost for local npm; the db service name for Compose
JWT_SECRETgenerate: node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
APP_SECRETapplication-level signing secret
ADAPTER_MODElocal (default, no DHIS2) or dhis2 — decided once at boot, see DHIS2 vs. Generic Mode
CORS_ORIGINScomma-separated; include your frontend dev URL

ADMIN_EMAIL / ADMIN_PHONE / DEFAULT_ORG_NAME / DEFAULT_ORG_CODE feed the first-run bootstrap (below) and are optional.

First run: what gets created

On a completely empty database, the first boot auto-creates:

  • a root Location (name/code from DEFAULT_ORG_NAME / DEFAULT_ORG_CODE, or Location01 / ROOT), flagged isMainStore: true
  • one admin01 user with the Administrator role and a random temporary password, logged once at boot

If you miss the logged password, another admin can still read tempPassword via GET /api/users until it's changed. See Permissions.

Verify it's up

curl http://localhost:<APP_PORT>/api/health
# → {"status":"ok", ...}
  • The global route prefix is /api — every endpoint is under it, except the Swagger UI, which is served at /.
  • Interactive docs: http://localhost:<APP_PORT>/ (basic-auth-gated unless NODE_ENV=development).

Common gotchas

  • migration:run reads from dist/, not source. db/data-source.ts points migrations and entities at dist/**. Run npm run build first, or migrations silently see an empty list. Same for migration:generate.
  • npm run lint auto-fixes. It has --fix baked in. CI runs eslint directly so it fails on issues instead of quietly rewriting them — if CI lint fails but local passes, that's why.
  • generate:openapi must run against dist/ — see API Contract & CI.