Web to Desktop
App data & updates
Lesson 4 of 5
What you'll learn
- Locate per-user app storage with
app.getPath("userData") - Persist settings safely: defaults merge, atomic writes, or
electron-store - Follow the auto-update flow from feed check to
quitAndInstall
A desktop app can't lean on a server database for its own preferences — it needs a home on disk. Electron gives every app one: app.getPath("userData") returns a per-user, per-app directory (~/Library/Application Support/YourApp on macOS, %APPDATA%\YourApp on Windows, ~/.config/YourApp on Linux). Settings, caches, and local documents all belong under it.
const { app } = require("electron");
const path = require("node:path");
const settingsPath = path.join(app.getPath("userData"), "settings.json");
The settings pattern
The classic pattern is a single JSON file with three rules. Merge over defaults when loading, so a missing file or a setting added in v2 both just work. Write atomically — serialize to a temp file, then rename over the real one — so a crash mid-write can't leave half a JSON file. Read and write in main, exposing settings:get/settings:set over IPC, so there's one owner and no write races between windows.
The electron-store package is that exact pattern productized: same JSON-in-userData layout, plus atomic writes, dot-path accessors, JSON-schema validation, and migrations. Hand-roll the file for two keys; reach for electron-store once settings grow real structure.
Never write inside the app itself
The installed app bundle is read-only in practice — and on macOS, modifying it breaks the code signature. Anything the app writes at runtime goes in userData (or temp/logs, also available via app.getPath).
The auto-update loop
Auto-update, at a high level, is four steps. The de-facto tool is electron-updater (from the electron-builder project), which reads a feed file (latest.yml) published next to your installers on GitHub Releases, S3, or any static host:
- On launch,
autoUpdater.checkForUpdatesAndNotify()fetches the feed and compares versions. - If newer, the update downloads in the background while the user works.
update-downloadedfires; you nudge the user ("Restart to update") or stay silent.autoUpdater.quitAndInstall()swaps the app and relaunches it.
const { autoUpdater } = require("electron-updater");
autoUpdater.checkForUpdatesAndNotify();
autoUpdater.on("update-downloaded", () => {
// e.g. show a "Restart now?" dialog, then:
autoUpdater.quitAndInstall();
});
One platform rule to know now (it shapes the next lesson): macOS refuses to install updates into an unsigned app — code signing isn't optional once you auto-update.
The challenge is a JS model of the settings store: load-merge-save round-trips against an in-memory "disk," with the atomic temp-then-rename dance spelled out.
Run it. First load falls back to defaults, save writes temp-then-rename, and the second load round-trips the change.
Why write settings to a temp file and then rename it over the real one?
Next: turning the project into something you can hand to users — packaging, signing, notarization, and publishing releases.
Saved on this device. Sign in to sync your progress everywhere.