Technology Stack
Transactional email uses Resend's HTTPS POST /emails API through Node 24 native fetch, with a
fixed production destination and no SDK or SMTP client. HTTPS avoids the SMTP port restrictions of
the anticipated hosting class and keeps the adapter small. The application receives only stable
delivery categories. See ADR 003 for provider prerequisites.
Authentication uses maintained Fastify plugins (@fastify/cookie, @fastify/session,
@fastify/csrf-protection, and @fastify/rate-limit). The session store is the project's own
PostgreSQL adapter rather than the plugin memory default. Passwords use the native argon2 package
with Argon2id; the production Node 24 Alpine image is the compatibility target.
1. Scope and version sources
This document records technology choices and their rationale for the implemented system. Architecture belongs in architecture.md, commands in development.md, test policy in test-strategy.md, and progress in current-state.md. Future product ideas are not implementation requirements.
The root package.json requires Node.js 24.x and declares pnpm 10.34.5. GitHub Actions
selects Node major 24; application Docker images use node:24-alpine. These select a major/tag, not
an immutable Node patch or image digest. Workspace manifests declare dependency ranges;
pnpm-lock.yaml records exact resolutions. Use pnpm install --frozen-lockfile to reproduce them.
Do not copy changing package versions into a second authoritative inventory.
2. Implemented application choices
- TypeScript throughout application and database code gives compile-time checking across the workspaces. Runtime inputs still require validation.
- React 19 renders a client-side SPA; there is no SSR requirement or Next.js.
- Vite 8 builds and serves the frontend with
@vitejs/plugin-react. - React Router 7 owns the route tree derived from the typed page declaration.
/,/products,/settings, and the hidden not-found fallback currently exist. - TanStack Query 5 manages API/server state, loading/errors, and one retry.
- Redux Toolkit 2 and React Redux 9 own only shared device preferences: supported language and the future light/dark/system appearance choice. TanStack Query remains the sole server-state cache; drawer state, form drafts, requests, responses, and errors do not belong in Redux. The previously unused Zustand dependency was removed instead of maintaining two client-state tools.
- i18next 26 and react-i18next 17 provide bundled English, Swedish, and Hungarian resources, English fallback, React bindings, and isolated instances. No runtime translation service or CDN is used.
- Material UI 9, MUI Icons 9, and Emotion 11 provide the responsive application shell, accessible controls, centralized light/dark theme, and component styling. Tailwind was removed after the affected shell and page surfaces migrated.
- Fontsource Azeret Mono 5 supplies repository-built webfont assets without a runtime font CDN.
- Fastify 5 serves a small HTTP/JSON API; its built-in Pino integration supplies structured
JSON diagnostics without a second logging library.
@fastify/cors11 supports optional exact-origin allowlisting. HTTP and logger objects stay outside application/domain logic. - Zod 4 validates backend environment variables and JSON form input at the HTTP boundary. Form routes use explicit strict schemas, constraints, and public field/code mappings rather than a generic schema/CRUD framework. The frontend parses typed JSON and normalizes malformed successful JSON, but does not duplicate backend schemas or runtime-validate response bodies with Zod.
- dotenv loads local environment files without overriding supplied variables.
- PostgreSQL 17.5 is the Compose/test-service database, using
postgres:17.5-alpine. - Drizzle ORM with postgres.js owns database queries in
packages/dband the backend repository implementation. drizzle-kit generates and applies committed SQL migrations; tsx runs development and database tooling.
3. Repository and architectural boundaries
The pnpm workspaces are:
| Directory | Package |
|---|---|
frontend/ |
@foodtruck-order/frontend |
backend/ |
@foodtruck-order/backend |
packages/db/ |
@foodtruck-order/db |
tests/e2e/ |
@foodtruck-order/e2e |
The repository uses these directories, not an apps/web, apps/api, or infra/ layout. docker/
contains the local gateway; .github/workflows/ contains CI and publishing. There is no shared
frontend/backend domain package, Nx, or Turborepo. The backend depends on @foodtruck-order/db;
frontend types mirror the public API without importing backend implementation code.
4. Testing and static quality tools
- Vitest 4 runs frontend/backend tests. React Testing Library, jest-dom, and jsdom verify observable component behavior. Backend tests run in Node.
@vitest/coverage-v8enforces the configured frontend/backend 100% coverage policy. Scope, exclusions, and aggregate responsibilities belong in the testing strategy, not a parallel coverage policy here.- Playwright drives Chromium for real browser workflows. The browser binary is installed separately. The same specs target dev-mode servers or Docker.
- Node's built-in test runner verifies the CI artifact contract in
tests/ci/. - Prettier 3 formats covered repository files. ESLint 9 uses typescript-eslint strict type-aware rules, React hooks, JSX a11y, import-x, and unused-imports. Import hygiene does not enforce all architectural boundaries.
The canonical static gate is pnpm quality; it includes documentation checks. The authoritative DoD
enforcement mapping is CI §14.
5. Containers and CI
Docker Compose supplies the canonical local application environment. Backend images use Node;
frontend and gateway images use nginx:1.29-alpine. The gateway serves the page and /api/* on the
same browser origin. Database tooling runs in separate migration/seed containers. The local gateway
uses HTTP, not HTTPS/Caddy.
GitHub Actions runs the mandatory quality, tests, builds, docs, Docker, coverage, supply-chain, and artifact checks. The staging migration gate and existing hosting assumptions are documented in CI §13. The repository does not publish application Docker images to a registry.
Supply-chain tooling is pinned and local-first: pnpm audit (lockfile; high/critical blocking with
narrow expiring entries in supply-chain/audit-exceptions.json), Gitleaks v8.18.4
(zricethezav/gitleaks:v8.18.4), Hadolint v2.12.0 (hadolint/hadolint:v2.12.0), Trivy 0.66.0
(aquasec/trivy:0.66.0) for weekly diagnostics only, and the project's own drizzle-kit migration
generator. Scanner wrappers live in scripts/ with fixture tests under tests/ci/; no secrets are
required and secret findings stay redacted.
When a transitive dependency range has not yet adopted a patched compatible release, keep the
temporary resolver override in the root package.json and remove it after upstream ranges include
the fix. The blocking audit remains the source of truth for whether the override is still needed.
6. Documentation tools
OpenAPI 3.1 is the version-controlled HTTP contract in docs/api/openapi.yaml. Redocly
validates it, Swagger UI renders it, and TypeDoc generates the intentional public TypeScript
reference. A small Node script with marked renders Markdown; there is no separate documentation
framework. Marked also supplies the Markdown token/heading model for offline local-link checks;
Cheerio 1.2.0 parses generated HTML for site links and fragments. CSpell 9.6.1, with pinned
Swedish 2.3.2 and Hungarian 1.1.2 dictionaries, checks scoped authored prose, comments, and UI copy.
ESLint's maintained no-restricted-imports/no-restricted-globals rules enforce the repository's
narrow architectural boundary baseline without a second dependency parser.
The complete site is generated into ignored docs-site/. Cloudflare Pages hosts a Direct Upload
project; a separate Actions workflow invokes the pinned wrangler 4.131.1 with pnpm dlx. GitHub
hosts source and executes builds; Cloudflare does not clone or build the repository. GitHub Pages is
not used. Tool boundaries and documentation coverage live in documentation.md.
7. Deferred choices and product direction
The implemented application contains a database-backed message, authentication/account recovery, the protected product catalog, and atomic non-commercial order finalization with immutable item snapshots. Product deletion removes a product from the current catalog without changing finalized snapshots. Commercial checkout finalization, order history presentation, payment/POS integrations, real-time updates, backups, and production infrastructure are not implemented. Do not infer them from later MVP specifications or historical plans.
If later specifications require them, the existing direction is:
- Use Surfboard for payment processing and its Swedish ECR service for fiscal cash-register processing, behind provider-neutral application ports. Local/test keep the deterministic fake, staging targets Surfboard Demo, and a future production deployment supplies independent Live configuration only after certification and operational prerequisites are complete. The evidence-backed discovery result and the port, configuration, and secret designs are recorded in ADR 004.
- Keep operational order and payment state distinct; enforce business rules on the backend and use transactions/idempotency when required.
- Select the simplest real-time mechanism that meets the requirement. SSE and WebSockets are candidates, not current dependencies or implemented behavior.
- Consider Caddy/HTTPS, production Compose, backups/restore, monitoring, and rollback as separately specified production work. Render/Neon staging is described by its existing release contract, not by a future VPS diagram.
- Avoid Redis, Kubernetes, Kafka, microservices, GraphQL, or complex offline synchronization without a demonstrated need. No such infrastructure exists.
Future scope and acceptance criteria must be established in specifications before implementation. The developer log preserves informal proposals without changing these technology decisions.