Why Your Health Data Belongs on Your Device (Not the Cloud): A Local-First Manifesto

Published: (December 26, 2025 at 11:00 AM EST)
5 min read
Source: Dev.to

Source: Dev.to

Introduction

Let’s be real for a second. How many patient portals do you have logins for?

I have one for my GP, one for the dentist, one for that specialist I saw three years ago, and another for my eye doctor. None of them talk to each other. If I lose my internet connection (or if their server decides to take a nap), I have zero access to my own medical history.

For years, we’ve been building health apps with the standard SaaS mindset: The Server is God. The client is just a dumb terminal that begs for data via REST or GraphQL.

But for something as sensitive and critical as Personal Health Records (PHR), this architecture is… well, kind of broken.

I’ve been experimenting lately with a different approach: Local‑First Architecture. It’s time we flip the model upside down.

The Problem with “Centralized by Default”

When we put health data in a centralized SQL database owned by a startup, we run into three massive headaches:

  • Privacy is a Policy, not a Guarantee: You have to trust that the admins aren’t looking at the rows, or that they encrypted the data at rest properly.
  • Latency & Connectivity: Ever try to pull up a vaccination record in a hospital basement with zero signal? It’s a nightmare.
  • Data Sovereignty: If the startup goes bust, your health history evaporates.

Enter Local‑First Software (LoFi)

The idea behind Local‑First is simple: The data lives on your device first. The cloud is just a synchronization relay or a backup, not the source of truth.

If I build a Personal Health Record app, I want it to feel like a file I own—like a .txt file or a spreadsheet, but with a nice UI.

The Stack: SQLite + WASM

A few years ago, running a relational DB in the browser was a pipe dream. You had localStorage (lol) or IndexedDB (which has an API only a mother could love).

Now, thanks to WebAssembly (WASM), we can run SQLite directly in the browser. It’s fast, it’s SQL, and it’s persistent.

import sqlite3InitModule from '@sqlite.org/sqlite-wasm';

const startDB = async () => {
  const sqlite3 = await sqlite3InitModule({
    print: console.log,
    printErr: console.error,
  });

  const oo = sqlite3.oo1; // object‑oriented API

  // Storing this in OPFS (Origin Private File System) so it persists!
  const db = new oo.OpfsDb('/my-health-data.sqlite3');

  db.exec(`
    CREATE TABLE IF NOT EXISTS vitals (
      id TEXT PRIMARY KEY,
      type TEXT NOT NULL,
      value REAL NOT NULL,
      timestamp INTEGER
    );
  `);

  console.log("Local DB is ready to rock.");
  return db;
};

// TODO: add error handling later lol

With this setup, the user owns the .sqlite3 file. They can download it, delete it, and it works seamlessly in Airplane Mode.

But… How Do We Sync? (The Magic of CRDTs)

Here is the hard part. If I update my allergies on my phone, and my partner updates my emergency contacts on the iPad, and we are both offline… what happens when we reconnect?

In a normal SQL setup, you get a conflict. “Last write wins” usually destroys data.

For a health app, we can’t afford to lose data. This is where Conflict‑free Replicated Data Types (CRDTs) come in. Tools like Yjs or Automerge treat data like a stream of changes rather than a static snapshot. They ensure that no matter what order the changes arrive in, the final state is identical on all devices.

import * as Y from 'yjs';

// The document holds our data
const ydoc = new Y.Doc();
const meds = ydoc.getArray('medications');

// Device A adds Ibuprofen
meds.push(['Ibuprofen - 200mg']);

// Device B adds Amoxicillin (while offline)
meds.push(['Amoxicillin - 500mg']);

// When they sync...
// Both arrays merge perfectly. No merge conflicts.
console.log(meds.toArray());
// Result: ['Ibuprofen - 200mg', 'Amoxicillin - 500mg']

We don’t need a smart backend API. We just need a “dumb” relay server to pass these encrypted binary blobs between devices. The server doesn’t even know what the data is.

Privacy by Design

This architecture solves the biggest issue in MedTech: trust.

If you encrypt the CRDT updates on the client side before sending them to the sync server (end‑to‑end encryption), the server literally cannot read your health data. It’s just shuffling meaningless bytes.

I wrote more about these architectural patterns and tech guides on my other blog, where I dive deeper into how we can structure better digital wellness tools.

It’s Not Perfect (Yet)

Local‑first is still bleeding edge.

  • Large datasets: Loading a 5 GB medical imaging history into the browser via WASM isn’t quite there yet (though we’re getting close).
  • Migrations: Schema changes on a client‑side DB are tricky. You have to write migration scripts that run on the user’s device, not on your server.

But honestly? The trade‑offs are worth it.

We need to stop treating health data like social‑media posts living on a server farm in Virginia. It’s your body; it should be your database.

Have you tried building with sqlite-wasm or generic CRDTs yet? Let me know in the comments. I’m still figuring out the best way to handle blob‑storage sync!

Happy coding! 🏥💻

Back to Blog

Related posts

Read more »

LocalFirst: You Keep Using That Word

Article URL: https://www.deobald.ca/essays/2026-01-01-localfirst-you-keep-using-that-word/ Comments URL: https://news.ycombinator.com/item?id=46506957 Points: 1...