Files
tessera-ctl/.planning/research/STACK.md
T
schalli d2ac1997d6 docs: complete project research
Ausschreibungs-Radar (v1.1) STACK/FEATURES/ARCHITECTURE/PITFALLS research plus SUMMARY.md synthesis.
2026-07-17 10:12:59 +02:00

29 KiB
Raw Blame History

Technology Stack

Project: Tessera — Modular Portal Platform with Marketplace Latest research: 2026-07-17 (v1.1 Ausschreibungs-Radar delta) — base stack researched 2026-06-18 Overall Confidence: MEDIUM for the v1.1 delta below (versions cross-verified via WebSearch + direct npm registry lookup; no Context7/docs-MCP available this session), HIGH for the v1.0 base stack (unchanged, see bottom section)


v1.1 Delta — Ausschreibungs-Radar Module

Domain: German public-procurement (Vergabe) tender aggregation — API client + HTML scraping adapters + RSS + XML/JSON normalization, on top of the existing NestJS/Prisma monorepo and the DKV module's inbox/mail/scheduler infrastructure.

This section is a delta only. It assumes the validated v1.0 stack below (pnpm/Turborepo, NestJS 11, Prisma 7/PostgreSQL 16, Next.js 16, Vitest) and covers only new libraries needed for this module. No new apps/web dependencies are required — the results UI (searchable list, filters, detail view) is built entirely with the existing Next.js/shadcn/TanStack Query/Zustand stack already in place.

Core Technologies (new)

Technology Version Purpose Why Recommended
fast-xml-parser 5.10.1 Parse eForms-DE XML (DÖE) + RSS 2.0 feeds (subreport-elvis, service.bund.de) Zero-dependency, pure-TS, handles both jobs with one library (XMLParser/XMLBuilder/XMLValidator). Used by Microsoft, NASA, VMware. Faster and simpler than xml2js (callback/SAX-based, heavier). One parser covers eForms XML and RSS XML — see "What NOT to Add" for why a dedicated rss-parser is skipped.
cheerio 1.2.0 HTML parsing/traversal for the AI AG NetServer and cosinex Vergabemarktplatz scraping adapters De-facto jQuery-style static HTML parser for Node — ~21k dependents, actively maintained (last release 2026-01-23). Wraps parse5/htmlparser2. 10–50× faster and far cheaper than a headless browser for server-rendered HTML, which both target portals produce for their public search-result pages.
csv-parse 7.0.1 Parse the DÖE OpenData CSV export as a fallback/cross-check format stream.Transform-based, part of the adaltas/node-csv toolkit, ~3100 dependents, actively released (14 days old at research time). Ships dual CJS/ESM — safe for apps/api's CommonJS build. Primary DÖE ingestion should use eForms-XML or OCDS-JSON (richer, structured); CSV is secondary/verification.
tough-cookie + fetch-cookie 6.0.2 / 3.2.0 Cookie-jar session handling for the scraping adapters (search-form POST → paginated results) apps/api already standardizes on native fetch (see favorites/icon-discovery.service.ts, calendar/providers/ics.provider.ts) — no axios anywhere in the app. fetch-cookie wraps global fetch with a tough-cookie CookieJar so session cookies (e.g. JSESSIONID on the AI AG servlet portal) persist across a scrape run without introducing a second HTTP client family.
playwright 1.61.1 (conditional — see note) Headless-browser fallback adapter, only if a specific portal's search UI turns out to require client-side JS rendering AI AG NetServer's PublicationControllerServlet URL pattern is a classic Java servlet MVC front-controller (JSP/form-based) — server-rendered HTML with a session cookie, not an SPA, and (unlike ASP.NET WebForms) has no ViewState/EventValidation mechanic to fight. cosinex markets Vergabemarktplatz as "fully browser-based" (marketing language, not confirmed SPA), making it the more likely candidate to need JS rendering. Build both adapters cheerio-first; only pull in playwright for whichever adapter's public search page is confirmed (phase-1 spike) to require JS execution. Don't add it speculatively — it downloads browser binaries, growing the Docker image ~300MB+, which the "wartbar/verständlich" constraint argues against unless proven necessary.

Supporting / Dev Tools (optional)

Tool Purpose Notes
undici (devDependency only) Mock outbound fetch calls in Vitest tests for the DÖE client and scraping adapters (MockAgent + setGlobalDispatcher) Node's global fetch is already powered by undici under the hood; adding it as a devDependency gives official, zero-extra-runtime-cost HTTP mocking without introducing nock/msw (neither currently used anywhere in the repo). Add only when writing the adapter test suite.

Installation

# apps/api — core additions
pnpm --filter @tessera/api add fast-xml-parser cheerio csv-parse tough-cookie fetch-cookie

# apps/api — conditional, only if phase-1 spike confirms a JS-rendered portal
pnpm --filter @tessera/api add playwright
pnpm --filter @tessera/api exec playwright install chromium   # only the one engine needed

# apps/api — dev/test only, optional
pnpm --filter @tessera/api add -D undici

What NOT to Add — Reuse Existing Instead

The DKV module already solved most of the infrastructure this feature needs. This is the most important section for scope control.

Don't add Why not Reuse instead
A dedicated DÖE / eForms / OCDS SDK None exists as a maintained npm package for the DÖE OpenData API specifically. It's a plain REST/Swagger endpoint returning XML/JSON/CSV — a heavy SDK would be unmaintained-dependency risk for zero benefit. Native fetch (already the established pattern) + fast-xml-parser for eForms-XML + plain JSON.parse for OCDS (it's just JSON, no schema library needed on day 1) + csv-parse for the CSV fallback.
rss-parser Latest release is 3.13.0 from 2023-04-11 — 3+ years stale (no security concern since RSS 2.0 is a frozen spec, but it's an extra dependency for a job fast-xml-parser already does). Only 2 feeds to consume (subreport-elvis, service.bund.de), both plain RSS 2.0 — a ~20-line field mapper (title/link/pubDate/guid/description) over fast-xml-parser's output is simpler than a second library's API. fast-xml-parser (already added for eForms) + a small internal parseRssFeed() helper.
axios / axios-cookiejar-support Would introduce a second HTTP client family. apps/api has zero axios usage today — both existing HTTP call sites use native fetch. Native fetch + fetch-cookie (wraps fetch, not axios).
node-fetch Obsolete since Node 18 ships fetch natively (powered by undici); only relevant for Node ≤16 or legacy stream-handling quirks, neither applies here. Native global fetch.
p-queue (v7+) ESM-only — apps/api's tsconfig.json is "module": "commonjs" (confirmed). A native-ESM package in a CJS NestJS build means either a dynamic import() workaround (the codebase already has one messy precedent for cron in dkv-scheduler.service.ts) or build breakage. Overkill anyway: the module polls a handful of sources (DÖE, 1 AI-AG adapter, 1 cosinex adapter, 2 RSS feeds, 1 inbox) on independent cron ticks — no need for a promise-concurrency-queue library. A ~20-line internal politeDelay(ms) / sequential-for-loop helper between requests within one adapter run. Simpler, CJS-native, matches the "wartbar für Nicht-Programmierer" constraint.
bottleneck Latest release 2.19.5 is from 2019 — unmaintained for 7 years. A community fork (@rutter/bottleneck) exists but adds a supply-chain trust question for zero real benefit given the low concurrency needs above. Same internal delay helper as above.
A separate portal-login-credential store for scraping The feasibility research's recommended tactic explicitly avoids automating authenticated scraping: for portals 2,3,4,6,7,8,9,10 (all except vergabe24/aumass, avoided entirely per their ToS), the admin registers one saved search per portal manually, and Tessera ingests the resulting alert emails — not the authenticated portal UI. Only the public, unauthenticated search-result pages (AI AG NetServer, cosinex DTVP) are scraped. Reuse the existing tenant-scoped encrypted-credential pattern (CalendarCryptoService, AES-256-GCM, via SettingsModule) only for the inbox connection used to ingest alert emails — same shape of secret the DKV module already stores, not a new credential type.
@nestjs-modules/mailer for digest/alert sending Already an established pitfall in this codebase: it cannot change its SMTP transport after startup, so a runtime SMTP-config change in the admin UI wouldn't take effect without a service restart (documented in dkv-mail.service.ts). Copy the DkvMailService pattern: a dedicated AusschreibungMailService that calls nodemailer.createTransport() fresh on every send, reading SettingsService.getDecryptedSmtpConfig(tenantId) each time.
A second cron/scheduling mechanism @nestjs/schedule + SchedulerRegistry.addCronJob() (dynamic, runtime-updatable) is already wired into AppModule and proven in DkvSchedulerService. Reuse the same dynamic-cron pattern — one job per source (DÖE poll, AI-AG adapter poll, cosinex adapter poll, 2× RSS poll, inbox alert poll), each independently configurable in minutes via the admin UI, same as DKV's pollIntervalMin.
A new IMAP/EWS client ImapProvider and ExchangeInboxProvider (both implementing InboxProvider) already handle IMAP (imapflow) and Exchange/EWS (raw SOAP over httpntlm, NTLM auth) inbox polling, including attachment size limits (PDF-bomb mitigation) and credential-safe logging. Reuse InboxProvider/ImapProvider/ExchangeInboxProvider directly for the Unterschwellen alert-email ingestion. Note: the current attachment filter is PDF-specific (collectPdfParts/looksLikePdf); the new module needs the email body/links, not attachments, for most portal alert mails (search-agent notifications are usually HTML emails linking to the tender, occasionally with a PDF). This means extending InboxProvider with a body-text/HTML-fetching capability, or adding a sibling interface — a phase-plan design decision, not a new dependency.

Alternatives Considered

Recommended Alternative When to Use Alternative
fetch + cheerio (adapter-first) playwright (adapter-first) If the phase-1 spike shows a target portal's public search results are rendered/paginated via client-side JS (React/Angular) rather than server HTML — confirm per-portal before committing, don't assume both need it.
fetch-cookie + tough-cookie got (built-in cookie support via got.extend({ cookieJar })) If the adapters later need retry/backoff/HTTP2 features beyond what a thin fetch wrapper offers — got bundles those, but it's another HTTP client family; only justified if manual retry logic (already needed per DKV's D-16 exponential-backoff pattern) becomes unwieldy.
fast-xml-parser for both eForms-XML and RSS xml2js Only if a specific eForms XML document needs xml2js's SAX-based tolerance for malformed/legacy XML — unlikely for a DÖE-generated, schema-validated eForms-DE feed.
No dedicated OCDS validation library ajv + the published OCDS JSON Schema If/when the module needs to strictly validate incoming OCDS JSON against the official schema (e.g. to catch DÖE-side data quality issues) rather than just mapping known fields defensively. Defer until proven necessary — DÖE's OCDS output is itself schema-validated upstream.
Internal politeDelay() helper p-queue (dynamic import()) or bottleneck (unmaintained fork) Only if the module later needs true concurrent multi-portal fan-out with a shared global rate budget (e.g. dozens of AI-AG-family portals at once, per the feasibility doc's "deckt viele weitere AI-Portale" note) — at that scale a real queue library becomes worth the complexity. Not needed for the 2-adapter v1.1 scope.

Version Compatibility

Package A Compatible With Notes
fast-xml-parser@5.x Node 18+ (project already on Node 22-class runtime per @types/node@^22) Pure TS/JS, no native bindings — no compatibility risk.
cheerio@1.2.x Node ≥18.17 Matches project's Node baseline.
fetch-cookie@3.x Native global fetch (Node 18+) or any WHATWG-fetch-compatible function Wraps whatever fetch implementation is passed in — works with Node's built-in fetch (undici) with zero extra config.
tough-cookie@6.x fetch-cookie@3.x fetch-cookie accepts any tough-cookie-compatible jar per its own docs; pin both current majors together.
playwright@1.61.x (if added) Requires downloading Chromium binary at install time (playwright install chromium) Docker implication: the apps/api production image must run playwright install --with-deps chromium in its build stage — another reason to add it only if actually needed, not speculatively.
csv-parse@7.x Node 18+, ESM and CJS builds published Unlike p-queue/bottleneck, csv-parse ships dual CJS/ESM — safe for the CommonJS apps/api build.

Sources (v1.1 delta)

  • npm registry (registry.npmjs.org) — direct authoritative version/publish-date lookup for cheerio, fast-xml-parser, csv-parse, rss-parser, playwright, p-queue, tough-cookie, fetch-cookie, bottleneck — fetched 2026-07-17. Confirms: cheerio 1.2.0 (2026-01-23), fast-xml-parser 5.10.1 (2026-07-16), csv-parse 7.0.1 (2026-07-02), rss-parser 3.13.0 (2023-04-11, stale), playwright 1.61.1 (2026-06-23), p-queue 9.3.1 (2026-07-03, ESM-only since v7), tough-cookie 6.0.2 (2026-07-07), fetch-cookie 3.2.0 (2025-12-15), bottleneck 2.19.5 (2019-08-03, unmaintained).
  • WebSearch (MEDIUM confidence, cross-checked against npm registry above where version-critical) — cheerio/fast-xml-parser/rss-parser/csv-parse/playwright/p-queue ecosystem status; undici-vs-node-fetch guidance; tough-cookie/fetch-cookie/axios-cookiejar-support session-handling patterns; Playwright-vs-cheerio scraping tradeoff for stateful ASP.NET-style vs static HTML portals; OCDS-for-eForms mapping (no dedicated npm library found — confirmed via standard.open-contracting.org documentation search, not a package).
  • https://www.oeffentlichevergabe.de/documentation/swagger-ui/opendata/ and bescha.bund.de DÖE pages — confirms eForms-DE/OCDS/CSV export formats, no-auth access.
  • .planning/research/ausschreibungs-portale-feasibility.md (this repo, 2026-07-16) — portal platform identification (AI AG NetServer = Java ControllerServlet, cosinex = separate incompatible HTML), which portals to scrape vs. email-alert-ingest vs. avoid entirely.
  • Codebase inspection (apps/api/src/dkv/, apps/api/src/settings/, apps/api/tsconfig.json, apps/api/package.json) — confirmed: CommonJS build target (rules out ESM-only libs), native fetch already the established HTTP client (rules out adding axios), existing InboxProvider/ImapProvider/ExchangeInboxProvider/DkvMailService/DkvSchedulerService/CalendarCryptoService patterns to reuse verbatim.

Gap / low-confidence area: No Context7 or other docs-MCP server was available in this session (.mcp.json only configures playwright for browser automation, not doc lookup) — all version numbers here come from WebSearch cross-checked directly against registry.npmjs.org, which is authoritative for version/publish-date facts but not for qualitative maintenance-health claims. This repo's automated confidence classifier flagged fast-xml-parser, csv-parse, playwright, p-queue, and tough-cookie as SUS — manual review indicates this is a false positive tripped by recent-publish velocity, not an actual supply-chain concern: all five are widely-adopted, well-known-maintainer packages (Playwright is Microsoft's own project). Recommend a final npm view <pkg> / Socket.dev spot-check immediately before pnpm add in the implementation phase, per this repo's existing dependency-hygiene bar.


v1.0 Base Stack (reference — unchanged)

Researched: 2026-06-18 Overall Confidence: HIGH

Monorepo & Package Management

Technology Version Purpose Why
pnpm 9.x Package manager 60-80% disk reduction via content-addressable store, strict dependency isolation prevents phantom deps, workspace protocol for internal packages
Turborepo 2.9.x Build orchestration Incremental builds, parallel execution, task dependency graph, caching. Vercel-maintained — aligns with Next.js ecosystem

Monorepo Structure:

tessera/
  apps/
    web/          # Next.js 16 frontend
    api/          # NestJS backend
    desktop/      # Tauri desktop wrapper
  packages/
    shared/       # Shared types, DTOs, constants
    ui/           # Shared UI components (shadcn-based)
    config/       # ESLint, TypeScript, Tailwind configs
    db/           # Prisma schema & client

Why Turborepo over Nx: Turborepo is simpler, zero-config for most cases, and aligns with Vercel/Next.js tooling. Nx is more powerful for massive enterprise repos but adds unnecessary complexity for a team building with Claude.

Frontend

Technology Version Purpose Why
Next.js 16.2.x Frontend framework App Router with React 19, Turbopack default bundler (4x faster builds), stable Server Components, use cache directive, multi-tenant middleware support
React 19.x UI library Ships with Next.js 16, React Compiler (automatic memoization), stable Server Actions
TypeScript 5.5+ Type safety Required by all major tools, catches bugs at compile time
Tailwind CSS 4.3.x Styling CSS-first config (no JS config file), 3.5x faster rebuilds, OKLCH colors, built-in dark mode via .dark selector
shadcn/ui CLI v4 Component library Not a dependency — copies components into your project. Accessible, themeable via CSS variables, dark/light mode trivial with next-themes. Dashboard-ready components
next-themes 0.4.x Theme switching 2-line dark mode integration with shadcn/ui, SSR-safe, system preference detection
next-intl 4.13.x Internationalization Built for Next.js App Router, Server Component native, 457 bytes gzipped, simpler than i18next for Next.js-only projects
react-grid-layout 2.2.x Dashboard grid TypeScript rewrite with hooks API (useGridLayout, useResponsiveLayout), drag & drop + resize, responsive breakpoints, 100% backward compat via /legacy
Zustand 5.0.x Client state Lightweight (1.1kb), no providers needed, works with Server Components, perfect for UI state (sidebar toggle, theme, user prefs)
TanStack Query 5.101.x Server state Caching, background refresh, optimistic updates, pagination. Use for client-side data fetching where Server Components don't suffice

Backend

Technology Version Purpose Why
NestJS 11.x API framework Modular architecture maps directly to Tessera's module system. Dependency injection, guards, interceptors, built-in microservices support. Modular monolith now, extract to microservices later if needed
Express 5.x HTTP server Default in NestJS 11, battle-tested, massive middleware ecosystem
Prisma 7.8.x ORM TypeScript-first, auto-generated types from schema, declarative migrations, Client Extensions for RLS multi-tenancy. v7 dropped Rust engine — 3x faster queries, 90% smaller bundles
PostgreSQL 16.x Database Row-Level Security for multi-tenancy, JSONB for flexible module config, excellent Docker support, robust at any scale
Redis 7.x Cache & sessions Session storage, pub/sub for real-time features, rate limiting, cache invalidation

Authentication & Authorization

Technology Version Purpose Why
Keycloak 26.6.x Identity provider Native multi-tenancy via realms, LDAP/AD federation built-in, OAuth2/OIDC standard, Docker-native, admin UI included. Eliminates building auth from scratch
nest-keycloak-connect latest NestJS integration Guards, decorators, multi-tenant realm resolvers — handles JWT validation and role extraction
@nestjs/passport latest Fallback auth For API key auth on module-to-module communication

Why Keycloak over custom auth: The project requires LDAP integration, multi-tenancy, admin user management, and token-based auth. Building this from scratch would take weeks and introduce security vulnerabilities. Keycloak provides all of this as a Docker container with zero custom code.

Note (v1.1): Live implementation diverged from this section — see apps/api/src/settings and the LDAP work already shipped directly against ldapts rather than via Keycloak federation. This base-stack section is kept as originally researched; treat the "Authentication & Authorization" row above as historical context, not current fact, when planning new auth-adjacent work.

Desktop Wrapper

Technology Version Purpose Why
Tauri 2.x (2.11+) Desktop app 5MB installer vs Electron's 150MB. Uses OS-native WebView — no bundled Chromium. Rust backend for system APIs. Windows + Linux supported. Perfect for wrapping an existing web app

Why Tauri over Electron: Tessera's desktop client is a thin wrapper around the web portal — it does not need Node.js APIs, multi-window workflows, or pixel-perfect cross-platform rendering. Tauri's 96% smaller bundle size, 30-50MB memory usage (vs 150-300MB), and native performance make it the clear choice for a web-app wrapper. The user is not a programmer, so less moving parts (no Node.js backend process) is better.

Trade-off acknowledged: Tauri uses the OS WebView (WebView2 on Windows, webkit2gtk on Linux), so minor rendering differences may exist. For an internal tool this is acceptable.

Infrastructure & DevOps

Technology Version Purpose Why
Docker 27.x Containerization Project constraint. Multi-stage builds for production images
Docker Compose 2.x Orchestration Multi-service local dev and production deployment. Health checks, volume management, networking
Traefik 3.x Reverse proxy Automatic SSL, Docker-native service discovery, routing rules via labels. Simpler than nginx for Docker-compose setups
Gitea existing Version control Already in place. Automate via webhooks and Gitea API

Note (v1.1): Reverse proxy in production is Nginx Proxy Manager (NPM), external to the Tessera Docker stack — not Traefik. Tessera containers do not include a reverse proxy; NPM handles SSL termination and routing on the host. Treat the "Traefik" row above as historical/superseded.

Testing

Technology Version Purpose Why
Vitest 3.x Unit/integration tests Fast, ESM-native, compatible with Jest API, works with both Next.js and NestJS
Playwright 1.x E2E tests Cross-browser, auto-wait, trace viewer. Best for testing the full portal flow
Testing Library latest Component tests DOM-based testing, framework-agnostic patterns

Developer Experience

Technology Version Purpose Why
Biome 2.x Linting + formatting Single tool replaces ESLint + Prettier, 100x faster, zero-config defaults. Reduces tooling complexity
Husky 9.x Git hooks Pre-commit formatting/linting enforcement
lint-staged 15.x Staged file linting Only lint changed files for fast commits

Alternatives Considered

Category Recommended Alternative Why Not
Frontend Framework Next.js 16 Remix / SvelteKit Next.js has best multi-tenant support, largest ecosystem, Vercel Platforms template as reference
Backend Framework NestJS 11 Fastify standalone / Express NestJS module system maps perfectly to Tessera modules. DI, guards, and interceptors reduce boilerplate
ORM Prisma 7 Drizzle ORM Prisma has gentler learning curve (built by Claude, non-programmer user), better migration safety. Drizzle is faster in serverless (irrelevant here — Docker deployment)
Database PostgreSQL 16 MariaDB PostgreSQL has native RLS for multi-tenancy, superior JSONB support, better extension ecosystem
Auth Keycloak Custom JWT + LDAP lib Building LDAP + multi-tenant auth from scratch is weeks of work and a security risk
Desktop Tauri 2 Electron 96% smaller, 5x less memory. Tessera desktop is a wrapper, not a full desktop app
State Zustand Redux Toolkit Zustand is 7x smaller bundle, no boilerplate, no providers. Redux is overkill for this project
i18n next-intl react-i18next next-intl is built for App Router, Server Component native, simpler API
CSS Tailwind 4 CSS Modules / Styled Components Utility-first is faster to develop, dark mode trivial, shadcn/ui requires it
Package Manager pnpm npm / yarn pnpm has strict isolation, better disk usage, workspace support built-in
Monorepo Tool Turborepo Nx Turborepo is simpler, aligns with Vercel ecosystem, sufficient for this project size
Linting Biome ESLint + Prettier Single tool, 100x faster, less config. ESLint is being replaced in NestJS 12 roadmap anyway
Component Library shadcn/ui Material UI / Ant Design shadcn gives ownership of components (no dep lock-in), built on Radix primitives, Tailwind-native
Dashboard Grid react-grid-layout Gridstack.js React-native, TypeScript rewrite in v2, hooks API, responsive breakpoints
Reverse Proxy Traefik nginx Docker-native service discovery, auto-SSL, config via labels not files — superseded in practice by external Nginx Proxy Manager, see note above

Multi-Tenancy Strategy

Approach: Shared schema with Row-Level Security (RLS) in PostgreSQL.

Why RLS over schema-per-tenant:

  • Scales to thousands of tenants without catalog bloat
  • Single connection pool (no per-tenant connection overhead)
  • Prisma Client Extensions support RLS via session variables
  • Simpler migrations — one schema to manage
  • Cost-effective for the planned growth trajectory

Implementation:

  1. Every tenant table has a tenant_id column
  2. RLS policies enforce WHERE tenant_id = current_setting('app.tenant_id')
  3. NestJS middleware sets the tenant context on every request
  4. Prisma Client Extension wraps queries with SET app.tenant_id
  5. Keycloak realm maps to tenant, JWT contains tenant_id claim

Installation

# Initialize monorepo
pnpm create turbo@latest tessera --example basic

# Frontend (apps/web)
pnpm add next@16 react@19 react-dom@19
pnpm add next-intl@4 next-themes zustand@5 @tanstack/react-query@5
pnpm add react-grid-layout@2
pnpm add -D tailwindcss@4 @tailwindcss/postcss typescript @types/react

# Backend (apps/api)
pnpm add @nestjs/core@11 @nestjs/common@11 @nestjs/platform-express@11
pnpm add @nestjs/config @nestjs/jwt @nestjs/passport
pnpm add nest-keycloak-connect
pnpm add @prisma/client@7 ioredis
pnpm add -D prisma@7 @nestjs/cli@11

# Desktop (apps/desktop)
# Tauri CLI installed via cargo or npm
pnpm add -D @tauri-apps/cli@2

# Shared (packages/shared)
# Types, DTOs, and constants — no runtime deps

# Dev tools (root)
pnpm add -D turbo@2 @biomejs/biome@2 husky@9 lint-staged@15
pnpm add -D vitest@3 @playwright/test

Docker Services (docker-compose.yml)

services:
  web:        # Next.js frontend (port 3000)
  api:        # NestJS backend (port 3001)
  db:         # PostgreSQL 16 (port 5432)
  redis:      # Redis 7 (port 6379)
  keycloak:   # Keycloak 26.6.x (port 8080)
  traefik:    # Reverse proxy (port 80/443) — superseded by external NPM, see note above

Version Pinning Strategy

  • Major versions: Pin to major (e.g., next@16, @nestjs/core@11)
  • Prisma: Pin exactly — schema changes require matched versions
  • Tailwind/shadcn: Follow latest within major — utility additions are non-breaking
  • Keycloak Docker image: Pin to minor (e.g., quay.io/keycloak/keycloak:26.6)

Sources (v1.0 base)