Skip to content

Repository Strategy โ€” Monorepo & Three-Layer Architecture ​

Status: Decided (records the locked structural decisions). The formal ADR is docs/internal/adr/0001-monorepo-three-layer-structure.md; the full platform context is pmo/platform-strategy.md.

Context ​

Heritage Community Hub must ship on three surfaces โ€” web, iOS, and Android โ€” from one codebase, built and maintained by a 1โ€“3 person team. The original question ("should this be multiple repos?") resolved into a clear structural decision: one monorepo, an API-first platform, decomposed on two axes. This document records that decision and the triggers that would cause us to revisit it.

Decision 1 โ€” API-first / headless platform ​

All business logic, data, auth, and rules live behind a single backend API (apps/api). Web and mobile contain only presentation + client state and call the platform through one typed client (packages/api-client). This is the keystone: build a feature once on the API and every surface consumes it, making web + 2 apps roughly 1.3ร— the effort instead of 3ร—.

Decision 2 โ€” Three conceptual layers ​

  1. Platform โ€” the core: backend API + web app + shared packages.
  2. Mobile apps โ€” iOS + Android clients (React Native + Expo).
  3. Features โ€” the capabilities the platform provides (calendar, announcements, marketplace, Pony Express, โ€ฆ), consumed by all surfaces.

Decision 3 โ€” ONE monorepo, decomposed on two axes ​

A monorepo is one Git repo holding many independently bounded workspaces (each with its own package.json, build, and deploy). Separation happens on two axes:

  • Axis 1 โ€” Surfaces (how users reach it): the deployable apps in apps/.
  • Axis 2 โ€” Features (what it does): vertical slices (features/<name>/) that repeat inside each surface, stitched together by shared types. A feature is not a top-level folder.
text
heritage-community-hub/            # ONE git repo
โ”œโ”€โ”€ apps/                          # SURFACES โ€” deployable applications
โ”‚   โ”œโ”€โ”€ api/   src/features/{calendar,announcements,โ€ฆ}   # all logic โ†’ cloud functions
โ”‚   โ”œโ”€โ”€ web/   src/features/{calendar,announcements,โ€ฆ}   # React web client
โ”‚   โ””โ”€โ”€ mobile/src/features/{calendar,announcements,โ€ฆ}   # React Native (Expo) โ†’ iOS/Android
โ”œโ”€โ”€ packages/                      # SHARED CODE โ€” imported by apps, not deployed alone
โ”‚   โ”œโ”€โ”€ shared-types/   # domain types / API contracts (one Event type, used everywhere)
โ”‚   โ”œโ”€โ”€ api-client/     # typed SDK web + mobile use to call the api
โ”‚   โ”œโ”€โ”€ shared-utils/   # validation, RBAC helpers, formatting
โ”‚   โ”œโ”€โ”€ shared-config/  # shared config/constants
โ”‚   โ””โ”€โ”€ ui/             # design tokens / shared design system
โ”œโ”€โ”€ infrastructure/                # IaC (Bicep)
โ”œโ”€โ”€ database/                      # schema (design doc), migrations, seeds
โ”œโ”€โ”€ docs/                          # architecture, decisions (adr/), guides
โ””โ”€โ”€ pmo/                           # planning & PM reference docs

Note: apps/* and packages/* are planned, not yet created โ€” they are scaffolded in Phase 1. The package.json workspace scripts have no targets until then.

How one feature (e.g. Calendar) lives across the repo: logic in apps/api/src/features/calendar, UIs in apps/web/... and apps/mobile/..., all sharing the Event/RSVP types from packages/shared-types and calling the platform via packages/api-client. That shared-by-import flow is the entire reason for a monorepo.

Why monorepo (not multi-repo) for this goal &ZeroWidthSpace;

  • Mobile and web share shared-types + api-client โ€” trivial in a monorepo, painful across repos (publishing/versioning a private package on every change).
  • Atomic commits: change an API contract and update all three clients in one PR.
  • Tiny team; one CI config, one dependency graph, one VS Code workspace.
  • The only defensible split โ€” mobile's separate release cadence โ€” is handled by a separate pipeline, not a separate repo.

When to revisit multi-repo &ZeroWidthSpace;

Split only when a real trigger appears:

  • Team size โ€” 10+ developers stepping on each other.
  • Independent deployment โ€” components that must deploy on genuinely independent schedules (beyond mobile's release cadence, which a separate pipeline already handles).
  • Divergent tech stacks โ€” a component adopts a completely different technology.
  • External access โ€” a third party needs access to one component only.
  • Compliance โ€” separate security/audit boundaries are required.

Current situation favors monorepo: small team, tightly coupled API + clients, shared deployment cycle, single community focus. Recommendation: start monorepo; extract packages as they mature only if a trigger above is met.

Heritage Community Hub โ€” Internal. Access restricted via Cloudflare Access + Entra ID.