Hilo B2B Dashboard · System Architecture Next.js · Single-Tenant

Hilo B2B Dashboard — System Architecture

Pragmatic single-monolith architecture for the B2B dashboard. Next.js for app + API. Postgres for data. Azure Blob WORM for tamper-evident audit logs. Compliance baked into the request path, not bolted on. No microservices, no MySQL, no Python — every piece exists for a reason.

Compute · runs our code Data · stores + retrieves Edge / network · routing + ingress Security · auth + secrets Observability · monitoring + audit

Interaction: drag to pan · scroll to zoom · double-click to reset · fullscreen button per diagram.

0. Components — what each thing is, why we have it

Every component in the build, before any diagrams. Struck-through cards = explicitly excluded with reasoning.

Entry point
Azure App Service · HTTPS Ingress

Azure-managed ingress. TLS termination, Azure-issued cert (auto-renewed), HTTP→HTTPS redirect, custom domain (e.g. partners.hilo.com), health probes. The single public entry point for the whole system.

Why App Service vs. Container Apps: One Next.js app, Linux runtime, simple deploy. Container Apps is overkill for one container. App Service has built-in deploy slots for blue/green and Application Insights integration out of the box.
Compute
Next.js 14+ Monolith

The whole application. Frontend pages, API routes, middleware stack, agent endpoints — all in one TypeScript codebase. Renders the dashboard SPA, handles auth callbacks, runs the RBAC + audit middleware on every API call, returns JSON. Stateless — scales horizontally on App Service.

Why one monolith and not microservices: Hilo B2B is one product, one team, one database. Splitting it into auth-service + patient-service + audit-service buys you network latency, distributed tracing complexity, and coordinated deploys — for zero benefit at this scale. One container, one log stream, one place to look when something breaks.
Data
Azure Database for PostgreSQL — Flexible Server

Single source of truth. Postgres 16. One schema for B2B. Holds every business entity: orgs, users, patients, device_assignments, patient_consent, plus the in-DB audit_log partner. Zone-Redundant HA. 35-day point-in-time restore. TDE (transparent data encryption) on by default.

Why Postgres alone — no MySQL, no MongoDB: Auth records and health data both fit cleanly in Postgres. JSONB handles dynamic measurement payloads. Row-Level Security gives tenant isolation at the DB layer. Two databases doubled the ops surface for zero technical reason — gone.
Audit · Tamper-Evident
Azure Blob Storage · Immutable (WORM)

The other half of the audit trail. Every audited event written to Postgres is also streamed to Blob Storage with an Immutability Policy applied — Write Once, Read Many. Once a blob is written, it cannot be modified or deleted by anyone — not Hilo ops, not the DBA, not the cloud admin — for the configured retention period (6 years). Regulators get read-only SAS tokens to verify logs independently.

Why this answers "why should regulators trust your logs": The Postgres audit_log is for fast app queries. The WORM blob is the legal record. Even if Hilo's app database is fully compromised or maliciously edited, the Blob WORM copy survives unchanged — cryptographically anchored by Azure's storage immutability service. This is the architectural difference between "logs we wrote" and "logs anyone can verify."
Security · Identity
Microsoft Entra External ID (Azure AD B2C)

Managed OIDC identity provider. Handles login, MFA (TOTP / SMS), password reset, account lockout, session management, social login if needed. Issues JWTs containing sub, org_id, roles[], and permissions[] claims. Next.js middleware verifies the JWT signature on every request — no DB lookup needed.

Why managed and not in-house: Building auth in 3 weeks under deadline pressure is the surest way to ship a token leak, missing CSRF, or a password reset bypass. External ID is free at this scale (under 50K MAU) and gives you compliant auth in one day. The right answer is always to outsource auth.
Security · Network
Azure VNet + Private Endpoints

Postgres and Blob Storage both have private IPs only — no public internet reachability. The Next.js App Service connects via VNet integration. NSG rules deny all inbound to the data subnet except from the app subnet. Database credentials never traverse the public internet.

Why private endpoints: Without them, Postgres has a public IP that anyone with credentials (or a stolen connection string) can hit from anywhere. Private Endpoints close that attack surface. For a system holding GDPR Article 9 health data, this is table stakes.
Security · Secrets
Azure Key Vault

Zero secrets in code, env vars, or container images. Stores: Postgres connection string, Entra External ID client secret, Blob Storage SAS keys, SendGrid API key, signing keys for JWT verification, encryption keys for at-rest field-level crypto. Next.js accesses via Managed Identity — no passwords to rotate, nothing to commit.

Why Key Vault: Secrets in .env files are how breaches happen. Managed Identity removes the bootstrap-credential problem entirely — the App Service authenticates to Key Vault via its Azure identity. Every secret access is logged.
Integration
Email · SendGrid or Postmark

Sends patient consent emails, partner invitation emails, password reset (or delegated to Entra), notifications. Bounce + delivery tracking required for GDPR-defensible consent — we need to be able to prove an email was delivered when the patient clicked accept.

Why a real provider: Patient consent is the legal anchor of the whole product. SMTP-direct from a server is best-effort; bounces, spam folders, shared inboxes will break the consent chain. SendGrid/Postmark give us delivery proofs.
Observability
Azure Application Insights

Auto-instruments Next.js requests, DB queries, external API calls. Distributed tracing — one user click gets one trace ID across middleware, Postgres, Blob, and email send. Metrics dashboards: p99 latency, error rate, DB connection pool utilization. Alerts to email + Teams.

Why Azure-native: Everything else is Azure — zero-config integration with App Service. We can search logs in KQL, set alerts in the same portal, and trace a single request end-to-end without paying a separate APM vendor.
Observability
Sentry · Error tracking

Captures every uncaught exception with stack trace + user context + last 5 user actions. When something breaks in production, Sentry surfaces the root cause in seconds — not hours of log searching.

Why both Sentry and App Insights: App Insights is great for infrastructure metrics. Sentry is purpose-built for exception triage — deduplication, frequency trends, release tracking. Different tools for different jobs.
Excluded · MySQL
MySQL

The existing B2C backend uses MySQL for accounts. The new B2B build does not — we use Postgres for everything.

Why removed: Two databases, two backup procedures, two query optimizers, two failure modes. Postgres handles everything MySQL would, plus JSONB, plus better RLS, plus pgvector if we ever need semantic search.
Excluded · MongoDB
MongoDB

The existing B2C backend uses MongoDB for measurement data. We don't.

Why removed: Postgres JSONB columns store flexible measurement payloads with the same query ergonomics as Mongo, plus ACID transactions across BP readings + audit log + consent — which Mongo cannot give you in a single transaction.
Excluded · Python / Flask
Python backend service

An earlier proposal had Flask alongside Next.js for "data processing."

Why removed: No CPU-bound work. No ML. No batch processing. The B2B dashboard is CRUD + auth + audit. Adding a second runtime in a second language to a 3-week MVP doubles the deployment surface for zero benefit. If we ever need ML (e.g. trend prediction), we add a Python service then.
Excluded · Microservices
Five-service split (auth · resolver · physiological · etc.)

The existing B2C backend uses 5 Spring Boot microservices. We don't replicate that.

Why removed: The reason microservices exist is to let independent teams ship independently. With one team and one product, you get all the operational pain (network calls, distributed tracing, schema-coordinated deploys) and none of the benefits.

1. System Architecture Overview — everything at once

The full map. Browser top-left, identity provider top-center, Azure region holds the whole stack. Two write paths to data: Next.js writes to Postgres, audit middleware additionally writes immutable copies to Blob WORM. No path bypasses the middleware stack.

Rendering…
Reading the diagram: The doctor's browser is the only public ingress. App Service terminates TLS. Next.js middleware runs in this order on every request: authenticate → authorize → scope → audit. Postgres holds business data and a fast-query audit log. Azure Blob WORM holds the immutable, regulator-verifiable copy of every audit event. Identity is fully delegated to Entra External ID — Hilo never stores a password.

2. A Request's Journey — what happens when a doctor clicks "view patient"

An HCP clicks on a patient in the dashboard. Walk through every middleware layer the request passes through, what each one verifies, and what gets written where.

Rendering…
Why the middleware order matters: authenticate first (no identity = stop). Then authorize (does this role have read:patients permission). Then scope (filter by org_id and assignment list — even an authorized user can only see their own slice). Then audit (we log before returning data, so a server crash mid-response still leaves a trace). The query never runs without all four passing.

3. RBAC + Tenant Scoping — how a clinic only sees its own patients

Every B2B customer is a tenant. Inside each tenant, users have roles. The architecture enforces both: tenant isolation (an HCP at Clinic A cannot see Clinic B's patients) and role-based scope (an HCP user only sees their assigned patients; an HCP admin sees all org patients).

Rendering…
Why enforce at the database layer too: the Next.js middleware filters every query by org_id and assignment list. But we add Postgres Row-Level Security as a second layer. If a future bug in the app forgets to scope a query, RLS rejects it at the DB. Defense in depth — same patient data, two independent enforcement layers, both have to fail for cross-tenant leakage to occur.

4. Tamper-Evident Audit Logging — why regulators can trust this

Every audited action is dual-written. The Postgres copy is for fast app-side queries (e.g. "show me everything user X did this week"). The Azure Blob WORM copy is the legal record — immutable, retention-locked, separately accessible to auditors.

Rendering…
The trust model in plain English: Hilo can read the WORM blob. Hilo cannot modify or delete it. Azure's Storage Immutability service enforces this at the platform layer — not the app, not Hilo's IAM policy. Regulators get a read-only SAS token that lets them list and download blobs directly from Azure, bypassing Hilo entirely. If the Postgres audit log and the WORM archive ever disagree, the WORM archive is the authoritative answer — and only Hilo's app can have tampered with the Postgres copy.
PropertyPostgres audit_logBlob WORM archive
PurposeFast app queries · activity timeline UILegal record · regulator verification
Modifiable by Hilo?Theoretically yes (DB admin)No — Azure platform-enforced
RetentionApp-managed, can be backfilled6 years, retention lock applied at write time
Auditor accessThrough Hilo's app, requires trustDirect read-only SAS, no Hilo middleman
Tamper detectionNone — a clever DBA could rewrite historyBlob hashes verify integrity; immutability rejects writes

5. GDPR + HIPAA Compliance Flows — consent, residency, right to erasure

Three compliance flows are wired into the system architecture, not added later: explicit consent (Article 9 special category data), data residency (EU patients stay in EU regions), and right to erasure (Article 17). Each one is enforced in middleware, so no API path can skip it.

Rendering…
Reading the flow: a patient grants consent → consent middleware records it (Postgres + WORM). When an HCP later queries that patient's data, the scope middleware first checks the live consent record. If consent is revoked, the query returns nothing — even though the row still exists in Postgres. Right to erasure is a soft delete with a scheduled hard-delete job; audit logs are preserved (regulators need them) but every consumer query now treats the patient as gone.

6. Device Data Flows — HCP-managed vs patient-managed

The two device-ownership models look like the same dashboard view but are completely different data flows. The HCP-managed flow is buildable for the May 28th milestone. The patient-managed flow needs a B2C → B2B data bridge that depends on the mobile app team.

Rendering…
Time of measurement vs time of sync: HCP-managed bands buffer days of data and only sync when the patient returns to the clinic. The dashboard shows both timestamps — "last reading 5 days ago, last synced 1 hour ago" — so a doctor never reads stale-but-unsynced data as a clinical event.

7. Deployment + Operations — from git push to production

One Next.js container. One Postgres database. One Blob Storage account. CI/CD via GitHub Actions. Staging slot for safe pre-prod verification. Zero-downtime deploys via App Service slot swap.

Rendering…
Why slot swap: the new build deploys into the staging slot, runs smoke tests against a copy of prod traffic, and only swaps in if healthy. If the new version is broken, swap back instantly — no downtime, no panic. Database migrations are always backward-compatible (add columns, never drop) so rollbacks don't break the schema.

8. Open Architecture Decisions

Decisions that need Hilo input before code is written. Not for the dev team to make alone.

#DecisionOwnerBlocks
1B2C → B2B data access mechanism (direct DB read · read replica · internal API)Hilo tech (Matti)Patient-managed flow
2Identity provider · Entra External ID vs Auth0Hilo tech leadershipAuth integration
3HIPAA applicability (US customers? EU only?)Hilo legalCompliance scope
4Patient consent flow wording & legal basisHilo legalPermission management UX
5Azure region(s) — single EU region or EU + US for residencyHilo techInfra provisioning
6BP categorization thresholds (ESH 2023 / AHA 2017 / Hilo-defined)Hilo productPatient view rendering
7Cross-team capacity for patient-managed flow (mobile app team)Hilo engineeringWhether patient-managed is in MVP