Skip to content

Latest commit

 

History

110 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Churches

Build and deploy Node.js app to Azure Web App - crgolden-churches

Quality gate

The end-user surface of a nationwide U.S. church discovery platform: an Angular 22 SSR application with a Node.js Express Backend-for-Frontend (BFF), served by a single Node process. The BFF holds the OIDC session and proxies every data call to the standalone Directory API; the browser never sees an access token directly. Anonymous SEO routes are server-side rendered; authenticated routes are client-side.

Architecture: the full end-to-end platform architecture — Churches UI + BFF, the Directory API, and the Functions data pipeline — is documented in ARCHITECTURE.md. This README covers only how to run and deploy this repo.

Sibling Applications

Repo Role How Churches interacts
Identity OIDC Identity Provider OIDC authorization-code flow via openid-client in the Node BFF
Directory Church directory API BFF proxies /directory/api/** via a fetch-based proxy (src/bff/proxy.ts), attaching the user Bearer token (scope directory) when present
Infrastructure Health monitoring dashboard Polls GET /health (returns Healthy)

Architecture

See ARCHITECTURE.md for the end-to-end platform architecture (request flow through the Express/SSR stack, OIDC + proxy sequence diagrams, the Directory API, and the Functions data pipeline). In one paragraph:

A single Node process runs both the Angular 22 SSR renderer and an Express BFF. The BFF owns the OIDC session (openid-client v6, PKCE; scopes offline_access openid profile email directory), proxies /directory/api/** to the Directory API with the session's Bearer token (or anonymously), and requires X-CSRF: 1 on mutating calls. Anonymous routes (/, /churches, /churches/:slug) are server-rendered for SEO; authenticated routes (/contribute/:slug, /admin/moderation) are client-rendered. Frontend is zoneless Angular; maps are Leaflet, browser-only. Observability: OTLP traces/metrics → Grafana Alloy; structured logs → Elasticsearch (pino-elasticsearch). GET /health → Healthy.

Tech Stack

Layer Technology
Framework Node.js 24 / Express 5
Auth / BFF openid-client v6 + express-session + connect-redis
Frontend Angular 22 SSR (@angular/ssr)
Observability OpenTelemetry → Grafana Alloy (OTLP), pino → Elasticsearch
Hosting Azure App Service (Linux, Node 24)
Secrets Azure Key Vault (Managed Identity)

Getting Started

The full local stack needs the Identity server and the Directory API running, plus the local config:

Environment variables (.env.local, gitignored; npm start and serve:ssr load it):

PORT=4000
OidcAuthority=https://localhost:7261
DirectoryApiAddress=https://localhost:7002
ChurchesClientId=<dev client id>
ChurchesClientSecret=<dev client secret>
SessionSecret=<at-least-32-chars-dev-secret>
SessionStore=memory
SitemapBlobBaseUrl=https://crgolden.z13.web.core.windows.net/

PORT and SessionSecret are required, and so are RedisHost and RedisPort unless SessionStore=memory; a missing one stops the process at startup. SessionStore=memory keeps sessions in process (they don't survive a restart). To use a local Redis instance instead (e.g. if you already run one for Manuals/Infrastructure), remove SessionStore and set RedisHost=localhost, RedisPort=6379 and the five keepalive settings (RedisSocketTimeoutMs, RedisPingIntervalMs, RedisReconnectStepMs, RedisReconnectMaxDelayMs, RedisReconnectJitterMs; positive integers, with the ping interval shorter than the socket timeout); SessionStore=memory wins over any Redis settings.

Key Vault secrets required at runtime (production):

Each is wired up as an App Service setting holding a @Microsoft.KeyVault(SecretUri=...) reference, so App Service resolves it from Key Vault at startup using the app's managed identity and hands it to the process as an ordinary environment variable. The app has no Key Vault SDK dependency and makes no vault calls of its own.

Secret name Description
ChurchesClientId OIDC client ID
ChurchesClientSecret OIDC client secret
ElasticsearchUsername Elasticsearch basic auth username
ElasticsearchPassword Elasticsearch basic auth password
RedisPassword Redis TLS password
SessionSecret Cookie signing secret (≥ 32 chars)

Key pieces

  • src/server.ts — Express entry: /health, request logging, session, /bff/*, /directory/api proxy, Angular SSR catch-all.
  • src/bff/* — openid-client auth, session (Redis / in-memory), Directory proxy, CSRF.
  • src/environments/* — per-environment config (notably SSR allowedHosts), swapped via fileReplacements.
  • instrumentation.mjs — OpenTelemetry sidecar (OTLP→Alloy); src/telemetry/logging.ts — pino→Elasticsearch.

Commands

npm install
npm start            # ng serve — dev server with SSR via server.ts, http://localhost:56432; /bff and /directory/api go through src/proxy.conf.js (ASPNETCORE_URLS)
npm run build        # SSR production build → dist/churches.client/{server,browser}
npm run build:ci     # SSR build with the ci environment (allowedHosts=localhost)
npm run serve:ssr    # run the full SSR + BFF: node --import ./instrumentation.mjs dist/churches.client/server/server.mjs
npm run lint         # ESLint
npx vitest run       # unit tests (Vitest); add --coverage for LCOV
npm run e2e          # build:ci + Playwright E2E vs the real Node server + mock Directory API; /bff/* is route-mocked (self-builds)
npm run e2e:synthetic # seeded random walk of a deployed stack (WalkerBaseUrl); normally run on a schedule

See TESTING.md for the full E2E / synthetic-walker guide and CI configuration.

Project Structure

src/
  server.ts        # Express app: session, BFF routes, SSR catch-all
  bff/              # openid-client auth, session, Directory proxy, CSRF
  app/              # Angular application
  environments/     # per-environment config (allowedHosts, etc.)
  telemetry/        # pino → Elasticsearch logging
e2e/                # TypeScript Playwright E2E + synthetic walker
instrumentation.mjs # OpenTelemetry Node SDK init (loaded via --import)

Deployment

The GitHub Actions workflow (.github/workflows/main_crgolden-churches.yml) triggers on pushes to main and pull requests.

Build job — npm ci → lint → npm run build:ci (SSR, ci config) → Vitest coverage → Playwright E2E → SonarCloud (JS LCOV) → npm run build (production) → npm prune --omit=dev → upload deployment artifact.

Deploy job — deploys the Node bundle to Linux Azure App Service crgolden-churches (Production slot) via Azure OIDC. Startup command: node --import ./instrumentation.mjs dist/churches.client/server/server.mjs

There is no post-deploy job. The deployed app is exercised by the scheduled synthetic walker (.github/workflows/synthetic.yml), which signs in through Identity with a passkey and walks a seeded random journey twice a day.

This repo deploys only the frontend + Node BFF; the Directory API and its SQL schema deploy from the Directory repo (crgolden-directory).

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages