PersonalPre-launch2025–present

Migraine Tracker

Lead Developer

A migraine-tracking mobile app whose export is real FHIR 4.0.1: attacks modelled as Observations coded in SNOMED CT and HL7 terminology, not ad-hoc JSON dressed up to look like it. iOS and Android, works offline.

Migraine Tracker

Stack

  • React Native 0.85
  • Expo 56
  • Express 5
  • PostgreSQL + Drizzle
  • Better Auth
  • FHIR 4.0.1
  • HealthKit
  • Android Health Connect
  • Sentry

The ten-minute appointment

Anyone living with migraine keeps notes: in a notebook, in the phone’s notes app, in screenshots. At the neurologist’s appointment all of that becomes a spoken summary, and the doctor re-types whatever fits. I built an app that records the attack while it is happening and produces a file the doctor’s system reads on its own.

Logging while the attack is still on

Logging happens with the pain still going, so it has to be short: intensity, where in the head, symptoms, what came before it, what was taken. A timer keeps the attack open and closes it when it passes.

Nothing waits for a network. The entry is written on the device first and synced afterwards, because an attack does not wait for you to find signal.

Pixel-art illustration of the logging screen: an intensity bar, a duration field, icons for food, weather and sleep with checkboxes, and a strip of dates
An illustration of the logging screen, not a screenshot with real data
How it works
  • What an entry holds: intensity 0 to 10, start and end time, duration type, pain sites on a head map, symptoms with a severity, triggers, medications with a dose and whether they helped, and free notes.
  • Local first: a local SQLite database with a write queue, so the screen never waits on the server.
  • Sync: a bidirectional /api/sync endpoint with last-write-wins, the right call for one person’s own data: simple, with no conflict-resolution screen.
  • Deletes: removed entries travel as tombstones, so a delete does not reappear from another device.
  • Backend: Express 5 with Drizzle ORM over PostgreSQL, with Better Auth.

Patterns, not impressions

After a few weeks the app starts answering questions nobody remembers accurately: how often, how badly, what tends to come first, which medication actually works.

The weather is recorded with every attack, so a change in barometric pressure is a measurement rather than a feeling.

Pixel-art illustration of the trends screen: a frequency chart, a row of trigger icons and a list of medications with times
An illustration of the trends screen
How it works
  • Weather: temperature, humidity, condition and barometric pressure are stored with each entry, along with the pressure change over the previous 24 hours.
  • Analysis: frequency per period, trends, sensitivity per trigger and a risk reading for the day, all computed from the entries themselves.
  • Medications: doses, reminders and a taken-history per medication.
  • Checks: 793 automated tests across 115 files cover the calculations, the sync and the screens.

A file the doctor’s system can read

Pixel-art illustration of the clinical data screen: cards for an observation, patient details and medication information, with download and share buttons
The export gives either a PDF to read or FHIR to import

Most health apps hand over a PDF. Here there is a second road: the history comes out as FHIR, the language hospital systems already speak, with international medical codes instead of free text. The doctor imports it.

How it works
  • What is exposed: a REST API publishing a CapabilityStatement with fhirVersion 4.0.1, serving Patient, Observation (read and search by date, _count, _sort) and MedicationStatement as application/fhir+json, with errors as OperationOutcome.
  • An attack as an Observation: status final, category survey from the HL7 terminology, code set to SNOMED CT 37796009 (Migraine), a subject reference to the Patient, and effectivePeriod for start and end.
  • Intensity: carried as valueInteger on a 0 to 10 scale, with interpretation from the HL7 v3-ObservationInterpretation system.
  • Sites and symptoms: 8 SNOMED codes for head regions in bodySite and 6 for symptoms as components, each with a severity.
  • Duration and triggers: components under SNOMED codes 103335007 and 134296009, with the trigger itself as a text value.
  • Access: the endpoint accepts either the patient’s session or a Patient Access Token, stored only as a SHA-256 hash, with a label, a scope, an expiry and a revocation.

The data stays the patient’s

A pain diary is one of the most personal records a person keeps. Nothing leaves without an explicit action, and the user can take all of it with them or delete it.

How it works
  • Export and deletion: a GDPR endpoint returning everything on the account, and account deletion behind a confirmation.
  • Device health: the health-records HealthKit entitlement on iOS and Android Health Connect through an in-house expo plugin, so history that already exists does not have to be typed again.
  • Lock: an app-lock overlay when the app goes to the background.
  • Error tracking: Sentry on both the app and the backend.
  • Repo: jimrarras/migraine-app (private)
  • Zosimades Healthcare Ops Stack: other healthcare software, but institutional ops rather than personal health
  • collection-app (part of the Zosimades stack): another offline-first mobile app
Get in touch

Display preferences

Text

Text size
Line spacing
Letter spacing

Color

Contrast

Motion and images

Reading

Reading aid
No changes