Files
familysync/docs/DEVELOPMENT.md
T
Lucas Berger 982438dc10 style(13-03): apply Prettier formatting across repo
Mechanical reformat — no logic changes. 398 files changed, 19125
insertions(+), 16457 deletions(-). Prettier 3.8.4 with .prettierrc
(singleQuote:true, semi:true, tabWidth:2, trailingComma:all,
printWidth:100). Isolated per D-13-08 for reviewability.
2026-06-11 20:35:18 -04:00

9.6 KiB

Development Guide

Local development setup and workflows for FamilySync — a pnpm monorepo with two workspaces: apps/api (Hono + Node.js) and apps/pwa (React + Vite).

Repo Layout

familysync/
├── apps/
│   ├── api/          # Hono API server — Node.js 22, TypeScript, Drizzle/MariaDB
│   └── pwa/          # React 19 PWA — Vite, TanStack Query, Schedule-X
├── docker-compose.yml
├── docker-compose.dev.yml
├── package.json      # Root workspace scripts
└── pnpm-workspace.yaml

Key paths inside apps/api/src/:

src/
├── db/
│   ├── schema.ts       # Drizzle table definitions (source of truth for migrations)
│   ├── client.ts       # mysql2 pool + drizzle instance
│   └── migrations/     # Generated SQL migration files
├── routes/             # Hono route files (events, lists, push, sse, me, health)
├── auth/               # OIDC middleware
├── broker/             # CalDAV broker (tsdav + ical.js)
└── lib/                # Shared utilities

Prerequisites

  • Node.js 22 LTS — the Dockerfile base is node:22-alpine; match this locally
  • pnpm 11.5.1 — managed via corepack (corepack enable pnpm)
  • Docker + Docker Compose — for MariaDB and Redis in dev
  • TypeScript 5.x — installed per-workspace as a dev dependency

Local Setup

1. Install dependencies

pnpm install

This installs all workspace packages (apps/api and apps/pwa) in a single pass.

2. Start the dev database and Redis

docker compose -f docker-compose.yml -f docker-compose.dev.yml up mariadb redis -d

The dev override (docker-compose.dev.yml) exposes MariaDB on localhost:3306 and Redis on localhost:6379.

3. Configure environment variables

Copy the root .env.example to .env and fill in the required values. The root .env is sourced by docker-compose for container env vars. When running the API server directly on the host (outside Docker), you must override DB_HOST:

# Source the root .env, then override DB_HOST for host-side execution
DB_HOST=127.0.0.1 node dist/index.js

Or set DB_HOST=127.0.0.1 in your local .env for the dev workflow. The docker-compose production config sets DB_HOST: mariadb (the service name); that value does not resolve on the host.

For auth bypass during local UI development, set DEV_AUTH_BYPASS=true — this skips OIDC and authenticates as the dev user (id 1).

Development Workflow

API dev loop

The dev script runs the compiled output via node --watch. A build must exist before starting the dev server — the watcher restarts dist/index.js on file changes, but it does not recompile TypeScript. You must rebuild when source changes.

# From apps/api — or use the root shortcut
pnpm --filter @familysync/api build   # compile src/ → dist/
pnpm --filter @familysync/api dev     # node --watch dist/index.js

# Root shortcuts
pnpm dev:api   # runs dev in apps/api (requires dist/ to already exist)

Recommended inner loop: run pnpm --filter @familysync/api build after each change, the --watch process restarts automatically.

PWA dev server

pnpm --filter @familysync/pwa dev
# or from root:
pnpm dev:pwa

Vite serves the PWA with HMR on the configured dev port. The PWA's API calls target the backend; set VITE_API_URL (or the Vite proxy config) to point at the running API.

Build Commands

Root workspace scripts

Command Description
pnpm dev:api Start API dev watcher (node --watch dist/index.js)
pnpm dev:pwa Start Vite dev server for the PWA
pnpm build Build both apps/api (tsc) and apps/pwa (tsc + vite build)
pnpm test Run API test suite (vitest run in apps/api)
pnpm lint Run lint in all workspaces (pnpm -r lint)
pnpm typecheck Run tsc --noEmit in all workspaces

apps/api scripts

Command Description
pnpm --filter @familysync/api build Compile TypeScript (tsc) → dist/
pnpm --filter @familysync/api dev Start node --watch dist/index.js
pnpm --filter @familysync/api start Start node dist/index.js (no watch)
pnpm --filter @familysync/api test Run vitest once (vitest run)
pnpm --filter @familysync/api test:watch Run vitest in watch mode
pnpm --filter @familysync/api typecheck tsc --noEmit
pnpm --filter @familysync/api db:generate Generate SQL migrations from schema changes
pnpm --filter @familysync/api db:migrate Apply pending migrations to the database

apps/pwa scripts

Command Description
pnpm --filter @familysync/pwa dev Start Vite dev server with HMR
pnpm --filter @familysync/pwa build tsc && vite builddist/
pnpm --filter @familysync/pwa preview Serve the production build locally
pnpm --filter @familysync/pwa typecheck tsc --noEmit
pnpm --filter @familysync/pwa test Run vitest once

Drizzle Migration Workflow

Schema changes follow a strict two-step process. drizzle-kit push is not available — it has been removed from the scripts because it emits a false destructive diff (table truncation) on populated MariaDB databases.

Step 1 — Generate the migration

After editing apps/api/src/db/schema.ts:

pnpm --filter @familysync/api db:generate

This runs drizzle-kit generate and writes a new .sql file to apps/api/src/db/migrations/. Review the generated SQL before proceeding — confirm it matches the intended schema change with no unexpected DROP or truncation statements.

Step 2 — Apply the migration

pnpm --filter @familysync/api db:migrate

This runs drizzle-kit migrate and applies any pending migration files to the target database. Drizzle reads DB_HOST, DB_USER, DB_PASSWORD, DB_NAME, and optionally DB_PORT (default 3306) from the environment, as defined in apps/api/drizzle.config.ts.

When running migrations from the host against the Docker database:

DB_HOST=127.0.0.1 pnpm --filter @familysync/api db:migrate

Migration files live in apps/api/src/db/migrations/ and are committed to version control.

TypeScript Strict Checks

Both workspaces use "strict": true in their tsconfig.json. The build step (tsc) catches type errors in apps/api (since it emits output). For apps/pwa, Vite uses esbuild to transpile and does not perform type checking — vitest will pass even when there are type errors in the PWA. Always run the typecheck script explicitly:

# Check both workspaces
pnpm typecheck

# Or individually
pnpm --filter @familysync/api typecheck
pnpm --filter @familysync/pwa typecheck

Run pnpm typecheck before opening a PR to catch errors that vitest and Vite builds will silently miss.

Code Style

ESLint and Prettier are listed as the intended linting and formatting tools. Check for config files in each workspace and confirm the lint script is wired before running pnpm lint.

Docker Compose Dev Stack

# Bring up the full dev stack (API in Docker + MariaDB + Redis, with ports exposed)
docker compose -f docker-compose.yml -f docker-compose.dev.yml up

# Bring up only backing services (run API on host for faster iteration)
docker compose -f docker-compose.yml -f docker-compose.dev.yml up mariadb redis -d

The dev override:

  • Exposes MariaDB on localhost:3306
  • Exposes Redis on localhost:6379
  • Mounts apps/api/src into the container for live source access
  • Sets NODE_ENV=development

The production docker-compose.yml builds the API and PWA into a single image (production target in apps/api/Dockerfile). The PWA dist/ is copied into the API image's ./public directory and served on port 3000.

Common Issues

dev script fails with "Cannot find module" The dev script runs node --watch dist/index.js. If dist/ does not exist or is stale, run pnpm --filter @familysync/api build first.

DB connection refused when running API on host DB_HOST defaults to mariadb (the Docker service name). When running the API outside Docker, override it: DB_HOST=127.0.0.1. The dev compose override exposes port 3306 on the host.

drizzle-kit migrate says "cannot connect" Same DB_HOST issue. Prepend DB_HOST=127.0.0.1 to the migrate command when running from the host.

API integration tests fail with FK errors The vitest config sets fileParallelism: false to prevent concurrent test files from conflicting via the shared MariaDB. Ensure you are not overriding this. Tests require a running MariaDB — set DB_HOST=127.0.0.1 and ensure the dev database is up.

TypeScript errors missed during development Vite/esbuild strips types; type errors will not surface in vitest run or vite build output. Run pnpm typecheck explicitly to catch them.