The
apps/demeter/area: the three deployable client/server applications of Demeter, a home-gardening platform — a Fastify REST API plus an Expo mobile app and a Vite web dashboard that both consume it.
What this area is#
Demeter is a home-gardening product: users model their gardens and beds, track
plantings through their lifecycle, log harvests and journal observations, manage
garden tasks, wire up IoT soil/environment sensors, watch the weather, and lean
on a gardening assistant. The apps/demeter/ directory holds the three
deployable applications that surface that product — it is the app tier, not
the domain logic. The reusable domain packages live separately under
libs/demeter/* (for example @demeter/core, which owns the Drizzle schema and
Zod shapes, alongside feature libraries like analytics, automation,
biodynamic, community, and more).
The three apps are exactly the projects git ls-files reports under this tree:
@demeter/api (the backend, apps/demeter/api), @demeter/mobile (the React
Native client, apps/demeter/mobile), and @demeter/web (the browser
dashboard, apps/demeter/web). All three carry the scope:demeter / type:app
Nx tags; the API additionally tags layer:service, mobile tags
platform:mobile, and web tags platform:web (see each project.json).
The relationship between them is a classic one-backend / two-frontend split.
@demeter/api is the single source of truth: it exposes a versioned REST
surface under /v1/* (the route groups registered in
apps/demeter/api/src/routes/index.ts are gardens, plants, plantings,
tasks, harvests, sensors, weather, observations, ai, and
community, plus unauthenticated /health* probes). Both @demeter/mobile and
@demeter/web are pure clients of that surface — each ships its own fetch-based
API client (apps/demeter/mobile/src/api/client.ts and
apps/demeter/web/src/api/client.ts) and a parallel set of React Query hooks
per domain (src/api/hooks/use*.ts in both). They do not share code directly;
they share the wire surface the API defines.
How it fits the wider system#
@demeter/api is the only node here that reaches into the rest of the monorepo:
its package.json depends on @demeter/core (the Drizzle schema —
demeterGardens, demeterGardenBeds, demeterGardenMembers, etc., imported by
the stores) plus the platform libraries @oshun/database, @oshun/errors, and
@oshun/logging. It persists to PostgreSQL via Drizzle, caches/rate-limits via
Redis, and integrates the public Open-Meteo weather service. The two client apps
depend on nothing in the workspace at the app boundary — they talk HTTP to the
API and store tokens locally (Expo SecureStore on mobile, localStorage on
web), so the boundary between this area and the rest of the platform is the REST
contract plus @demeter/core/@oshun/* on the server side. The
apps/demeter/docker-compose.yml and the API's Dockerfile describe how the
backend runs against its Postgres/Redis dependencies.
Entity catalog (3)#
The 3 tracked Nx projects in demeter, each a code-linked entity node — package, type, source path, declared targets, and its internal dependency graph (depends-on / used-by, resolved from the package manifests, §6/§8), read from the project graph. Grouped by architectural layer; walk the dependency links to travel the system. 3 of these carry an authored deep-dive (what / why / how it fits); the rest are generated scaffolds awaiting one.
service (1)#
Demeter Home Gardening API — Fastify-based REST API
The Fastify backend (apps/demeter/api), described by its own README as the
"Demeter Home Gardening API — Fastify-based REST API." Its architecture is a
real ordered plugin stack assembled in src/app.ts's buildServer():
request-context, security headers, CORS, error handler, Swagger/OpenAPI, JWT
auth, a PostgreSQL+Drizzle database plugin, Redis, and rate limiting, with all
route groups registered last. Persistence is genuinely relational — the src/db
stores (e.g. garden-store.ts) issue Drizzle queries against the
@demeter/core tables, map between the public API shape and the schema (garden
location to lat/long/elevation/aspect columns, bed metres to centimetres, soil
type to a jsonb soil_composition), guard UUIDs, strip NUL bytes, and use
soft-delete (deletedAt). Six checked-in migrations live under drizzle/
(0000_demeter_initial through 0005_community_tables). External integrations
are dependency-injectable seams: src/weather/weather-provider.ts ships a real
Open-Meteo client (mapping WMO codes and wind degrees, frost-risk thresholds)
that tests override with a deterministic provider, and
src/auth/oauth-verifiers.ts is similarly injectable. The /v1/ai/* routes
(src/routes/ai.ts) are honestly heuristic, not an LLM pass-through — and
the file says so: plant identification is a trait-matching scoring algorithm
over a built-in profile table, diagnosis is a symptom-disease correlation
engine, plus planting-plan generation, companion-aware layout optimization,
harvest-timing prediction, and a keyword-scored chat knowledge base. Testing is
extensive and layered: unit specs in src/__tests__/, a full
test/integration/ suite, and a test/performance/ suite with its own harness
and benchmarks.
unclassified (2)#
Demeter - Home Gardening Platform Mobile App
The Expo / React Native client (apps/demeter/mobile) — "Demeter - Home
Gardening Platform Mobile App." App.tsx is a real entry point that wires
SafeAreaProvider, React Query, and the navigation container, restores the
session from stored tokens, and wires the API client's 401 handler to force
logout. The app is broad and implemented, not scaffolded: src/screens/
contains full screen sets for gardens, plants, journal, tasks,
sensors, weather, ai (plant identification/diagnosis, assistant,
recommendations feed), community, settings, and auth, navigated through
src/navigation/AppNavigator.tsx. Server access goes through
src/api/client.ts — a fetch client with Expo SecureStore token storage,
deduplicated 401 refresh, exponential-backoff retries, and per-domain React
Query hooks under src/api/hooks/. The standout is the offline layer in
src/services/offline/: SyncEngine.ts is a real priority queue (CRITICAL→LOW
enum), persisted to AsyncStorage, with deduplication by key, batched
processing, per-item exponential-backoff retry, status listeners, and auto-sync
— accompanied by ActionQueue, ConflictResolver, OfflineStorage,
PlantCache, WeatherCache, SensorSync, and OfflinePhotoManager. There is
also a src/services/widgets/ set (Siri Shortcuts, Google Assistant, Quick
Actions, Watch, Widget data, Notification actions) and Jest test suites
co-located with the screens and services.
The Vite + React browser dashboard (apps/demeter/web). src/App.tsx
initializes the auth store and theme then mounts a RouterProvider;
src/router.tsx defines a large lazy-loaded route tree — eager auth pages
plus protected sections for dashboard, gardens (including a planner and a
GardenPlanner3DPage), plants (companion matrix, planting calendar,
pest/disease browser, comparison), tasks (calendar, recurring, scheduler,
templates, bulk, export), harvests, sensors/IoT (visualization, automation
rules, scenes, alerts, device pairing, real-time monitor, history), weather,
journal, analytics (yield, cost, resource, environmental, comparison, report
builder, export, printable), community, settings, and profile. It talks to
the same backend via src/api/client.ts (base URL from VITE_API_URL, falling
back to /api, with localStorage tokens and 401 auto-refresh) and per-domain
hooks under src/api/hooks/. The component library is real (src/components/
charts, forms, layout, table, ui) and styling is Tailwind. One honest caveat
lives in src/stores/expensesStore.ts: it is a Zustand store backed by
localStorage with seed data, explicitly labelled in its own docstring as a
stand-in for CostAnalysisPage "before the production expense API is available"
— a documented local placeholder, not a claimed server integration. Quality is
covered by src/__tests__/ (vitest) and an e2e/ Playwright suite spanning
auth, gardens, plants, tasks, sensors, community, journal, navigation, and
plant-id flows.