chore: initialize repository with project rules, docs, and phase-1 ADRs

Set up the foundation for the adastra-portal project:

- CLAUDE.md captures durable project rules (quality bar, security/perf/a11y
  as first-class, language, commit conventions, ADR proactivity).
- docs/ and decisions/ scaffolding with maintained indexes (docs/README.md
  and decisions/README.md), MADR 4.0.0 template, and tag vocabulary.
- Phase-1 ADRs (0001-0006) lock structural choices: ADR usage, Nx monorepo
  with the apps preset, naming convention (adastra-portal / portal-shell /
  portal-bff), Angular CSR/zoneless/Signals/Vitest, NestJS over Express,
  PostgreSQL with Prisma.
- docs/setup/ guides translated to English.
- .gitignore covers Node/Nx artifacts and the personal notes/ scratchpad.

The Nx workspace itself is not yet bootstrapped; that step is gated on a
revised setup guide aligned with the ADRs.
This commit is contained in:
Julien Gautier
2026-04-29 20:43:00 +02:00
commit 79eee77594
15 changed files with 1170 additions and 0 deletions
+45
View File
@@ -0,0 +1,45 @@
# Dependencies
node_modules/
.pnpm-store/
# Build outputs
dist/
build/
out-tsc/
tmp/
.nx/cache/
.nx/workspace-data/
# Test artifacts
coverage/
.test-output/
# Logs
*.log
npm-debug.log*
yarn-debug.log*
pnpm-debug.log*
# Environment / secrets
.env
.env.local
.env.*.local
*.pem
*.key
# OS / editor scrap
.DS_Store
Thumbs.db
*.swp
*~
.idea/
# VS Code: keep shared settings, ignore local overrides
.vscode/*.local
.vscode/*.code-workspace
# Claude Code: keep shared settings, ignore local overrides
.claude/settings.local.json
# Personal scratchpad — see CLAUDE.md
notes/
+22
View File
@@ -0,0 +1,22 @@
{
"workbench.colorCustomizations": {
"activityBar.activeBackground": "#1f6fd0",
"activityBar.background": "#1f6fd0",
"activityBar.foreground": "#e7e7e7",
"activityBar.inactiveForeground": "#e7e7e799",
"activityBarBadge.background": "#ee90bb",
"activityBarBadge.foreground": "#15202b",
"commandCenter.border": "#e7e7e799",
"sash.hoverBorder": "#1f6fd0",
"statusBar.background": "#1857a4",
"statusBar.foreground": "#e7e7e7",
"statusBarItem.hoverBackground": "#1f6fd0",
"statusBarItem.remoteBackground": "#1857a4",
"statusBarItem.remoteForeground": "#e7e7e7",
"titleBar.activeBackground": "#1857a4",
"titleBar.activeForeground": "#e7e7e7",
"titleBar.inactiveBackground": "#1857a499",
"titleBar.inactiveForeground": "#e7e7e799"
},
"peacock.remoteColor": "#1857a4"
}
+77
View File
@@ -0,0 +1,77 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project rules (durable)
These constraints were set by the project lead at kickoff. They apply to every change.
- **Scale & quality bar.** Treat this as a large-scale portal for a sizable organization, not a prototype. No bricolage, no exotic stacks. **Default to stable, recognized, battle-tested choices.** Cutting-edge / "à la pointe" alternatives must always be evaluated alongside the stable option, but are only adopted when the trade-off is captured in an ADR (drivers, risk, exit strategy). Pre-1.0 dependencies and one-maintainer projects are rejected unless an ADR justifies the exception.
- **Security, performance, accessibility.** All three are first-class concerns from day one — never bolted on. Architecture, dependency, and feature decisions must explicitly consider their impact on these axes and document the trade-offs.
- **Project name.** Currently `adastra_portal`, provisional. Do not hardcode it outside repo/workspace-level metadata so a rename stays a one-line change.
- **Language.** All code, identifiers, comments, documentation, commit messages, and PR descriptions are written in **English**. (Conversation with the project lead happens in French — but artifacts shipped in the repo are English-only.)
- **Commits / PRs.** Never add a `Co-Authored-By: Claude` trailer or a `🤖 Generated with Claude Code` footer to commits or PR bodies.
- **Be a peer, not a typist.** Challenge requests when a better approach exists; surface trade-offs frankly. Don't silently execute a suboptimal directive — propose, then execute the agreed plan.
## Documentation
- All documentation lives in `.md` files under [docs/](docs/), indexed by [docs/README.md](docs/README.md). The index is **maintained automatically** whenever a doc is added, renamed, or removed — no need to be asked.
- Documentation is written proactively whenever it is genuinely useful (architecture, runbooks, onboarding, security/perf/a11y rationales). It is **not** created for trivial things just to tick a box.
- The folder [notes/](notes/) is the project lead's **personal scratchpad** — git-ignored and not part of project artifacts. Never write project documentation there.
## Architectural Decision Records (ADRs)
- Format: **MADR 4.0.0** (https://adr.github.io/, https://github.com/adr/madr). Template at [decisions/template.md](decisions/template.md).
- Location: flat folder [decisions/](decisions/), indexed by [decisions/README.md](decisions/README.md).
- Filename convention: `NNNN-kebab-title.md` with **globally sequential** 4-digit numbers. Numbers are never reset and never reused — even when an ADR is superseded or deprecated.
- Categorization: via the `tags:` array in the MADR frontmatter (e.g. `[frontend, security]`). The canonical tag vocabulary lives in `decisions/README.md`; never invent ad-hoc tags inline.
- **Proactivity.** Any non-trivial development decision (tool/library choice, framework pattern, security control, perf budget, a11y target, naming convention, deprecation, breaking change) warrants proposing an ADR before implementation. Don't wait to be asked. Update the index in the same change.
## Architecture (decided in phase-1 ADRs)
The structural choices are recorded as ADRs and summarized below. Any change to these requires updating the corresponding ADR.
- **Workspace:** Nx monorepo with the `apps` preset, managed by pnpm — see [ADR-0002](decisions/0002-adopt-nx-monorepo-apps-preset.md).
- **Naming:** workspace `adastra-portal`; apps `portal-shell` (frontend) and `portal-bff` (backend); libs `feature-<name>` and `shared-<scope>` — see [ADR-0003](decisions/0003-workspace-and-app-naming-convention.md).
- **Frontend (`portal-shell`):** Angular at the latest LTS major — standalone APIs, zoneless change detection, Signals, **CSR only (no SSR)**, Vitest, SCSS — see [ADR-0004](decisions/0004-frontend-stack-angular-csr-zoneless-signals.md).
- **Backend (`portal-bff`):** NestJS at the latest stable major, mounted on the Express adapter (Fastify adapter swappable later) — see [ADR-0005](decisions/0005-backend-stack-nestjs.md).
- **Persistence:** PostgreSQL (latest stable major) via Prisma — see [ADR-0006](decisions/0006-persistence-postgresql-prisma.md).
- **Sessions / cache:** Redis self-hosted — to be locked-in by ADR-0009 (phase 2).
- **Identity:** dual-audience Microsoft Entra (workforce + Entra External ID for customers), OIDC Auth Code + PKCE flow via `@azure/msal-node` — to be locked-in by ADRs 0007, 0008, 0010 (phase 2).
- **Observability:** Pino structured logs + OpenTelemetry traces with W3C Trace Context propagation — to be locked-in by ADR-0012 (phase 2).
- **Runtime:** Node.js latest LTS major.
## Repository status
The Nx workspace is **not yet bootstrapped** — there is no `package.json`, no source code, no tests. Phase-1 ADRs (0001 → 0006) have fixed the structural choices; the next step is to scaffold the workspace per a revised version of [docs/setup/03-angular-nx-monorepo.md](docs/setup/03-angular-nx-monorepo.md) (the existing file still carries the original placeholder values and needs an update to match the ADRs before it is followed).
If asked to "build", "test", or "run" anything, first verify whether the workspace exists; if not, the right response is to scaffold it (or update the setup doc), not to invent commands.
## Commands once the workspace exists
App-scoped — `<app>` is one of `portal-shell`, `portal-bff`:
```bash
pnpm nx serve <app> # dev server
pnpm nx build <app>
pnpm nx test <app> # Vitest, all tests for the app
pnpm nx lint <app>
```
Run a single test file:
```bash
pnpm nx test <app> --testFile=path/to/file.spec.ts
```
Workspace-wide:
```bash
pnpm nx run-many -t lint test build
pnpm nx affected -t lint test build # only projects affected by current changes
pnpm nx format:check
```
## Environment conventions
- **Never install Angular globally.** Use `pnpm dlx` for one-off CLI invocations and project-local `pnpm nx ...` for everything else — versions stay pinned per project.
- **Work inside the WSL filesystem** (`~/dev/...`), never under `/mnt/c` — the latter has severe I/O penalties that break Nx caching and dev-server reload times.
- **pnpm is mandatory** (activated via `corepack enable`); do not introduce npm or yarn lockfiles.
- Prettier config target: `singleQuote: true`, `semi: true`, `printWidth: 100`.
@@ -0,0 +1,62 @@
---
status: accepted
date: 2026-04-29
decision-makers: R&D Lead
tags: [process]
---
# Use ADRs to record architectural decisions
## Context and Problem Statement
Architecturally-significant decisions will be taken throughout the lifecycle of `adastra-portal`. Without a written, durable trace of *why* a decision was made — context, drivers, alternatives, trade-offs — that knowledge fades within months. New contributors re-litigate settled debates, the rationale of constraints (security, performance, accessibility) gets lost, and reversing a choice later becomes risky because nobody remembers what it was protecting against.
How do we record decisions in a way that is light enough to be sustained, durable enough to outlive contributor turnover, and integrated into the development workflow?
## Decision Drivers
* Preserve the *why* of each decision so future contributors can understand or revisit it without re-deriving it.
* Make architecture reviewable — both at the time of a decision and during retrospective audits.
* Keep the format light enough that writing an ADR isn't a bureaucratic deterrent.
* Live alongside the code in version control, so decisions evolve with what they govern.
* Match the project's stated values: high technical bar, no bricolage, security/performance/accessibility as first-class concerns whose rationale must be traceable.
## Considered Options
* No formal records — rely on commit messages and tribal knowledge.
* External wiki / Confluence — decisions live outside the codebase.
* ADRs in markdown using the [MADR 4.0.0](https://github.com/adr/madr) format, in-repo.
* ADRs in another format (Nygard, Y-Statement, Cockburn).
## Decision Outcome
Chosen option: **ADRs in markdown using MADR 4.0.0**, stored in `decisions/` at the repository root.
Conventions are defined in [README.md](README.md):
- flat folder layout (no nested category folders);
- globally sequential 4-digit numeric prefix (`NNNN-kebab-title.md`);
- categorization via the `tags:` field in the MADR frontmatter, drawn from a canonical vocabulary;
- status lifecycle: `proposed``accepted` → optionally `deprecated` / `superseded by ADR-NNNN`;
- index maintained in [README.md](README.md) alongside any ADR change.
### Consequences
* Good, because the *why* of every architecturally-significant decision is captured at the moment it is made and travels with the code.
* Good, because ADRs are reviewed via the normal pull-request flow — architecture becomes an explicit team conversation.
* Good, because MADR provides enough structure (drivers, options, trade-offs) without being heavy.
* Good, because flat-folder + tags scales better than nested category folders as the count grows.
* Bad, because writing an ADR adds friction; this must be balanced against the cost of *not* writing one.
* Bad, because the index is maintained manually until tooling or a CI check is added.
### Confirmation
* Every change introducing or transitioning the status of an ADR updates the index table in [README.md](README.md) in the same commit.
* PR review explicitly checks for ADR proposal whenever a non-trivial decision is being introduced.
* (Future) A CI lint job validates filename pattern, frontmatter shape, and tag vocabulary.
## More Information
* MADR 4.0.0 specification: https://github.com/adr/madr
* General ADR resources: https://adr.github.io/
* Template: [template.md](template.md)
* Tag vocabulary: see "Tag vocabulary" in [README.md](README.md).
@@ -0,0 +1,92 @@
---
status: accepted
date: 2026-04-29
decision-makers: R&D Lead
tags: [infrastructure, frontend, backend]
---
# Adopt Nx monorepo with the `apps` preset
## Context and Problem Statement
`adastra-portal` will host two cooperating runtime artifacts from day one — an Angular SPA frontend and a Node.js BFF backend — and is expected to grow with feature libraries shared between them (auth, data-access, UI primitives). Strongly-typed contracts must travel from back to front without round-tripping through a published package, dependency hygiene must be enforceable, and CI must run only on what changed.
How do we structure the repository so both apps coexist with shared libraries, builds and tests run incrementally, and the tooling is mainstream enough to be supported long-term?
## Decision Drivers
* Two cooperating runtimes (Angular + NestJS), both TypeScript, sharing types and contracts on the wire.
* Need for incremental, cached builds and tests on what changed (`affected`-style workflow).
* Long-term enterprise track record — no exotic stack.
* Compatibility with `pnpm` (already mandated by project setup).
* Enforceable dependency boundaries between apps and libs.
## Considered Options
* **Polyrepos** — separate repository per runtime.
* **Nx with the `apps` preset** — generic multi-tech workspace, plugins added as needed (`@nx/angular`, `@nx/nest`).
* **Nx with the `angular-monorepo` preset** — front-first preset, BFF grafted on later.
* **Turborepo + pnpm workspaces** — lighter alternative to Nx.
* **pnpm workspaces alone** — manual orchestration, no monorepo tool.
## Decision Outcome
Chosen option: **Nx monorepo with the `apps` preset, managed by pnpm**.
The workspace will host:
- `apps/portal-shell` — Angular SPA frontend (see [ADR-0004](0004-frontend-stack-angular-csr-zoneless-signals.md));
- `apps/portal-bff` — NestJS BFF backend (see [ADR-0005](0005-backend-stack-nestjs.md));
- `libs/shared/<scope>` and `libs/feature/<name>` for shared and feature code (see [ADR-0003](0003-workspace-and-app-naming-convention.md)).
Plugins enabled at bootstrap: `@nx/angular`, `@nx/nest`, `@nx/eslint`, `@nx/vite` (Vitest).
### Consequences
* Good, because front and back share types, lint config, formatter, and test runner with no extra plumbing.
* Good, because Nx's project graph + `affected` make CI fast and frugal as the codebase grows.
* Good, because the `apps` preset stays neutral — neither runtime is privileged in workspace structure.
* Good, because Nx is mainstream, used by major enterprises, and actively maintained by Nrwl.
* Bad, because Nx adds a non-trivial conceptual surface (executors, generators, project graph) — a one-time learning cost.
* Bad, because monorepos require discipline to keep dependency boundaries clean — mitigated by Nx's `enforce-module-boundaries` lint rule.
### Confirmation
* Workspace is bootstrapped with `pnpm dlx create-nx-workspace@latest adastra-portal --preset=apps --pm=pnpm`.
* `nx.json` has `@nx/eslint/enforce-module-boundaries` enabled and configured against the project tags.
* CI runs `pnpm nx affected -t lint test build` on every push.
## Pros and Cons of the Options
### Polyrepos
* Good, because each runtime has independent versioning and CI scope.
* Bad, because sharing TypeScript types across repos requires a published package (overhead, version drift).
* Bad, because cross-cutting changes (e.g. adjusting an auth claim shape) touch two repos and two PRs.
### Nx with `apps` preset (chosen)
* Good, because runtime-agnostic — Angular and Node coexist as peers.
* Good, because Nx provides build orchestration, caching, generators, and lint boundaries out of the box.
### Nx with `angular-monorepo` preset
* Good, because faster initial setup for a front-first project.
* Bad, because the preset implies the project is "an Angular workspace" — the BFF feels grafted, the workspace `package.json` ends up biased toward Angular tooling.
### Turborepo
* Good, because lighter, no codegen surface.
* Neutral, because caching/affected is comparable.
* Bad, because no first-class Angular plugin; both runtimes would need manual integration.
* Bad, because smaller enterprise track record than Nx.
### pnpm workspaces alone
* Good, because no extra tool to learn.
* Bad, because no caching, no `affected`, no generators — every piece reinvented by hand (the bricolage we're forbidden).
## More Information
* Nx documentation: https://nx.dev
* Nx `apps` preset: https://nx.dev/getting-started/intro
* Related ADRs: [ADR-0003](0003-workspace-and-app-naming-convention.md), [ADR-0004](0004-frontend-stack-angular-csr-zoneless-signals.md), [ADR-0005](0005-backend-stack-nestjs.md), ADR-0017 (CI/CD, future).
@@ -0,0 +1,61 @@
---
status: accepted
date: 2026-04-29
decision-makers: R&D Lead
tags: [process]
---
# Workspace and app naming convention
## Context and Problem Statement
Default Nx scaffolding offers placeholder names like `my-workspace` and `web`, which are unacceptable for a long-lived enterprise project. Naming has to survive two foreseeable changes: the product's marketing name may evolve (the working title `adastra_portal` is provisional), and the project may sit alongside other R&D projects in the same Gitea organization, where collisions on generic names like `web` or `api` would cause confusion.
What naming convention do we adopt for the workspace, the apps, and future libraries so names are explicit, function-anchored, and stable against rebranding?
## Decision Drivers
* The product's marketing name is provisional — names anchored on it become stale at rebrand.
* Names should reflect *function*, not *brand*.
* Multiple R&D projects may share the org — generic names (`web`, `api`, `frontend`) cause friction.
* Compliance with npm package naming rules (lowercase, kebab-case).
* Readability inside Nx's project graph and CLI output.
## Considered Options
* **Defaults** — `my-workspace`, `web`. (Rejected up-front.)
* **Brand-anchored** — `adastra-front`, `adastra-back`. Fragile to rebranding.
* **Function-prefixed** — `portal-shell`, `portal-bff`. (Chosen.)
* **Generic** — `shell`, `bff`. Risk of org-wide collision; reduced readability.
## Decision Outcome
Chosen option: **function-prefixed naming**.
| Scope | Name | Rationale |
| --- | --- | --- |
| Workspace / repo / npm package root | `adastra-portal` | matches the Gitea repository name; the only place a brand-ish name lives, so a rebrand is a one-line change |
| Frontend app | `portal-shell` | "shell" describes the function — the Angular host that loads features |
| Backend app | `portal-bff` | explicit role: backend-for-frontend |
| Feature libraries | `feature-<name>` | e.g. `feature-auth`, `feature-billing` |
| Shared libraries | `shared-<scope>` | e.g. `shared-ui`, `shared-data-access`, `shared-util` |
The `portal-` prefix encodes the *function* (a portal). Even if the brand `Adastra` is renamed, "portal" remains accurate. The brand-ish token `adastra` is confined to the workspace root and a tiny number of metadata files (root `package.json`, repo URL).
### Consequences
* Good, because names survive a brand change with minimal churn.
* Good, because `portal-shell` / `portal-bff` are unambiguous in CLI output and in the Nx project graph.
* Good, because the prefix scales when more apps appear (e.g. `portal-admin`, `portal-jobs`).
* Bad, because the prefix adds verbosity (`pnpm nx serve portal-shell` vs `pnpm nx serve web`). Acceptable.
* Neutral, because if "portal" itself ceases to be accurate (e.g. the system pivots to something other than a portal), the prefix would also need updating — but that would be a deeper architectural pivot anyway.
### Confirmation
* Nx project names match the convention in `nx.json` and in each project's `project.json`.
* `@nx/eslint/enforce-module-boundaries` is configured with tags aligned to the convention (`scope:portal-shell`, `scope:portal-bff`, `type:feature`, `type:shared`).
* PR review rejects new project names that don't match.
## More Information
* Related ADRs: [ADR-0002](0002-adopt-nx-monorepo-apps-preset.md), and the "Project name" rule in [CLAUDE.md](../CLAUDE.md).
@@ -0,0 +1,95 @@
---
status: accepted
date: 2026-04-29
decision-makers: R&D Lead
tags: [frontend]
---
# Frontend stack — Angular (latest LTS), standalone, zoneless, Signals, CSR-only, Vitest
## Context and Problem Statement
The portal's frontend (`portal-shell`) is a single-page application that aggregates access to existing applications and progressively integrates re-developed features. It sits fully behind authentication: no public content. We need to fix the framework, the rendering mode, the change-detection model, and the test runner now, so subsequent ADRs (auth flow, observability, accessibility, performance budgets) can build on a stable foundation.
What frontend stack maximizes alignment with our backend (NestJS), gives us first-class accessibility and performance hooks, and is enterprise-stable for a long-lived project?
## Decision Drivers
* Architectural alignment with NestJS (DI, decorators, modules, RxJS) — minimizes cognitive distance for the team.
* Performance and accessibility as first-class concerns from day one.
* Long-term enterprise stability (no pre-1.0, no exotic stack).
* End-to-end type safety.
* No public surface — all content gated by authentication, which removes the usual SSR drivers (SEO, anonymous LCP).
## Considered Options
* **Angular (latest LTS) — standalone, zoneless, Signals, CSR, Vitest.** (Chosen.)
* Angular with SSR (`@angular/ssr`).
* React + Next.js (or Vite + React Router).
* Vue + Nuxt.
* Svelte + SvelteKit.
## Decision Outcome
Chosen option: **Angular at the latest LTS major**, with the following modern defaults:
* **Standalone APIs** — no `NgModule`s. Configuration is composed via providers; routing is functional.
* **Zoneless change detection** — no `Zone.js`. Change detection is driven by Signals and explicit `markForCheck` where needed.
* **Signals** — primary reactive primitive for component state. RxJS retained for async streams (HTTP, events).
* **Vitest** — unit and component test runner (Karma is deprecated upstream).
* **CSR only — no SSR** for v1. Reconsider only if a measured performance metric requires it.
* **SCSS** for styles. Design-token strategy to be defined in a future ADR.
* **Strict TypeScript** across the workspace (`strict: true`, `noUncheckedIndexedAccess: true`).
### Consequences
* Good, because the team works with a single mental model across front and back (DI, decorators, RxJS).
* Good, because zoneless + Signals removes Zone.js overhead and gives finer-grained, more predictable reactivity.
* Good, because Vitest is significantly faster than Karma and shares the Vite toolchain Nx already uses.
* Good, because CSR keeps the BFF free of SSR concerns (no auth-aware rendering, no double-fetch, no hydration debugging) — simpler operational surface.
* Bad, because Angular's release cadence is brisk; the project commits to staying on LTS rather than skipping versions.
* Bad, because zoneless and Signals are recent — though stable, the broader Angular ecosystem still includes Zone-dependent libraries that must be vetted before adoption.
* Bad, because CSR yields a minimal initial HTML payload; this must be paired with strong performance budgets (covered by ADR-0015, future).
### Confirmation
* `package.json` pins Angular to the current LTS major.
* `app.config.ts` registers `provideZonelessChangeDetection()` (or its current stable equivalent) and standalone routing.
* `tsconfig.base.json` sets `strict: true` and `noUncheckedIndexedAccess: true`.
* CI runs `pnpm nx test portal-shell` (Vitest).
* No `import 'zone.js'` anywhere in the codebase.
* `@angular/ssr` is not installed.
## Pros and Cons of the Options
### Angular CSR (chosen)
* Good, because alignment with NestJS minimizes context-switching for the team.
* Good, because mature, enterprise-supported (Google + community).
* Good, because Signals + zoneless make Angular's reactivity competitive with React/Vue.
* Bad, because verbosity is higher than minimalist frameworks.
### Angular SSR
* Good, because better TTFB and accessibility on first paint for unauthenticated content.
* Bad, because we have no unauthenticated content — the gain is essentially zero.
* Bad, because SSR-with-auth introduces non-trivial complexity (cookie-aware fetch, hydration with secured data).
### React + Next.js
* Good, because large ecosystem, fast iteration.
* Bad, because no architectural alignment with NestJS — different DI/state paradigms.
* Bad, because Next.js's defaults push toward server components and edge rendering, which conflict with our on-prem, all-authenticated profile.
### Vue + Nuxt / Svelte + SvelteKit
* Good, because lighter syntax, strong DX.
* Bad, because no DI alignment with NestJS.
* Bad, because smaller enterprise-grade community than Angular or React.
## More Information
* Angular zoneless change detection: https://angular.dev/guide/experimental/zoneless
* Angular Signals: https://angular.dev/guide/signals
* Vitest with Nx: https://nx.dev/nx-api/vite
* Related ADRs: [ADR-0005](0005-backend-stack-nestjs.md), ADR-0008 (auth flow, future), ADR-0014 (accessibility, future), ADR-0015 (performance budgets, future).
+91
View File
@@ -0,0 +1,91 @@
---
status: accepted
date: 2026-04-29
decision-makers: R&D Lead
tags: [backend]
---
# Backend stack — NestJS over Express, Fastify, Hono
## Context and Problem Statement
The backend (`portal-bff`) is the BFF: it owns authentication, sessions, and downstream API orchestration; it persists business data; it must be observable and auditable. The frontend never holds tokens or talks directly to upstream APIs. The choice of HTTP framework has architectural ripple effects (DI shape, validation pipeline, security defaults, ecosystem of integrations).
Which Node TypeScript framework gives us the right combination of structure, ecosystem, performance, and longevity for a security-sensitive BFF in an enterprise context?
## Decision Drivers
* Security is a first-class concern — the framework must impose structure rather than leave it to be hand-built.
* Architectural alignment with the Angular frontend (DI, decorators, modules, RxJS).
* First-class ecosystem for the things we will need: Passport / MSAL, Pino logging, OpenTelemetry, Prisma, Redis, validation, health checks.
* Long-term enterprise viability (no pre-1.0, no single-maintainer projects).
* Performance acceptable for a BFF (request mediation, not raw throughput benchmarks).
## Considered Options
* **NestJS** (latest stable major). (Chosen.)
* **Express 5** — minimal, ubiquitous.
* **Fastify 5** — performance-oriented, plugin-based.
* **Hono** — modern, multi-runtime.
* **tRPC** — typed RPC over HTTP.
## Decision Outcome
Chosen option: **NestJS at the latest stable major**, mounted on the **Express adapter** by default. Migration to the Fastify adapter remains an option later if profiling justifies it; this is a configuration change, not an application rewrite, because both adapters are first-class in NestJS.
### Consequences
* Good, because NestJS imposes a clear, opinionated architecture (modules, providers, controllers, pipes, guards, interceptors) — minimal room for bricolage.
* Good, because the team lives in the same paradigm as Angular (DI, decorators, RxJS).
* Good, because every supporting concern has a first-class NestJS integration: `@nestjs/passport` + `@azure/msal-node` for auth, `nestjs-pino` for logs, `nestjs-cls` for AsyncLocalStorage, `@nestjs/terminus` for health checks, `nestjs-prisma` for persistence.
* Good, because NestJS is at v11+ with a healthy enterprise track record (Adidas, Roche, Capgemini publicly), an active core team, and commercial support options.
* Good, because the adapter pattern lets us swap to Fastify later without touching application code.
* Bad, because NestJS has a non-trivial conceptual surface (DI hierarchy, lifecycle hooks, request-scoped providers) — one-time learning cost.
* Bad, because the framework is heavier than Express or Fastify alone — acceptable for our profile.
### Confirmation
* `apps/portal-bff/package.json` depends on `@nestjs/core`, `@nestjs/common`, `@nestjs/platform-express`.
* `apps/portal-bff/src/main.ts` bootstraps via `NestFactory.create`.
* All HTTP entry points are `@Controller`-based; no raw Express routes outside framework escape hatches.
* A global `ValidationPipe` is applied with `whitelist: true, forbidNonWhitelisted: true, transform: true`.
## Pros and Cons of the Options
### NestJS (chosen)
* Good, because opinionated structure sets a clear bar across the team.
* Good, because alignment with Angular reduces cognitive distance.
* Good, because the ecosystem covers every BFF concern we have.
* Bad, because verbose for tiny services — acceptable here, this is a real BFF.
### Express 5
* Good, because the lowest common denominator — every Node engineer knows it.
* Bad, because minimalist by design — every cross-cutting concern (DI, validation, structured errors, lifecycle) is built by hand. For a security-sensitive BFF in an "anti-bricolage" project, this is a poor fit.
* Bad, because Express-as-foundation in 2026 is the legacy default; the modern center of gravity has moved.
### Fastify 5
* Good, because excellent raw performance and a clean plugin model.
* Good, because schema-driven validation is built in.
* Bad, because architecture is left to the application — we'd reinvent NestJS poorly.
* Note: NestJS can run on a Fastify adapter — we can have both if a perf profile demands it.
### Hono
* Good, because modern, minimal, multi-runtime (Node, Bun, Workers).
* Bad, because too young (~2023) for our longevity bar — limited enterprise track record.
* Status: keep in watch list; reconsider in 1224 months.
### tRPC
* Good, because end-to-end type safety from server to client without code generation.
* Bad, because it tightly couples frontend and backend builds — incompatible with a BFF that may also need to serve non-Angular consumers later (downstream API gateways, mobile, partner integrations).
* Bad, because not a general HTTP framework — orthogonal to the choice we're making.
## More Information
* NestJS documentation: https://docs.nestjs.com
* NestJS Fastify adapter: https://docs.nestjs.com/techniques/performance
* Related ADRs: [ADR-0002](0002-adopt-nx-monorepo-apps-preset.md), [ADR-0006](0006-persistence-postgresql-prisma.md), ADR-0008 (auth flow, future), ADR-0009 (sessions, future), ADR-0012 (observability, future).
@@ -0,0 +1,119 @@
---
status: accepted
date: 2026-04-29
decision-makers: R&D Lead
tags: [data, backend]
---
# Persistence — PostgreSQL with Prisma
## Context and Problem Statement
The BFF owns two persistent concerns: session state (covered by Redis in the future ADR-0009) and **business data** owned directly by the portal — the portal is not a pure proxy. Business data is relational, multi-audience (workforce vs. external customers), audit-sensitive, and must run on-prem.
Which database engine and which data-access layer do we adopt?
## Decision Drivers
* On-prem deployment (no managed-service shortcut).
* Multi-audience data with strict isolation between workforce and customer scopes (Row-Level Security desirable).
* ACID transactions and a well-understood operational story (HA, backup, point-in-time recovery).
* TypeScript-first developer experience for the BFF.
* Long-term licensing safety — open source, no recent license-change risk (e.g. SSPL).
## Considered Options
### Database engine
* **PostgreSQL** (latest stable major). (Chosen.)
* MariaDB / MySQL.
* SQL Server (would imply Microsoft licensing terms).
* MongoDB / DocumentDB (rejected — not a fit for relational, audit-heavy business data; SSPL concerns).
### Data-access layer
* **Prisma** (latest stable major). (Chosen.)
* Drizzle.
* Kysely.
* TypeORM.
* Plain `pg` driver + hand-written SQL.
## Decision Outcome
Chosen options: **PostgreSQL** as the engine, **Prisma** as the data-access layer.
The engine is pinned to the latest stable major at workspace bootstrap, and tracks the upstream support window thereafter.
Prisma is wired into NestJS via the `nestjs-prisma` integration, with a dedicated `PrismaService` extending `PrismaClient` and exposed through DI. Migrations are managed by `prisma migrate`, committed to the repository, validated in CI, and applied through a controlled deployment step (covered by a future infrastructure ADR).
### Consequences
* Good, because PostgreSQL is the de facto enterprise open-source RDBMS — mature HA, replication, RLS, JSONB, full-text search.
* Good, because RLS gives a strong substrate for the workforce/customer isolation called for by ADR-0007 (identity model, future).
* Good, because Prisma's schema-first model is a single source of truth, with type generation and a readable migration log.
* Good, because Prisma's NestJS integration is well-trodden territory.
* Bad, because Prisma adds a generated client and a query engine binary — operational surface to ship and version.
* Bad, because Prisma's escape hatch for advanced PostgreSQL features (advanced CTEs, window functions, RLS policies) is `$queryRaw` — usable, but a reminder that not everything goes through the ORM. We will not fight Prisma when raw SQL is the right tool.
* Neutral, because Drizzle is a credible "à la pointe" alternative; we accept the more conservative choice for now and re-evaluate at the next major architectural review.
### Confirmation
* `apps/portal-bff` depends on `prisma` and `@prisma/client`.
* A single `schema.prisma` lives at `apps/portal-bff/prisma/schema.prisma`.
* `nestjs-prisma`'s `PrismaModule` is imported globally in the BFF.
* CI runs `prisma validate` and `prisma migrate diff` against the staging schema.
* Database engine version is pinned in deployment manifests.
## Pros and Cons of the Options
### PostgreSQL (chosen)
* Good, because mature, open source under the PostgreSQL License (permissive), broad on-prem operational tooling.
* Good, because RLS is first-class — directly serves multi-audience isolation.
* Good, because JSONB + relational hybrid suits BFF data shapes.
* Bad, because operational complexity (HA, backups, tuning) must be owned — true of any on-prem RDBMS.
### MariaDB / MySQL
* Good, because operationally well-known.
* Bad, because feature gap vs. PostgreSQL (RLS, JSON ergonomics, CTE maturity, generated columns).
### MongoDB
* Good, because the document model fits some BFF caches.
* Bad, because business data here is relational; forcing a document store would push joins into the application — bricolage.
* Bad, because the SSPL license raises enterprise legal complications.
### Prisma (chosen)
* Good, because schema-first declarative, strong DX, type generation, mainstream in 2026.
* Good, because a mature NestJS integration exists.
* Bad, because the query engine binary adds a deployment artifact.
* Bad, because some PostgreSQL features require `$queryRaw`.
### Drizzle
* Good, because lighter, closer to SQL, faster, type-safe.
* Bad, because younger (2022) — smaller enterprise track record than Prisma. Kept on the watch list for re-evaluation.
### Kysely
* Good, because elegant query-builder, no ORM magic, fully type-safe.
* Bad, because no schema/migrations of its own — must be paired with another tool, increasing surface area.
### TypeORM
* Good, because long-standing default in NestJS history.
* Bad, because design issues (decorator hell, runtime metadata fragility, migration story) and slowing maintenance — community momentum has shifted to Prisma and Drizzle.
### Plain `pg` driver + hand-written SQL
* Good, because zero abstraction; full control.
* Bad, because every cross-cutting concern (transactions, connection pooling, type mapping, migrations) is hand-built — the very bricolage we are forbidden.
## More Information
* PostgreSQL: https://www.postgresql.org/
* Prisma: https://www.prisma.io/
* `nestjs-prisma`: https://nestjs-prisma.dev/
* Related ADRs: [ADR-0005](0005-backend-stack-nestjs.md), ADR-0007 (identity model + RLS use, future), ADR-0009 (sessions in Redis, future), future infrastructure ADRs (Postgres HA, backup).
+52
View File
@@ -0,0 +1,52 @@
# Architectural Decision Records
This project records architecturally-significant decisions as **ADRs** in the [MADR 4.0.0](https://github.com/adr/madr) format. References: [adr.github.io](https://adr.github.io/).
## Why ADRs
ADRs capture the *why* behind a decision — context, drivers, options considered, trade-offs accepted — at the moment the decision is made. They make architecture reviewable, onboarding faster, and prevent the same debate from being re-litigated later.
## Conventions
- **Format:** MADR 4.0.0. Start from [template.md](template.md).
- **Filename:** `NNNN-kebab-case-title.md`, e.g. `0007-adopt-tailwind-for-design-tokens.md`.
- **Numbering:** globally sequential 4-digit prefix. Numbers never reset, never get reused — even when an ADR is superseded or deprecated.
- **Layout:** flat folder. ADRs are not nested into category subfolders; topical organization happens via tags.
- **Tags:** every ADR carries a `tags:` array in the MADR frontmatter, drawn from the [tag vocabulary](#tag-vocabulary) below. An ADR may carry several tags. Propose new tags (or renames) in the same PR that needs them; never invent ad-hoc tags inline.
- **Status lifecycle:** `proposed``accepted` → optionally `deprecated` or `superseded by [ADR-NNNN](NNNN-other.md)`. Update the YAML frontmatter; never delete an ADR.
- **Index maintenance:** every ADR addition or status change must update the [Index](#index) below in the same commit.
## When to write an ADR
Write one whenever a development decision is non-trivial: tool or library choice, framework pattern, security control, perf budget, a11y target, naming convention, deprecation, breaking change, or any choice that future contributors would benefit from understanding the *why* of.
## Tag vocabulary
The vocabulary below is the source of truth. It is intentionally coarse — propose extensions only when an existing tag genuinely doesn't fit, and avoid overly narrow tags.
| Tag | Scope |
| ---------------- | ----- |
| `frontend` | UI, Angular, components, design system, client-side state |
| `backend` | API, BFF, server-side services |
| `security` | AuthN, AuthZ, sessions, CSP, dependency scanning, secret management |
| `performance` | Perf budgets, caching, bundle size, Lighthouse |
| `accessibility` | WCAG, a11y testing, keyboard, ARIA, contrast |
| `infrastructure` | CI/CD, hosting, deployment, runtime |
| `observability` | Logs, metrics, traces, correlation IDs, monitoring |
| `data` | Persistence, schemas, migrations, data flow |
| `process` | Team conventions, workflows, repo policy |
> _Status: starter vocabulary, to be refined as ADRs accumulate. Update this table whenever a tag is added, renamed, or retired._
## Index
ADRs are listed in numerical order. To slice by topic, filter on the `Tags` column.
| # | Title | Status | Tags | Date |
| ---- | ----- | ------ | ---- | ---- |
| [0001](0001-use-adrs-to-record-architectural-decisions.md) | Use ADRs to record architectural decisions | accepted | `process` | 2026-04-29 |
| [0002](0002-adopt-nx-monorepo-apps-preset.md) | Adopt Nx monorepo with the `apps` preset | accepted | `infrastructure`, `frontend`, `backend` | 2026-04-29 |
| [0003](0003-workspace-and-app-naming-convention.md) | Workspace and app naming convention | accepted | `process` | 2026-04-29 |
| [0004](0004-frontend-stack-angular-csr-zoneless-signals.md) | Frontend stack — Angular (latest LTS), standalone, zoneless, Signals, CSR-only, Vitest | accepted | `frontend` | 2026-04-29 |
| [0005](0005-backend-stack-nestjs.md) | Backend stack — NestJS over Express, Fastify, Hono | accepted | `backend` | 2026-04-29 |
| [0006](0006-persistence-postgresql-prisma.md) | Persistence — PostgreSQL with Prisma | accepted | `data`, `backend` | 2026-04-29 |
+72
View File
@@ -0,0 +1,72 @@
---
# `tags` is REQUIRED for this project — pick from the vocabulary in decisions/README.md.
# The other fields are standard MADR 4.0.0 metadata; remove any that don't apply.
status: "{proposed | rejected | accepted | deprecated | … | superseded by [ADR-NNNN](NNNN-example.md)}"
date: {YYYY-MM-DD when the decision was last updated}
decision-makers: {list everyone involved in the decision}
consulted: {list everyone whose opinions are sought (typically subject-matter experts); two-way communication}
informed: {list everyone who is kept up-to-date on progress; one-way communication}
tags: [<tag1>, <tag2>]
---
# {short title, representative of solved problem and found solution}
## Context and Problem Statement
{Describe the context and problem statement, e.g., in free form using two to three sentences or in the form of an illustrative story. You may want to articulate the problem in form of a question and add links to collaboration boards or issue management systems.}
<!-- This is an optional element. Feel free to remove. -->
## Decision Drivers
* {decision driver 1, e.g., a force, facing concern, …}
* {decision driver 2, e.g., a force, facing concern, …}
*
## Considered Options
* {title of option 1}
* {title of option 2}
* {title of option 3}
*
## Decision Outcome
Chosen option: "{title of option 1}", because {justification, e.g., only option which meets k.o. criterion / resolves force / comes out best (see below)}.
<!-- This is an optional element. Feel free to remove. -->
### Consequences
* Good, because {positive consequence, e.g., improvement of one or more desired qualities, …}
* Bad, because {negative consequence, e.g., compromising one or more desired qualities, …}
*
<!-- This is an optional element. Feel free to remove. -->
### Confirmation
{Describe how the implementation of / compliance with the ADR is confirmed. E.g., by a review, an ArchUnit test, a Lighthouse CI threshold, an axe-core assertion. Although classified as optional, this element is included in most ADRs.}
<!-- This is an optional element. Feel free to remove. -->
## Pros and Cons of the Options
### {title of option 1}
{example | description | pointer to more information | …}
* Good, because {argument a}
* Good, because {argument b}
* Neutral, because {argument c}
* Bad, because {argument d}
### {title of option 2}
{example | description | pointer to more information | …}
* Good, because {argument a}
* Good, because {argument b}
* Neutral, because {argument c}
* Bad, because {argument d}
<!-- This is an optional element. Feel free to remove. -->
## More Information
{Additional evidence/confidence for the decision; team agreement; when and how the decision should be realized; consequences for other decisions, projects, or teams; links to related ADRs.}
+32
View File
@@ -0,0 +1,32 @@
# Documentation index
This is the entry point to all project documentation. It is maintained automatically: any addition, rename, or removal of a `.md` file under `docs/` must be reflected here in the same change.
## Conventions
- Documentation is written in **English**.
- One topic per file. Group related files into a folder when there are three or more.
- Cross-reference with relative links so they keep working in GitHub, IDEs, and exported sites.
- For architectural decisions, do **not** add them here — they belong in [../decisions/](../decisions/) as MADR 4.0.0 ADRs.
## Sections
### Onboarding & environment
Setup guides for new contributors:
- [setup/01-wsl-terminal-setup.md](setup/01-wsl-terminal-setup.md) — modern WSL terminal (Zsh + Powerlevel10k + CLI tools)
- [setup/02-dev-web-stack.md](setup/02-dev-web-stack.md) — Node via nvm, pnpm via corepack, Docker
- [setup/03-angular-nx-monorepo.md](setup/03-angular-nx-monorepo.md) — Angular + Nx monorepo bootstrap
### Architecture
_Empty — to be populated as the project grows._
### Operations & runbooks
_Empty — to be populated when we deploy._
### Security, performance, accessibility
_Empty — placeholders to be filled with rationale docs alongside their corresponding ADRs._
+134
View File
@@ -0,0 +1,134 @@
# 🖥️ Modern WSL terminal (Zsh + Powerlevel10k)
## Goal
A terminal that is:
* readable
* fast
* equipped with intelligent autocomplete
* suited for modern development
---
## 1) Install Zsh
```bash
sudo apt update
sudo apt install -y zsh
```
Set it as the default shell:
```bash
chsh -s $(which zsh)
```
Under WSL, add the following to `~/.bashrc`:
```bash
if command -v zsh >/dev/null 2>&1; then
exec zsh
fi
```
---
## 2) Install Oh My Zsh
```bash
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
```
---
## 3) Install Powerlevel10k
```bash
git clone --depth=1 https://github.com/romkatv/powerlevel10k.git \
${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/themes/powerlevel10k
```
In `~/.zshrc`:
```bash
ZSH_THEME="powerlevel10k/powerlevel10k"
```
Then:
```bash
source ~/.zshrc
p10k configure
```
---
## 4) Install a Nerd Font (on Windows)
Install a Nerd Font on Windows (MesloLGS NF recommended).
Configure it in Windows Terminal:
* Settings → Debian profile
* Font → MesloLGS NF
---
## 5) Useful plugins
```bash
plugins=(
git
fzf
zsh-autosuggestions
zsh-syntax-highlighting
)
```
Install them:
```bash
git clone https://github.com/zsh-users/zsh-autosuggestions \
${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
git clone https://github.com/zsh-users/zsh-syntax-highlighting \
${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting
```
---
## 6) Modern CLI tools
```bash
sudo apt install -y \
bat eza fd-find ripgrep fzf zoxide
```
Recommended aliases:
```bash
alias ls="eza --icons"
alias ll="eza -lh --git --icons"
alias cat="batcat"
alias grep="rg"
alias find="fdfind"
```
---
## 7) FZF
Add to `~/.zshrc`:
```bash
[ -f ~/.fzf.zsh ] && source ~/.fzf.zsh
```
---
## Result
* A modern terminal
* Fast navigation
* Higher productivity
+100
View File
@@ -0,0 +1,100 @@
# ⚙️ Web dev stack (Node + pnpm + tooling)
## Goal
Install a modern, reproducible stack.
---
## 1) Install Node via nvm
```bash
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash
source ~/.zshrc
```
Install Node LTS:
```bash
nvm install --lts
nvm alias default 'lts/*'
```
---
## 2) Activate pnpm
```bash
corepack enable
corepack prepare pnpm@latest --activate
pnpm setup
exec zsh
```
---
## 3) Working tree
Create a dev folder:
```bash
mkdir -p ~/dev
cd ~/dev
```
⚠️ Do not work inside `/mnt/c` (slow I/O).
---
## 4) Conventions
* ❌ No global Angular install
* ✅ Use `pnpm dlx`
* ✅ Versions pinned per project
---
## 5) Useful commands
```bash
pnpm install
pnpm run dev
pnpm run build
pnpm run test
```
---
## 6) VS Code
Recommended extensions:
* WSL
* Angular Language Service
* ESLint
* Prettier
* GitLens
---
## 7) Docker
Install Docker Desktop (Windows).
Enable:
* Settings → WSL Integration → Debian
Test:
```bash
docker ps
```
---
## Result
* Stable environment
* Clean version management
* Team-compatible
+116
View File
@@ -0,0 +1,116 @@
# 🏗️ Angular monorepo with Nx + pnpm
> ⚠️ This guide is **pending revision**: the placeholder workspace name (`my-workspace`) and app name (`web`) must be replaced with project-specific values, and the procedure must be extended to cover the Node API / BFF side. Treat the steps below as a baseline only.
## Goal
Create a scalable and maintainable architecture.
---
## 1) Workspace creation
```bash
cd ~/dev
pnpm dlx create-nx-workspace@latest my-workspace \
--preset=angular-monorepo \
--pm=pnpm \
--appName=web \
--style=scss \
--routing=true \
--unitTestRunner=vitest
```
---
## 2) Recommended layout
```text
apps/
web/
libs/
shared/ui/
shared/data-access/
shared/util/
feature/auth/
```
---
## 3) Generate libraries
```bash
pnpm nx g @nx/angular:library shared-ui
pnpm nx g @nx/angular:library feature-auth
```
---
## 4) Main commands
```bash
pnpm nx serve web
pnpm nx build web
pnpm nx test web
pnpm nx lint web
```
---
## 5) Workspace-wide commands
```bash
pnpm nx run-many -t lint test build
pnpm nx affected -t lint test build
```
---
## 6) Prettier
`.prettierrc`:
```json
{
"singleQuote": true,
"semi": true,
"printWidth": 100
}
```
---
## 7) Git hooks
```bash
pnpm add -D husky lint-staged
pnpm exec husky init
```
---
## 8) CI/CD
> ⚠️ The original procedure targeted GitLab CI. The project repository now lives on Gitea, so this section needs to be rewritten for Gitea Actions (or the chosen CI runner). Tracked under the next round of CI ADRs.
---
## 9) Daily workflow
```bash
pnpm install
pnpm start
pnpm nx g component login
pnpm nx affected -t lint test build
```
---
## Result
* Scalable monorepo
* Optimized builds
* Fast CI
* Clear architecture