Express 5 + TS backend service template
Find a file Use this template
2026-11-05 10:17:34 -06:00
.forgejo fix: derive app-name at runtime; template expansion breaks ${{ }} expressions 2026-11-05 01:57:51 -06:00
migrations feat: initial backend service template 2026-11-05 01:43:18 -06:00
src feat: add GET /log-demo route showing structured logging to Kibana 2026-11-05 10:17:10 -06:00
.dockerignore feat: initial backend service template 2026-11-05 01:43:18 -06:00
.env.example feat: initial backend service template 2026-11-05 01:43:18 -06:00
.gitignore feat: initial backend service template 2026-11-05 01:43:18 -06:00
.nvmrc feat: initial backend service template 2026-11-05 01:43:18 -06:00
.prettierignore feat: initial backend service template 2026-11-05 01:43:18 -06:00
.prettierrc.json feat: initial backend service template 2026-11-05 01:43:18 -06:00
CLAUDE.md feat: initial backend service template 2026-11-05 01:43:18 -06:00
commitlint.config.js feat: initial backend service template 2026-11-05 01:43:18 -06:00
Dockerfile feat: initial backend service template 2026-11-05 01:43:18 -06:00
eslint.config.js feat: initial backend service template 2026-11-05 01:43:18 -06:00
package-lock.json feat: initial backend service template 2026-11-05 01:43:18 -06:00
package.json feat: initial backend service template 2026-11-05 01:43:18 -06:00
README.md docs: how to see your logs in Kibana 2026-11-05 10:17:34 -06:00
tsconfig.json feat: initial backend service template 2026-11-05 01:43:18 -06:00
vitest.config.ts feat: initial backend service template 2026-11-05 01:43:18 -06:00

${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
  • GET /log-demo?msg=... logs { demo: true, text } with message log-demo (logging-pipeline demo; delete when you don't need it)

Local dev

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?. 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 show up within ~10 s; retention is 14 days.

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/<app>/ and add apps/<app>.yaml in nholtware/gitops.
  4. Push to main. Generating a repo emits no push event, so CI only starts on your first push.