close

DEV Community

William
William

Posted on

How I Built a Desktop Trading Journal with Electron, React, and SQLite

Last week I shipped a desktop app called Aurafy. It's a trading journal for futures traders that runs entirely locally. No cloud, no accounts, no subscription. I wanted to share the technical decisions behind it because I think the "local first" approach is underrated for tools that handle sensitive financial data.

The Stack

The app is built as a monorepo with three pieces:

Server: Express.js + better-sqlite3. The server runs inside the Electron process (no child process spawn, which cuts startup time to under 2 seconds). SQLite with WAL mode handles all persistence. Every write uses synchronous = FULL because losing trade data is unacceptable.

Client: React + Vite + Tailwind CSS + Recharts. Standard SPA that talks to the Express server over localhost. TanStack Query handles all data fetching and caching.

Electron wrapper: The main process starts the Express server in-process, opens a BrowserWindow pointing to localhost, and handles native features like screen recording permissions and floating camera windows.

Why Local First?

Trading data is sensitive. Your P&L, your account sizes, your mistakes. Most trading journals upload all of this to their cloud. I didn't want that.

With SQLite, everything lives in ~/Library/Application Support/aurafy/data/journal.db. The user can back it up, move it, or delete it. No API keys, no OAuth flows, no "please log in again."

The tradeoff is no cross-device sync. But for a trading journal that you use at your desk, this hasn't been an issue. Traders don't journal on their phones.

Screen Recording in Electron

One feature I'm proud of is the built-in screen recorder. Traders record their sessions to review later, similar to how athletes watch game film.

Electron's desktopCapturer API provides screen capture. I combine it with getUserMedia for microphone input, mix both audio streams using the Web Audio API, and feed everything into a MediaRecorder.

The camera overlay is a separate BrowserWindow with transparent: true, alwaysOnTop: true, and frame: false. It floats above all apps like Loom's camera bubble. The HTML is dead simple: a circular div with a video element showing the webcam feed.

CSV Import with Auto-Detection

Traders export their data from platforms like Tradovate and NinjaTrader as CSV files. The challenge is that every platform uses different column names, date formats, and instrument naming conventions.

Tradovate calls the instrument "MNQM6" while NinjaTrader calls it "MNQ 06-26". Both mean the same thing: Micro Nasdaq futures, June 2026 contract.

I wrote a parser that:

  1. Auto-detects the platform by checking column headers
  2. Normalizes instrument names using regex (strip the contract month code)
  3. Matches to a local instruments table with tick sizes and point values
  4. Pairs entry/exit executions and calculates P&L

The whole import flow is: drop CSV → see preview with detected trades → confirm. No manual mapping.

Lessons Learned

Run the server in-process. My first version spawned a Node child process for the Express server. This added 3+ seconds to startup and caused issues with macOS code signing (the OS saw it as two separate apps). Running Express inside Electron's main process using require() fixed both problems.

SQLite's WAL mode matters. Without it, writes block reads. With journal_mode = WAL and synchronous = FULL, you get concurrent reads during writes and guaranteed durability on crash.

Electron auto-update is fragile. electron-updater creates draft releases on GitHub, which means download URLs return 404 until you manually publish them. I added a CI step that auto-publishes after both Mac and Windows builds complete.

ELECTRON_RUN_AS_NODE will haunt you. If this env var is set (which it is in some development environments), Electron runs as plain Node.js and require('electron') returns a string instead of the module. I spent hours debugging this.

Try It

Aurafy is free and available at aurafy.dev. The code handles futures contracts (ES, NQ, CL, MES, MNQ) with proper point values and tick sizes.

If you're interested in the Electron + Express + SQLite architecture pattern, happy to answer questions in the comments.

Top comments (4)

Collapse
 
admin_chainmail_6cfeeb3e6 profile image
Admin Chainmail

Nice work! Fellow Electron dev here. I built a desktop Gmail client called ChainMail.

Some things I learned that might save you time:

Auto-update: test the update flow early. electron-updater + a simple file server (I use Cloudflare R2) works well, but the first time you ship a broken auto-update, you have stranded all your existing users on an old version.

Code signing on Mac is painful but worth it. Unsigned apps get quarantine warnings that kill conversion.

SQLite concurrency: if you are doing writes from multiple windows or IPC channels, WAL mode is your friend.

How are you handling data sync? Pure local SQLite, or is there a cloud backup component planned? Curious because for trading data, losing your journal to a disk failure would be brutal.

Collapse
 
william_b898ff4ee6a7e992f profile image
William

Thanks, appreciate the real feedback.

Auto-update: learned this the hard way actually. Started with GitHub Releases but moved to Cloudflare R2 for the update feed after running into issues with private repos. The generic provider in electron-updater pointing to a Worker that serves latest.yml from R2 has been solid so far.

Code signing: yeah this is killing my conversion right now. Mac users get "file is damaged" and Windows gets SmartScreen warnings. On the list but haven't pulled the trigger on the $99/year Apple cert yet. How much did it actually move the needle for you on downloads?

SQLite: WAL mode + synchronous=FULL from day one. The server runs in-process inside Electron (no child spawn) so concurrency hasn't been an issue since it's single-user. Flush the WAL on SIGTERM/beforeExit to avoid data loss on quit.

Data sync: pure local right now and honestly that's a feature not a limitation for this audience. Futures traders don't want their P&L, account sizes, and trading psychology data on someone else's server. That said, I'm thinking about adding an encrypted local backup to a user-chosen location (iCloud Drive, Dropbox folder, external drive) so they own the backup too. No cloud component planned.

ChainMail sounds cool, will check it out.

Collapse
 
admin_chainmail_6cfeeb3e6 profile image
Admin Chainmail

Ha, we're running the exact same stack — Cloudflare Worker serving latest.yml from R2, electron-updater generic provider. Rock solid.

Honest answer on code signing: we haven't signed yet either, so I can't give you a before/after number. We're seeing the exact same SmartScreen and Gatekeeper friction. Mac users get the 'damaged file' dialog which kills trust completely. Our current workaround is documentation in the download flow ('how to bypass Gatekeeper'), but it's a terrible UX. The $99 Apple cert is on our list too but at $0 revenue it's hard to justify. Would love to compare notes once one of us pulls the trigger.

Your SQLite architecture sounds solid — WAL + synchronous=FULL with in-process server is the right call for single-user. The encrypted local backup idea is smart for that audience too.

And thanks for checking out ChainMail! We're in beta right now (Google OAuth verification pending). If you want to try it, drop me your Gmail and I'll add you as a test user.

Collapse
 
admin_chainmail_6cfeeb3e6 profile image
Admin Chainmail

Code signing: honestly we haven't pulled the trigger either. Still eating the SmartScreen warnings on Windows. I suspect the drop-off is real though — even dev-savvy users hesitate at that dialog. If you go first on the Apple cert, genuinely curious if it moves the needle.

R2 for auto-update: we landed on the exact same architecture. Worker serving latest.yml from R2. Way cleaner than GitHub Releases for public distribution.

Local-first as a feature — completely agree. For email it's even more clear-cut. Nobody wants their inbox on yet another server. The privacy angle sells itself to the right audience.

Encrypted local backup to a user-chosen location is a great idea. Simple, user-controlled, no cloud dependency. Ship it.

Building desktop apps with Electron in 2026 feels like a very small club. Good to find another member.