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:
- Auto-detects the platform by checking column headers
- Normalizes instrument names using regex (strip the contract month code)
- Matches to a local instruments table with tick sizes and point values
- 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 (0)