11 KiB
Architecture
Analysis Date: 2026-04-30
System Overview
┌─────────────────────────────────────────────────────────────┐
│ Frontend SPA (React) │
├──────────────────┬──────────────────┬───────────────────────┤
│ App Shell/Routes │ Shared State │ Feature Pages │
│ `frontend/src/ │ `frontend/src/ │ `frontend/src/pages/` │
│ App.tsx` │ context/` │ │
└────────┬─────────┴────────┬─────────┴──────────┬────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ Backend API (Fastify) │
│ `backend/src/index.ts` + `backend/src/routes/` │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ SQLite Persistence + Migration Layer │
│ `backend/src/db/schema.ts` + `backend/src/db/client.ts` │
└─────────────────────────────────────────────────────────────┘
Component Responsibilities
| Component | Responsibility | File |
|---|---|---|
| Frontend bootstrap | Mount providers/router and start app tree | frontend/src/main.tsx |
| App router/shell | Public share routes, authenticated shell routes, global modal composition | frontend/src/App.tsx |
| Frontend orchestration | Compose domain hooks and expose app-level state/actions | frontend/src/context/AppContext.tsx |
| API proxy boundary | Rewrite /api/* requests to backend root routes |
frontend/vite.config.ts |
| Backend composition root | Register plugins/routes, await migrations, start schedulers | backend/src/index.ts |
| Route handlers | HTTP contracts, validation, auth hooks, response shaping | backend/src/routes/*.ts |
| Domain services | Shared domain logic and scheduler behavior | backend/src/services/*.ts |
| Persistence | Table definitions + compatibility migration/runtime initialization | backend/src/db/schema.ts, backend/src/db/client.ts |
Pattern Overview
Overall: Layered modular monolith (single frontend SPA + single backend process)
Key Characteristics:
- Frontend uses React Router + context/hook composition (
frontend/src/App.tsx,frontend/src/context/AppContext.tsx). - Backend uses route modules with shared service modules (
backend/src/routes/medications.ts,backend/src/services/medications-service.ts). - Data persistence is centralized in Drizzle schema + startup migrations (
backend/src/db/schema.ts,backend/src/db/client.ts).
Layers
Frontend Presentation + Orchestration:
- Purpose: Render UI, route navigation, manage client state, invoke API.
- Location:
frontend/src/main.tsx,frontend/src/App.tsx,frontend/src/pages/,frontend/src/context/,frontend/src/hooks/. - Contains: pages, modals, app shell, hook-based API callers.
- Depends on: backend
/api/*, i18n, shared frontend utils/types. - Used by: browser clients.
Backend HTTP/API Layer:
- Purpose: Expose REST endpoints, authenticate/authorize requests, validate input, map to service/db logic.
- Location:
backend/src/index.ts,backend/src/routes/,backend/src/plugins/. - Contains: Fastify app setup, route registration, auth middleware.
- Depends on: services, db client/schema, env plugin.
- Used by: frontend SPA and API consumers.
Domain Services Layer:
- Purpose: Reusable business logic for scheduling, notifications, stock math, parsing.
- Location:
backend/src/services/,backend/src/utils/. - Contains: reminder scheduler, notification builders/delivery, medication helpers.
- Depends on: db models and utilities.
- Used by: routes and startup process.
Persistence Layer:
- Purpose: Define DB schema and keep existing SQLite instances compatible.
- Location:
backend/src/db/schema.ts,backend/src/db/client.ts,backend/drizzle/. - Contains: tables, migration execution, backward-compatible alter migrations.
- Depends on: Drizzle + libsql client.
- Used by: routes/services.
Data Flow
Primary Request Path
- Frontend page triggers API call via
/api/*fetch (frontend/src/pages/PlannerPage.tsx:307). - Vite proxy rewrites
/apiprefix to backend route root (frontend/vite.config.ts:23,frontend/vite.config.ts:26). - Fastify route handles request under
/planner/send-emailwith auth + validation (backend/src/routes/planner.ts:141,backend/src/routes/planner.ts:158). - Route loads user settings and dispatches channel delivery helpers (
backend/src/routes/planner.ts:221,backend/src/routes/planner.ts:432,backend/src/routes/planner.ts:829).
Public Share Flow
- Frontend routes public token URL to shared schedule view (
frontend/src/App.tsx:35). - Shared schedule component fetches token payload from
/api/share/:token(frontend/src/components/SharedSchedule.tsx:311). - Backend public share route reads token/settings and returns filtered medication schedule (
backend/src/routes/share.ts:125,backend/src/routes/share.ts:156).
State Management:
- Frontend: context-centric state aggregation (
frontend/src/context/AppContext.tsx:248,frontend/src/context/AppContext.tsx:1020). - Backend: DB-backed state with runtime scheduler state persisted through notification state utilities (
backend/src/services/reminder-scheduler.ts:42).
Key Abstractions
Auth Context + Guards:
- Purpose: unify session/API-key auth across protected routes.
- Examples:
backend/src/plugins/auth.ts,backend/src/routes/settings.ts. - Pattern: route-level
preHandlerguard plus request decoration (backend/src/routes/settings.ts:138,backend/src/plugins/auth.ts:236).
Notification Delivery Contract:
- Purpose: keep route-triggered and scheduler-triggered notifications consistent.
- Examples:
backend/src/routes/planner.ts,backend/src/services/reminder-scheduler.ts,backend/src/services/notifications/delivery.ts. - Pattern: shared builders/delivery/state helpers imported into both paths (
backend/src/routes/planner.ts:23,backend/src/services/reminder-scheduler.ts:39).
Frontend App Context Aggregator:
- Purpose: centralize shared medication/settings/dose/share/refill state for page/modal consumers.
- Examples:
frontend/src/context/AppContext.tsx,frontend/src/context/ShareContext.tsx. - Pattern: compose domain hooks, expose typed value via provider (
frontend/src/context/AppContext.tsx:248,frontend/src/context/AppContext.tsx:1020).
Entry Points
Frontend bootstrap:
- Location:
frontend/src/main.tsx - Triggers: browser loads
index.html. - Responsibilities: initialize theme/provider stack and router (
frontend/src/main.tsx:12,frontend/src/main.tsx:15).
Backend process entry:
- Location:
backend/src/index.ts - Triggers:
npm run dev/npm startin backend package. - Responsibilities: await migrations, register routes, start HTTP listener and schedulers (
backend/src/index.ts:231,backend/src/index.ts:305,backend/src/index.ts:309,backend/src/index.ts:334).
Architectural Constraints
- Threading: Single Node.js event loop process with in-process schedulers started at runtime (
backend/src/index.ts:309,backend/src/index.ts:323). - Global state: Module/global singletons exist in auth and context layers (
backend/src/plugins/auth.ts:15,frontend/src/context/AppContext.tsx:222). - Circular imports: Not detected from sampled route/service/db/frontend orchestration files.
- API boundary: Frontend network calls must use
/api/*so proxy rewrite applies (frontend/vite.config.ts:23,frontend/vite.config.ts:26).
Anti-Patterns
Duplicated Backend App Wiring
What happens: Route/plugin registration appears in both createApp(...) and top-level startup path.
Why it's wrong: Two bootstrap paths increase divergence risk when new routes/plugins are added in one path but not the other.
Do this instead: Keep a single shared app-construction function used by both test/runtime startup paths (backend/src/index.ts:133, backend/src/index.ts:207, backend/src/index.ts:289).
Oversized Frontend Orchestration Context
What happens: AppContext aggregates many unrelated concerns (medications, settings, doses, sharing, import/export, modal history) in one large provider.
Why it's wrong: High coupling and broad rerender surface make safe changes harder and increase regression risk.
Do this instead: Preserve existing provider contract, but move new domain concerns into focused hooks/providers and re-export through composition only when needed (frontend/src/context/AppContext.tsx, file size ~1035 lines).
Error Handling
Strategy: Fail fast at route boundary with explicit status codes and schema validation, then log context-rich errors.
Patterns:
- Route validation + immediate 400 responses for invalid input (
backend/src/routes/medications.ts:76,backend/src/routes/medications.ts:584). - Planner routes return explicit channel/config errors (
backend/src/routes/planner.ts:204,backend/src/routes/planner.ts:509). - Frontend captures network errors and maps them to normalized error codes for UI handling (
frontend/src/hooks/useMedications.ts:80).
Cross-Cutting Concerns
Logging: Fastify logger options configured centrally with environment-aware formatting (backend/src/index.ts:66, backend/src/index.ts:161).
Validation: Zod validation for medication payloads and explicit OpenAPI schema contracts in routes (backend/src/routes/medications.ts:76, backend/src/routes/planner.ts:157).
Authentication: Route-level auth hooks and dual API-key/session handling (backend/src/routes/planner.ts:141, backend/src/plugins/auth.ts:113, backend/src/plugins/auth.ts:236).
Architecture analysis: 2026-04-30