Command PaletteSearch for a command to run...
0
Blog
PreviousNext

Zelife: An Offline-First Finance PWA with Gist Sync

How I built Zelife — an offline-first personal finance and task manager that runs without a backend database and backs up to private GitHub Gists.

Most budgeting apps have the exact same playbook: sign up for an account, send all your personal transaction history to their servers, pay a monthly subscription, and watch the app freeze the second you are on airplane mode or have spotty internet.

I wanted something different for myself: a fast PWA that works completely offline, has zero tracking, supports both Solar Jalali and Gregorian dates, and lets me own my data.

That is why I built Zelife (GitHub repository).

The Local-First Idea

Instead of treating a remote server as the source of truth, Zelife stores everything directly on your device using localStorage and IndexedDB.

  • Adding an expense or checking off a task takes zero network requests. The UI updates instantly through Zustand.
  • The app works 100% offline via service workers.
  • There is no central user database to breach or maintain.

Using Private GitHub Gists as a Free Cloud Backup

The biggest question with offline-first apps is: how do you sync across devices without hosting a paid database?

My solution was using GitHub Gists.

Almost every developer already has a GitHub account. GitHub provides a reliable API for Gists, supports private visibility, and scopes permissions cleanly to the gist token scope.

Here is the sync flow in practice:

  1. The user creates a GitHub Personal Access Token with only the gist permission.
  2. Zelife encrypts and serializes the local transactions and tasks into a single JSON snapshot.
  3. It pushes the snapshot to a private Gist named zelife_backup.json.
  4. On another device, the user enters their token, and Zelife fetches the Gist, checks timestamps, and merges changes into local storage.

Here is a simplified snippet of how the Gist client handles backups:

export interface SyncPayload {
  version: number
  updatedAt: string
  finances: FinanceRecord[]
  tasks: TaskRecord[]
}
 
export async function backupToGist(
  token: string,
  gistId: string | null,
  data: SyncPayload
): Promise<string> {
  const headers = {
    Authorization: `Bearer ${token}`,
    Accept: "application/vnd.github+json",
    "Content-Type": "application/json",
  }
 
  const payload = {
    description: "Zelife Private State Backup",
    public: false,
    files: {
      "zelife_backup.json": {
        content: JSON.stringify(data, null, 2),
      },
    },
  }
 
  // Create new private Gist if it is the first time syncing
  if (!gistId) {
    const res = await fetch("https://api.github.com/gists", {
      method: "POST",
      headers,
      body: JSON.stringify(payload),
    })
    if (!res.ok) throw new Error("Could not create Gist backup")
    const result = await res.json()
    return result.id
  }
 
  // Update existing Gist
  const res = await fetch(`https://api.github.com/gists/${gistId}`, {
    method: "PATCH",
    headers,
    body: JSON.stringify(payload),
  })
  if (!res.ok) throw new Error("Could not update Gist backup")
  return gistId
}

This keeps server hosting costs at literally zero dollars while giving users complete ownership of their data.

Dealing with Solar Jalali & Gregorian Calendars

Personal finance relies heavily on monthly budgets, recurring bills, and date ranges. In Iran, the Solar Jalali calendar is standard, while international platforms use Gregorian.

To make this seamless:

  • Every transaction is stored in UTC ISO 8601 strings internally.
  • Date math for monthly boundaries is calculated via an adapter layer. If the user selects Jalali, the month ranges calculate based on 30/31-day Jalali rules. If they switch to Gregorian, the view recalculates on the fly without modifying the underlying data.

PWA & Daily Use

Zelife is configured with a Web App Manifest and service worker cache. On mobile (iOS Safari and Android Chrome), you can tap "Add to Home Screen" to install it. It launches full-screen with no browser address bar and works whether you have 5G or zero connectivity.

You can try it live at zelife.ir or check out the code on GitHub.