DEV Community

Cover image for How I built PeerSplit: A free, peer-to-peer expense-splitting app—from idea to launch in just 2 weeks
Tanay Karnik
Tanay Karnik

Posted on • Originally published at tanay.xyz

How I built PeerSplit: A free, peer-to-peer expense-splitting app—from idea to launch in just 2 weeks

I built PeerSplit—a free, peer-to-peer alternative to Splitwise—in just two weeks, from idea to launch!

PeerSplit is a local-first app for splitting group expenses. It works offline, is 100% free and private, and doesn't require signups or any personal data.

Here’s how I built it and everything I learned along the way.

Why PeerSplit?

I’ve relied on Splitwise for years to manage expenses with friends and roommates. But with its recent daily transaction limits and intrusive ads, it’s become frustrating to use.

I wanted a free, privacy-first alternative that didn't require servers to store or sync data. I wouldn't trust my expenses with a third-party server.

After working on peer-to-peer, local-first projects like a workout tracker and a distraction-free writing app, I realized I could apply the same approach to expense splitting.

That’s how PeerSplit was born. I started designing the app.


Building the UI with Nuxt + Nuxt UI

I suck at designing UIs.

A few months ago, I wouldn't have thought I could build a UI as polished as PeerSplit's (some people even say it has better UX than Splitwise).

So, how did I manage to do it? Nuxt UI.

Nuxt UI is gorgeous, and it has amazing developer experience (DX).

It also comes with other useful Nuxt modules like @nuxt/icon, @nuxtjs/tailwindcss, and @nuxtjs/colormode.

All I had to do was pick a primary color, and I had all the components I needed—icons, dark mode, and everything else—to bring PeerSplit’s UI together.


cr-sqlite for local syncing 🔗

For local data storage and syncing, I went with cr-sqlite, which builds on wa-sqlite and uses CRDTs (conflict-free replicated data types).

CRDTs are great for peer-to-peer systems because they handle conflicts automatically—so users can work offline, and when they reconnect, changes merge seamlessly.

However, cr-sqlite doesn't sync changes over the network by itself. It only provides APIs to export and merge changes. You need to manually send those changes between devices.


Gun.js for peer-to-peer syncing 🌐

To handle secure peer-to-peer syncing, I used Gun.js, which provides a peer-to-peer, distributed graph database.

Gun’s gun.user API lets me create encrypted nodes for each group. All changes for a group are stored on that node and synced only with group members, keeping everything private.

When a user performs an action, I take the changes exported from cr-sqlite and push them to the node. When the user comes back online, Gun syncs the new changes, keeping everyone up-to-date.

Implementing this in a performant way was tricky. For more details, you can check out the source code here.


Simplifying debts 💰

One cool feature of Splitwise (and now PeerSplit) is "simplifying debts."

Here’s how it works: If A owes B and B owes C, A can just pay C directly to potentially reduce the number of repayments.

In PeerSplit, I first calculate the net balance for each person. Then I sort those balances and suggest payments one by one to bring at least one person’s balance to zero each time.

This sorting ensures that everyone sees the same repayments on their devices.

It’s not 100% optimal (some groups might still have up to n-1 payments), but it works well in most cases.

An optimal solution would be exponential to calculate and would only save a few payments. So this was the best tradeoff for simplicity and speed!

export const groupGetPayments = (group) => {
  const payments = [];
  const balances = Object.entries(groupGetBalances(group)).map(([a, b]) => [
    b,
    a,
  ]);
  balances.sort();
  let i = 0,
    j = balances.length - 1;
  while (i < j) {
    if (balances[i][0] === 0) {
      i++;
    } else if (balances[j][0] === 0) {
      j--;
    } else if (-balances[i][0] > balances[j][0]) {
      payments.push({
        from: balances[i][1],
        to: balances[j][1],
        value: round(balances[j][0]),
      });
      balances[i][0] += balances[j][0];
      balances[j][0] = 0;
    } else {
      payments.push({
        from: balances[i][1],
        to: balances[j][1],
        value: round(-balances[i][0]),
      });
      balances[j][0] += balances[i][0];
      balances[i][0] = 0;
    }
  }
  return payments;
};
Enter fullscreen mode Exit fullscreen mode

PWA

I wanted PeerSplit to function as an offline app, but I didn’t want to go through the hassle of building multiple native applications or dealing with the lengthy process of publishing them on app stores. So, opting for a Progressive Web App (PWA) was the clear choice.

A PWA combines the best of web and mobile apps, allowing users to install it on their devices while still enjoying offline capabilities.

To transform my Nuxt app into a PWA, I used vite-pwa.
I designed an SVG logo in Figma and used it to generate all the necessary PWA assets through vite-pwa’s asset generator.

After that, I configured the PWA manifest, and vite-pwa automatically set up the service worker for me.

I configured Nuxt to prerender all the routes, so that my app could fully function offline.


And that's a wrap. Thanks for reading!

Product Hunt Launch

PeerSplit just launched on Product Hunt! It's my first launch, and I’d love your support and feedback.

Check out PeerSplit on Product Hunt

PeerSplit is fair-source, so feel free to contribute or submit feature requests on GitHub.

GitHub logo tanayvk / peersplit

PeerSplit is a free, local-first, peer-to-peer app that helps you easily and privately split and track group expenses.

PeerSplit

PeerSplit is a free, local-first, peer-to-peer app that helps you easily and privately split and track group expenses.

Features

  • 💯 100% Free — No sign-up required
  • 🌐 Local-First — Works fully offline
  • 📱 Cross-Platform PWA — Use it on mobile, desktop, or laptop
  • 🔒 Peer-to-Peer — Collaborate with friends while keeping your data private
  • Simple UX — A smooth and minimal interface that stays out of your way
  • 🌙 Dark and Light Modes — Switch between themes to match your preferences
  • 🔄 Import/Export — Import from Splitwise and export data to CSV

Planned Features

  • 🔢 Advanced Bill Splitting — Add an itemized bill as a single expense.
  • 🧾 Bill Scanning — Automatically scan and split bills by taking a picture.
  • 💱 Multi-Currency Support — Handle expenses in different currencies with real-time conversion rates.
  • 📝 Transaction Notes & Comments — Add notes and comments for each transaction to keep…




Top comments (0)