{"thread":{"id":"L5nXPYTIHvr-","title":"Is SQLite actually enough for a small web app?","content":"I keep reading both \"just use SQLite\" and \"you'll regret it\". Small app, a few hundred users at most, single VPS, mostly reads. It's a Node process. Postgres feels like one more thing to run and back up, but I don't want to migrate in six months.\n\nWho's run SQLite in production and what bit you?","category":"general","status":"open","author":"sam","createdAt":"2026-09-30T00:42:14.234Z","updatedAt":"2026-11-04T16:31:14.234Z","expiresAt":null,"replyCount":17},"replies":[{"id":"ICQm1ujDj-GY","content":"Not a web app, but my sensor box writes every few seconds and has for years. WAL mode, busy_timeout set, no problems. Use .backup or VACUUM INTO for backups and not cp.","author":"dan_r","createdAt":"2026-09-30T00:56:14.234Z"},{"id":"0SP2ZDugP5tl","content":"I run Postgres for a living and I'd say use SQLite for this. The question that matters is how many writers and whether they hold transactions open. One writer at a time, so if you do a slow thing inside a write transaction, everyone queues behind you.\n\nThings that bite:\n1. Start write transactions with BEGIN IMMEDIATE. A read transaction that upgrades to a write can fail with SQLITE_BUSY straight away, and the busy timeout doesn't help you there.\n2. No network filesystems, WAL needs shared memory on one host.\n3. ALTER TABLE is limited. Migrations that change a column mean creating a new table and copying.","author":"marta_k","createdAt":"2026-09-30T01:22:14.234Z"},{"id":"ar1KWlVIGY3J","content":"Oh good, a sensible answer. Now watch somebody suggest kubernetes.","author":"vek","createdAt":"2026-09-30T01:24:14.234Z"},{"id":"H5w8-ZQo1U9U","content":"Point 1 is news to me, I'd never have found that. Does better-sqlite3 do that for you?","author":"sam","createdAt":"2026-09-30T01:45:14.234Z"},{"id":"6ymZTuN8nmTB","content":"Its transaction() helper has .immediate() on it. The default is deferred. node:sqlite you'd write it yourself.","author":"nils","createdAt":"2026-09-30T02:03:14.234Z"},{"id":"3jZhq40JNBaO","content":"You'll want a second app server eventually and then you're stuck.","author":"gcfail","createdAt":"2026-09-30T03:30:14.234Z"},{"id":"Go4mjvHQSjcl","content":"A few hundred users.","author":"vek","createdAt":"2026-09-30T03:35:14.234Z"},{"id":"2_qUxh5aqTw6","content":"Today.","author":"gcfail","createdAt":"2026-09-30T03:46:14.234Z"},{"id":"9Y0-pAan9EcT","content":"If it gets there, migrating to Postgres is a known week of work, and you've saved a couple of years of running it. The bigger trap is type sloppiness. SQLite will let you put text in an INTEGER column and the Postgres import will then find every one of those. Add CHECKs or use STRICT tables from the start.","author":"marta_k","createdAt":"2026-09-30T04:03:14.234Z"},{"id":"RSb-3dB2GHVQ","content":"Ok this has been really useful. Going with SQLite, WAL, BEGIN IMMEDIATE on writes. One more: backups. Is a nightly .backup to another machine enough?","author":"sam","createdAt":"2026-09-30T08:40:14.234Z"},{"id":"kj5TS9ixjmq-","content":"It's what I do. Test a restore or you don't have a backup.","author":"dan_r","createdAt":"2026-09-30T08:53:14.234Z"},{"id":"DQ6s5DYi7gB8","content":"If losing a day of writes would hurt, look at Litestream, it ships the WAL to object storage continuously. Haven't needed it but people I trust like it.","author":"rproxy","createdAt":"2026-09-30T09:29:14.234Z"},{"id":"-vCHXGhf4mkX","content":"Nobody's said it so I will: most of what you want from a database at this size is one file you can copy somewhere and open with a CLI.","author":"carcdr","createdAt":"2026-09-30T10:02:14.234Z"},{"id":"Ljhk6muu6_-U","content":"counterpoint, had a corrupted sqlite db on a VPS whose provider had a flaky disk. postgres wouldn't have saved me either but you do feel alone. PRAGMA integrity_check in your backup job, please","author":"Anonymous","createdAt":"2026-09-30T13:20:14.234Z"},{"id":"4jdiHBHFVxIh","content":"Update about ten days in. It's been boring, which is good. Hit one locked database error, it was my own fault, a batch import holding a transaction during an HTTP call. marta_k called that one exactly.","author":"sam","createdAt":"2026-10-03T16:14:14.234Z"},{"id":"ChKAVP7rNSwn","content":"Happens in Postgres as well, just with more connections piling up before you notice.","author":"marta_k","createdAt":"2026-10-03T16:26:14.234Z"},{"id":"XAtk6HBGztWR","content":"Still on it. See the STRICT tables thread, I ended up doing that as well.","author":"sam","createdAt":"2026-11-04T16:31:14.234Z"}]}