# ${REPO_NAME} Express 5 + TypeScript backend generated from `nholtware/service-template-express`. ## Endpoints - `GET /healthz` liveness, always 200 once listening - `GET /readyz` readiness: Postgres `SELECT 1` and Redis `PING` for whichever of `DATABASE_URL` / `REDIS_URL` are set; 503 + JSON reason otherwise - `GET /` service name, version (git sha), pod, a `service_info` row, Redis hit counter, and `banner` while the `hello-banner` flag is on - `GET /log-demo?msg=...` logs `{ demo: true, text }` with message `log-demo` (logging-pipeline demo; delete when you don't need it) ## Local dev ```sh cp .env.example .env # then export the vars, or use your shell's env loader npm ci && npm run dev npm run lint && npm test ``` Env is validated at boot (`src/lib/config.ts`): `PORT`, `SERVICE_NAME` (required), `LOG_LEVEL`, `DATABASE_URL?`, `REDIS_URL?`, `UNLEASH_URL?`, `UNLEASH_API_TOKEN?` (required with `UNLEASH_URL`), `UNLEASH_REFRESH_MS` (default 5000). Migrations: SQL files in `migrations/`, applied with `npm run migrate`. Logs: one JSON object per line on stdout (`level,time,msg,service,version`, plus `req.*`/`res.statusCode`/`responseTime` per request). ## Seeing your logs Every container's stdout is shipped to Elasticsearch by Fluent Bit; nothing to configure per service. 1. Open Kibana: http://kibana.192.168.1.83.nip.io, user `elastic`, password from `kubectl get secret -n logging logging-es-elastic-user -o jsonpath='{.data.elastic}' | base64 -d`. 2. **Discover**, data view `kube-*` (the default). 3. Query with KQL, e.g. `service:"${REPO_NAME}" and demo:true`, or `service:"${REPO_NAME}" and level:"error"`. Any field you pass to the logger becomes a queryable field: `req.log.info({ orderId }, "order placed")` → KQL `orderId:123`. The message string always overrides a `msg` key in the object, so don't name a field `msg`. Lines are searchable within a few seconds; retention is 14 days. ## Feature flags Flags come from Unleash (UI: http://unleash.192.168.1.83.nip.io, environment `development`). `src/lib/flags.ts` wraps the official `unleash-client` SDK; routes get it as `deps.flags`: ```ts if (deps.flags?.isEnabled("my-flag", { userId })) { ... } const v = deps.flags?.getVariant("my-experiment"); // v.name, v.enabled, v.payload ``` - In the cluster the deployment sets `UNLEASH_URL=http://unleash.unleash.svc:4242/api` and `UNLEASH_API_TOKEN` from Secret `unleash-client-token` (key `token`) in the service's namespace. - The SDK polls every 5 s; flipping a flag in the UI changes behaviour within seconds, no redeploy. - With `UNLEASH_URL` unset (local dev, tests) every flag reads `false` and one log line says so. If Unleash is down, flags keep their last fetched value (or `false`) and the service keeps serving. - Create the flag in the Unleash UI (project `default`) before using it; unknown flags read `false`. - Tests: pass a fake `Flags` to `createApp` (see `src/app.test.ts`); never hit a real Unleash. - `hello-banner` on `GET /` is the platform demo flag; delete it when you don't need it. ## Using this template 1. **Generate** a repo from this template in Forgejo (public). The repo name is substituted into `package.json`, this README, `.env.example`, and the first migration (CI derives the app name from the repo name itself). 2. **Set 3 Actions secrets** on the new repo: `REGISTRY_USER` (`nholtware`), `REGISTRY_TOKEN` (registry push token), `GITOPS_TOKEN` (ci-bot gitops token). 3. **Add gitops entries**: render `templates/backend/` (see its README) into `manifests//` and add `apps/.yaml` in `nholtware/gitops`. 4. **Push** to `main`. Generating a repo emits no push event, so CI only starts on your first push.