Remote Eaze

Architecture

System architecture, tech stack, monorepo structure, and core design decisions for the Remote Eaze DFA solution.

Tech Stack

  • Runtime: Node.js v22.15.0+ (ES Modules)
  • Package Manager: pnpm v10.30.2+ (Strict Mode)
  • Orchestrator: Turbo v2.8.10+
  • Backend: Fastify + Zod v4 + Swagger
  • Auth: Better Auth (shared across API, Web, and Tools)
  • Frontend: React 19 + Vite 7 + TanStack (Router, Query, Form, Table, Virtual) + Zustand + Dexie (PWA)
  • Documentation: Fumadocs + Next.js 16
  • Database: PostgreSQL + Prisma 7.4 (@prisma/adapter-pg + pg driver) + tsup
  • Cache/Queue: Redis (ioredis + BullMQ)
  • Tooling: Biome 2 (Lint + Format), Vitest 4 (Testing), tsup (Bundling), Husky + lint-staged (Git hooks)
  • Infrastructure: Docker (Compose), GitHub Actions CI

Repository Structure

The project follows a strict monorepo pattern using pnpm workspaces.

.
├── apps
│   ├── api                 # Fastify backend (consumes packages)
│   ├── web                 # React + Vite + TanStack (PWA client)
│   └── docs                # Fumadocs + Next.js (documentation site)
├── packages
│   ├── config              # Environment variable loading & validation (dotenv + Zod)
│   ├── database            # Prisma 7 schema, client singleton, migrations
│   ├── custom-fields       # Shared "Brain" (rules, formulas, validation) — DB-free
│   ├── shared              # Common types, constants, and date utilities (Luxon)
│   └── ui                  # Design system (Radix UI, HugeIcons, CVA, Tailwind)
├── tools
│   └── scripts             # Seed & maintenance scripts (system-admin, ISO data, cleanup)
├── docker
│   ├── Dockerfile.api      # Multi-stage API build
│   ├── Dockerfile.web      # Vite build + Nginx static serving
│   ├── Dockerfile.tools    # Full monorepo for seeds & scripts
│   └── nginx.conf          # Nginx config for web container
├── curl-test-scripts       # Manual API test scripts (23 shell scripts)
├── .github/workflows
│   └── ci.yml              # PR checks: lint, typecheck, test
├── .husky/pre-commit       # Runs lint-staged on commit
├── biome.json              # Biome config (tabs, double quotes, Tailwind directives)
├── vitest.config.ts        # Root Vitest config (V8 coverage)
├── docker-compose.yml      # Production compose (api, web, migrate, tools)
├── pnpm-workspace.yaml     # Workspace boundaries (apps/*, packages/*, tools)
├── turbo.json              # Build pipeline configuration
├── tsconfig.base.json      # Shared TypeScript configuration
└── .env.example            # Required environment variables template

Core Architectural Decisions

1. Strict Workspace Isolation

We utilize a strict .npmrc configuration to prevent "Phantom Dependencies." Packages cannot access dependencies they do not explicitly declare in their package.json.

Enforcement (.npmrc):

shamefully-hoist=false
strict-peer-dependencies=true
auto-install-peers=true
save-workspace-protocol=rolling
prefer-workspace-packages=true
engine-strict=true
node-linker=isolated

2. Centralized Configuration (packages/config)

Environment variables are not read directly by apps.

  • The config package loads the root .env file using dotenv.
  • It validates all variables using Zod v4 schemas.
  • If the environment is invalid, the application crashes immediately on startup (Fail Fast).

3. The Database Strategy (Prisma 7 + tsup)

We utilize Prisma 7.4 with the @prisma/adapter-pg driver adapter and raw pg for the connection layer.

The Challenge: The prisma-client generator outputs raw TypeScript rather than pre-compiled JavaScript. Node.js cannot run these files directly, and standard tsc compilation struggles with internal Prisma imports lacking .js extensions in an ESM environment.

The Solution: Bundling with tsup

We treat the database package as a bundled library.

  1. Build Phase: tsup compiles src/index.ts and bundles the generated Prisma Client code into a single, valid ESM file located at dist/index.js.
  2. Development Phase: The package.json points "types" to ./src/index.ts. This allows IDEs and tsc to see the live TypeScript source without needing a build step for IntelliSense.
  3. Runtime: The package.json points "main" and "exports" to ./dist/index.js.

Configuration (packages/database/package.json):

{
  "main": "./dist/index.js",
  "types": "./src/index.ts",
  "exports": {
    ".": {
      "types": "./src/index.ts",
      "default": "./dist/index.js"
    }
  },
  "scripts": {
    "build": "tsup src/index.ts --format esm --clean"
  }
}

4. Logic Isolation

To support Offline-First capabilities, business logic is decoupled from data persistence.

  • The Brain (packages/custom-fields): Contains pure functions (Rule Engine, Formula Evaluator, Validators). Depends only on @remote-eaze/shared and Zod — zero database dependencies. It runs in both Node.js and the Browser (iPad).
  • The Muscle (apps/api): Handles Persistence (Prisma), Caching (Redis), and HTTP transport. It consumes "The Brain" to validate data before saving.

5. Feature-Module API Pattern

The API is structured as a collection of independent Fastify Plugins.

  • Structure: Each module (e.g., custom-fields) has its own routes.ts, service.ts, repository.ts, and schemas.ts.
  • Dependency Injection: Services accept dependencies (like Redis) via constructor, ensuring testability.
  • Validation: All I/O is validated using fastify-type-provider-zod.
  • API Docs: Swagger and Swagger UI are wired via @fastify/swagger and @fastify/swagger-ui.

6. Standardized Observability

  • Responses: All API responses follow a strict JSON Envelope ({ data, meta, message }).
  • Errors: A Global Error Handler intercepts all exceptions (Zod, Prisma, AppErrors) and formats them into standard HTTP error responses.
  • Logging: Structured JSON logging via Pino, with request context (requestId).

7. Authentication

Authentication is handled by Better Auth, shared across the API, web client, and tools. The API provides the auth endpoints, and the web client and seed scripts consume the same auth library for session management and user creation.

8. Design System (packages/ui)

The UI package is a full design system built on:

  • Primitives: Radix UI (accordion, dialog, dropdown, select, tabs, tooltip, etc.)
  • Icons: HugeIcons (Lucide is not used in the main app)
  • Styling: Tailwind CSS v4 + CVA (class-variance-authority) + Tailwind Merge
  • Fonts: IBM Plex Mono, IBM Plex Serif, Inter (via Fontsource)
  • Extras: cmdk (command palette), sonner (toasts), react-day-picker, input-otp

The package exports raw TypeScript (main and exports point to src/) — no build step required. The consuming app (apps/web) handles compilation.

Infrastructure

Docker

The project includes a full Docker Compose setup for production deployment:

ServiceDescription
apiFastify backend (multi-stage build, minimal runtime image)
webVite-built static files served by Nginx
migrateRuns prisma migrate deploy then exits
toolsOn-demand container for seeds and maintenance scripts (profile: tools)
docker compose build                                        # Build all images
docker compose up -d                                        # Start api + web
docker compose run --rm migrate                             # Run migrations
docker compose --profile tools run --rm tools pnpm seed:system-admin  # Run seeds

CI/CD

GitHub Actions runs on every PR to master:

  1. Install dependencies (pnpm install --frozen-lockfile)
  2. Generate Prisma client
  3. Generate TanStack Router route tree
  4. Lint (Biome)
  5. Typecheck (tsc)
  6. Test (Vitest)

Code Quality

  • Pre-commit: Husky runs lint-staged which applies biome check --write to staged .ts/.tsx files.
  • Biome config: Tab indentation, double quotes, organized imports, Tailwind CSS directive support.
  • Vitest: Root config with V8 coverage provider, excludes node_modules, dist, test files, and Prisma generated code.

Development Workflow

Prerequisites

  • Node.js v22.15.0+
  • pnpm v10.30.2+
  • PostgreSQL
  • Redis

Setup

# 1. Install dependencies from root directory
pnpm install

# 2. Generate Prisma Client (Required before dev)
pnpm db:generate

# 3. Start Development Server
pnpm dev

Database Management

All database commands should be run from the root, handled by Turbo or pnpm filtering.

# Apply migrations
pnpm db:migrate:dev

# Open Prisma Studio
pnpm db:studio

Never use db:push

db:push causes schema drift. Always use the migration workflow (db:migrate:dev).

Available Scripts

CommandDescription
pnpm devStart all apps in development mode
pnpm buildBuild all packages and apps in topological order
pnpm startRun compiled apps from dist/
pnpm lintRun Biome linting across all packages
pnpm lint:fixAuto-fix lint issues (biome check --write)
pnpm typecheckRun TypeScript type checking
pnpm testRun Vitest across all packages
pnpm test:watchRun Vitest in watch mode
pnpm db:generateGenerate Prisma client
pnpm db:migrate:devCreate/apply database migrations
pnpm db:studioOpen Prisma Studio
pnpm cleanRemove all build artifacts and node_modules
pnpm seed:system-adminSeed the system admin user
pnpm seed:isoSeed ISO reference data (countries, currencies, languages)
pnpm cleanup:test-dataRemove test data from the database

Production Build

We use Turbo to orchestrate the build order. It ensures dependent packages (config, database, custom-fields, shared) are built before the apps. The build task depends on ^build (all upstream packages) and db:generate (Prisma client generation).

# Builds all packages and apps in topological order
pnpm build

# Runs the compiled API from the dist/ folder
pnpm start

On this page