Skip to content

Local development setup ​

This guide covers setting up a local development environment for Heritage Community Hub. The platform is a pnpm monorepo orchestrated by Turborepo (ADR 0001). The application code is created in Phase 1 (ADO AB#3074); until that work lands, the workspace packages are skeleton directories with no source. The guide is written now so it is accurate and ready when code exists. Where a step depends on code that has not been written yet, it is marked with a "Phase 1" note.


Build environment note ​

Application builds are never run on the planning/documentation VM. That machine is for planning, documentation, and ADO work only. All npm install, pnpm install, docker build, and runtime commands must be executed in WSL or on a separate dedicated development box. This is a hard rule — see the team feedback memory for context.


Prerequisites ​

Node.js ​

Node.js 20 or later (LTS) is required. package.json enforces this via the engines field:

json
"engines": {
  "node": ">=20.0.0",
  "pnpm": ">=9.0.0"
}

Use nvm or nvm-windows to manage Node.js versions. Verify after installation:

bash
node --version   # must be v20.x or later

pnpm ​

pnpm 9 or later is required. Install it globally via corepack (bundled with Node.js 20):

bash
corepack enable
corepack prepare pnpm@9 --activate
pnpm --version   # must be 9.x or later

The .npmrc at the repo root sets node-linker=hoisted. This is required for the React Native / Metro bundler to resolve workspace packages (ADR 0001 update, ADR 0002). Do not change this setting.

Docker ​

Docker Desktop (or Docker Engine on Linux) is required for running a local PostgreSQL (Postgres) instance. The production database is Azure Database for PostgreSQL Flexible Server (ADR 0024). Locally, a containerized Postgres instance is the recommended approach to match the production dialect exactly.

bash
docker --version   # must be 20.x or later

Clerk developer account ​

Authentication is handled by Clerk (ADR 0003). You need a Clerk developer account and a development application:

  1. Sign in at clerk.com.
  2. Create a new application with Sign in with Apple and Google Sign-In enabled.
  3. Note the Publishable Key and Secret Key from the Clerk dashboard. These become CLERK_PUBLISHABLE_KEY and CLERK_SECRET_KEY in the environment.

Clerk development applications have a free tier. Do not use a production Clerk application for local development.

Turborepo ​

Turborepo is installed as a dev dependency; no global install is needed. It is invoked through the pnpm scripts defined in package.json.


Environment variables ​

All secrets in deployed environments come from Azure Key Vault (kv-hcs-vault-01) and are injected at runtime via the SecretsProvider adapter (ADR 0024). For local development, set them in an .env file at the repo root (or per-app .env files once apps/api and apps/web exist in Phase 1). The .env file must never be committed — it is listed in .gitignore.

VariableDescriptionRequired for
DATABASE_URLPostgres connection string, e.g. postgresql://user:password@localhost:5432/hch_devAPI (apps/api)
CLERK_PUBLISHABLE_KEYClerk frontend key (starts with pk_test_ in development)Web (apps/web), Mobile (apps/mobile)
CLERK_SECRET_KEYClerk backend key (starts with sk_test_ in development)API (apps/api)
CLERK_WEBHOOK_SECRETSigning secret for the Clerk webhook endpoint (see Clerk webhook setup)API (apps/api)
AZURE_STORAGE_CONNECTION_STRINGAzure Blob Storage connection string (behind the StorageProvider adapter)API (apps/api)
SENDGRID_API_KEYSendGrid API key for outbound email (behind the EmailProvider adapter; ADR 0013)API (apps/api)
TWILIO_ACCOUNT_SIDTwilio account SID for SMS (behind the SmsProvider adapter; ADR 0013)API (apps/api)
TWILIO_AUTH_TOKENTwilio auth tokenAPI (apps/api)
TWILIO_PHONE_NUMBERTwilio sending phone number (E.164 format, e.g. +12765550100)API (apps/api)
EXPO_PUBLIC_API_URLBase URL the mobile app uses to reach the API, e.g. http://localhost:3001Mobile (apps/mobile)

Notes:

  • In Azure deployed environments, all of the above are stored as Key Vault secrets in kv-hcs-vault-01 and injected into the containerized API (Azure Container Apps) at startup.
  • Twilio credentials for local development are optional until Phase 4 (Messaging & Notifications Epic AB#3138). Mock the SmsProvider adapter in tests rather than calling Twilio.
  • For local blob storage, the Azurite emulator is an alternative to a live Azure Storage account. Set AZURE_STORAGE_CONNECTION_STRING to the Azurite connection string (UseDevelopmentStorage=true).

First-time setup ​

1. Clone the repository ​

bash
git clone https://github.com/Heritage-Virginia/heritage-community-hub.git
cd heritage-community-hub

2. Install dependencies ​

bash
pnpm install

pnpm reads pnpm-workspace.yaml and installs dependencies for all packages in apps/* and packages/*. Because node-linker=hoisted is set in .npmrc, the resulting node_modules layout is flat — required for Metro / React Native (ADR 0002).

Phase 1 note: Until apps/ and packages/ are populated with source code in Phase 1 (AB#3074), pnpm install will complete quickly because there is nothing to install beyond the root devDependencies (turbo, typescript, @types/node).

3. Start a local Postgres instance ​

bash
docker run --name hch-dev-postgres \
  -e POSTGRES_USER=hch \
  -e POSTGRES_PASSWORD=localdev \
  -e POSTGRES_DB=hch_dev \
  -p 5432:5432 \
  -d postgres:16

Set DATABASE_URL=postgresql://hch:localdev@localhost:5432/hch_dev in your .env file.

4. Apply the schema ​

Phase 1 note: Phase 1 (AB#3074) will introduce a Prisma schema file with provider = "postgresql" (ADR 0024). Migrations will be managed by Prisma Migrate. Until then, the design schema in database/schema.sql is written in T-SQL / SQL Server dialect (the interim design artifact) and cannot be applied directly to Postgres. When Phase 1 scaffolding lands, run:

bash
# UNVALIDATED — command is correct in intent; exact invocation depends on Phase 1 Prisma setup
pnpm --filter @hch/api exec prisma migrate dev --name init

5. Seed development data ​

Phase 1 note: A seed script will be provided in database/seeds/ as part of Phase 1. Once available:

bash
# UNVALIDATED — Phase 1 seed script path and command TBD
pnpm --filter @hch/api exec prisma db seed

Seed data includes sample roles (the six canonical Plane-2 roles from ADR 0006), sample family groups, and an admin user so the approval workflow can be exercised locally.

6. Create the .env file ​

Copy the template (once created in Phase 1) and fill in values:

bash
cp .env.example .env
# Edit .env with your local values

Running the project ​

All run commands delegate to Turborepo task graph entries defined in turbo.json. The dev task is non-cached and persistent (appropriate for watch-mode servers).

All apps ​

bash
pnpm dev
# equivalent: turbo run dev

This starts all apps in the workspace concurrently. Turborepo's terminal UI ("ui": "tui" in turbo.json) shows per-app log streams.

API only ​

bash
pnpm dev:api
# equivalent: turbo run dev --filter=@hch/api

Phase 1 note: When the API is scaffolded, apps/api will run as a containerized Node.js/Express or Fastify server (ADR 0024). It will not use Azure Functions host bindings. The expected local port is 3001. Confirm in apps/api/package.json once Phase 1 lands.

Web app only ​

bash
pnpm dev:web
# equivalent: turbo run dev --filter=@hch/web

Phase 1 note: apps/web is a React + TypeScript application served by Vite or the equivalent dev server. The expected local port is 3000. Confirm in apps/web/package.json once Phase 1 lands.

Mobile app (React Native + Expo) ​

Mobile development requires a dev build — Expo Go does not support native modules (ADR 0002, ADR 0003). After Phase 4 (iOS Epic AB#3103) scaffolding:

bash
# UNVALIDATED — Phase 4; exact EAS command depends on project configuration
cd apps/mobile
pnpm exec expo prebuild        # generates ios/ and android/ native directories
pnpm exec expo run:ios         # runs on iOS Simulator (macOS required)
# or
pnpm exec eas build --platform ios --profile development

Sign in with Apple via Clerk requires EAS Build or a local device prebuild; it does not work in Expo Go. See ADR 0002 and ADR 0003 for constraints.

Expected ports (Phase 1, to be confirmed) ​

AppDefault port
apps/api3001
apps/web3000
Local Postgres5432

Running tests ​

bash
pnpm test
# equivalent: turbo run test

The Turborepo task graph runs test after build ("dependsOn": ["build"] in turbo.json). Tests are scoped per package; Turborepo runs them in dependency order.

Phase 1 note: The test framework and coverage targets will be defined in Phase 1 (AB#3074). Expected coverage: unit tests for service layer business logic, integration tests for API endpoints exercising a real Postgres test database, and snapshot tests for shared UI components.

To run tests for a single package once code exists:

bash
# UNVALIDATED — filter name depends on Phase 1 package.json name fields
pnpm --filter @hch/api test
pnpm --filter @hch/web test

Build ​

Full monorepo build ​

bash
pnpm build
# equivalent: turbo run build

Turborepo resolves the build dependency graph: packages in packages/* that apps/* depend on are built first ("dependsOn": ["^build"] in turbo.json). Build artifacts land in dist/, .next/, or build/ per package (configured in turbo.json outputs). Turborepo caches outputs locally; a second build with no file changes completes instantly.

Remote caching is not configured (cost constraint — see ADR 0001 update). Local caching is free and automatic.

What gets built ​

PackageOutputNotes
packages/shared-typesdist/TypeScript declarations; consumed by all other packages
packages/api-clientdist/Typed SDK; consumed by apps/web and apps/mobile
packages/shared-utilsdist/Utility functions
packages/shared-configdist/Shared constants
packages/uidist/Design system components + Storybook
apps/apidist/Compiled Node.js server; Docker image built separately in CI
apps/webbuild/ or .next/Static bundle deployed to Azure Static Web Apps
apps/mobilen/a locallyEAS Build produces the iOS archive / Android AAB in CI

Cleaning build artifacts ​

bash
pnpm run clean
# equivalent: turbo run clean

Troubleshooting ​

pnpm workspace resolution errors ​

Symptom: Cannot find module '@hch/shared-types' or similar when running an app.

Cause: A package in packages/* has not been built yet, so its dist/ output does not exist.

Fix: Run pnpm build from the repo root before starting the dev server. Turborepo will build packages in dependency order.


Metro cannot resolve a workspace package (mobile) ​

Symptom: Expo/Metro throws Unable to resolve module for a @hch/* import.

Cause: Metro requires a hoisted node_modules layout. If node-linker was changed or pnpm install was run with a different linker setting, the layout will be symlinked rather than hoisted.

Fix: Verify .npmrc contains node-linker=hoisted, then re-run pnpm install. Do not change .npmrc.


Clerk webhook not firing locally ​

Clerk sends webhook events (e.g. user.created) to a public HTTPS endpoint. In local development, the API runs on localhost which is not reachable from Clerk's servers.

Fix: Use ngrok or the Clerk CLI to forward events to localhost:

bash
# UNVALIDATED — ngrok command; adjust port to match apps/api local port
ngrok http 3001

Register the resulting https://<id>.ngrok.io/webhooks/clerk URL in the Clerk dashboard under Webhooks. Set CLERK_WEBHOOK_SECRET to the signing secret displayed there.


Postgres connection refused &ZeroWidthSpace;

Symptom: Error: connect ECONNREFUSED 127.0.0.1:5432

Cause 1: The Docker container is not running.

bash
docker ps | grep hch-dev-postgres
docker start hch-dev-postgres   # if stopped

Cause 2: DATABASE_URL is not set or points to the wrong port.

Check .env and confirm the port matches the -p flag used when the container was started.


Postgres schema not applied (T-SQL syntax errors) &ZeroWidthSpace;

The current database/schema.sql is written in T-SQL (SQL Server dialect) — it is a design document, not a runnable Postgres migration (ADR 0024 superseded the database choice after the schema was drafted). Do not apply it directly to a Postgres instance. Wait for the Prisma schema and migration files that will be created in Phase 1 (AB#3074).


pnpm version mismatch &ZeroWidthSpace;

Symptom: ERR_PNPM_BAD_PM_VERSION or packageManager field warnings.

package.json pins pnpm@9.0.0 in the packageManager field. If corepack or your global pnpm version differs, update:

bash
corepack prepare pnpm@9 --activate

References &ZeroWidthSpace;

Heritage Community Hub — Internal. Access restricted via Cloudflare Access + Entra ID.