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:
| Variable | Notes |
|---|---|
DATABASE_HOST / PORT / USER / PASSWORD / NAME | localhost for local npm; the db service name for Compose |
JWT_SECRET | generate: node -e "console.log(require('crypto').randomBytes(64).toString('hex'))" |
APP_SECRET | application-level signing secret |
ADAPTER_MODE | local (default, no DHIS2) or dhis2 — decided once at boot, see DHIS2 vs. Generic Mode |
CORS_ORIGINS | comma-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 fromDEFAULT_ORG_NAME/DEFAULT_ORG_CODE, orLocation01/ROOT), flaggedisMainStore: true - one
admin01user with theAdministratorrole 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 unlessNODE_ENV=development).
Common gotchas
migration:runreads fromdist/, not source.db/data-source.tspointsmigrationsandentitiesatdist/**. Runnpm run buildfirst, or migrations silently see an empty list. Same formigration:generate.npm run lintauto-fixes. It has--fixbaked in. CI runseslintdirectly so it fails on issues instead of quietly rewriting them — if CI lint fails but local passes, that's why.generate:openapimust run againstdist/— see API Contract & CI.