The tradeoff
When we started building the HalluCase registry and our admin backend, the default choice would have been Postgres. It's what everyone uses. It's battle-tested. It has every feature you could want.
But we chose SQLite. Here's why.
Zero operations
The backend is a single Express process that ships as a Docker container. With SQLite, there's no separate database server to manage, no connection pooling, no replication lag, no schema migration tooling beyond simple SQL.
For a team of one building an internal tool, the operational savings are enormous.
Concurrent writes? Not a problem
The standard objection to SQLite is write concurrency. SQLite serializes writes — only one writer at a time. For our use case, this is fine:
- Contact form submissions: ~10/day
- Newsletter signups: ~50/day
- Admin CRUD operations: ~100/day
- Stats updates: ~1/day
That's approximately zero contention.
What we lose
We lose: row-level locking, hot backups, streaming replication, and the ability to ALTER TABLE without rebuilding (though better-sqlite3 handles this well enough).
We gain: a single file for the entire database, no daemon to monitor, backups via cp, and test databases that are just temp files.
The judgment call
For production at scale, you want Postgres. For a single-operator project where the data fits in a file, SQLite is the right choice. The question isn't "can SQLite handle it?" — it's "will this ever grow beyond what SQLite can handle?" For us, the answer is no.