Storage Landscape
Pick storage by ownership, query shape, platform, and recovery path. Do not start with the trendiest sync library.
| Store | Best for | Avoid when | Recovery story |
|---|---|---|---|
| SQLite | relational local app state, app documents, desktop/CLI data | many concurrent writers or untrusted DB imports without validation | online backup, VACUUM INTO, copy after checkpoint, export tables |
| Room | Android relational app state with Kotlin APIs | tiny preferences or cache-only data | schema export, migration tests, app-managed export |
| IndexedDB | browser structured data, blobs, offline queues | tiny settings, cross-origin sharing, guaranteed non-eviction without persistence | JSON/blob export, Dexie export/import, server sync |
| OPFS | browser file-like data and worker-heavy writes | user-visible files or broad browser compatibility assumptions | app-managed export to user-selected file |
| JSON/JSONL | append-only logs, configs, transcripts, simple portable files | relational queries, multi-record transactions, high write concurrency | copy file, validate checksum/schema, import line by line |
| Markdown | human-owned notes and knowledge | strict schema or high-volume queries | user can read and edit directly; indexes are derived |
| Secure store | keys, tokens, small secrets | large data sets | platform-specific account/device recovery path |
| Remote DB | shared server truth, collaboration, cross-device sync | offline-only or privacy-first v1 | server backups plus local cache recovery |
SQLite
SQLite is the safest default for app-local relational data because it gives transactions, constraints, indexes, and a single durable file. Use WAL for app-local concurrency, then use the SQLite backup API or VACUUM INTO for live backups.
Use SQLite as an app document when users need one portable file with relational structure. Treat the schema as a file format contract.
Room
Room is the Android default for non-trivial structured local data. It adds compile-time query validation, DAOs, entities, database classes, and migration helpers over SQLite.
Room does not remove storage responsibility. You still need schema export, migration tests, export/import, backup decisions, and encryption/key management.
IndexedDB And OPFS
Use IndexedDB for browser-local structured data. It is asynchronous, transactional, same-origin scoped, supports indexes, and can store blobs. Use a wrapper such as Dexie when you want cleaner transactions, schema versions, and export/import helpers.
Use OPFS when you need file-like browser-local storage, especially from a worker. Do not use OPFS as the only user recovery story because it is origin-private.
Desktop Paths
Desktop apps must choose deliberate data locations:
| Stack | Path rule |
|---|---|
| Tauri | use app config/data paths and plugin permissions; do not scatter DB files in working directories |
| Electron | use app.getPath('userData') with app-specific subdirectories; keep cache/session data separate from user data |
| Native apps | follow OS app data conventions and document backup locations |
File-Backed State
Files beat databases when the user benefits from direct inspection and edits. Good examples include Markdown notes, JSON configs, JSONL transcripts, and attachment folders.
Use a database index next to files when you need fast search. Keep the index rebuildable.