Picking a Database for Small Apps: SQLite vs Postgres vs Supabase
- #comparison
- #setup
The database choice for a side project rarely matters as much as people agonize over it. What actually differs between these three is the setup cost and mental overhead, and that’s what determines whether you finish the project or not.
SQLite (and Turso)
SQLite is a single file. No server to provision, no connection string, nothing that can go down independently of your app. You add a database by adding a file. For a project with a handful of users and no serious concurrent writes, this removes an entire category of decisions before you’ve written a single feature.
Turso extends SQLite with edge replication if you eventually need the database close to users in multiple regions, without losing the “it’s just a file” simplicity. Reads hit a nearby replica, writes go to a primary, the library handles the routing.
The limitation is real, not theoretical: SQLite locks the whole file for writes rather than doing row-level locking, so it handles concurrent writes worse than a server database. Rarely bites a side project, but the ceiling exists. And if you’re deploying somewhere with an ephemeral filesystem, most serverless platforms wipe local disk between invocations, you need Turso or a persistent volume, not a literal local file.
Postgres
The default “real database” for a reason. JSON columns when you want schema flexibility without giving up SQL, full-text search built in, extensions like pgvector and PostGIS for the stuff you didn’t know you’d need yet. If you’re unsure what your app needs in a year, Postgres is the safe bet, because it rarely says no to a feature later.
Neon and Supabase’s managed Postgres remove the “someone has to patch and back this up” burden that used to make Postgres feel heavy for something small. You get a connection string, not a server to babysit.
Connection pooling is the actual gotcha once you’re on serverless functions: a burst of invocations can each try to open a new connection and blow through a small connection limit fast. PgBouncer or a pooling-aware driver fixes it, but it’s one more concept you now need to know exists. You’re also running a migration tool from day one, since “just edit the file” stops being an option.
Supabase
Postgres plus a hosted layer: auth, storage, RLS policies, a generated REST and realtime API on top of your tables. If you want to skip writing a backend API layer entirely, this is a legitimately fast way to ship a full-stack app solo. I’d go further than that: for a solo builder, skipping the API layer is the actual selling point here, not a nice-to-have.
The catch is that RLS takes real getting used to. The rules deciding who can read which row live in the database, in SQL, not in application code you debug with breakpoints, and it’s genuinely powerful once it clicks, Postgres enforcing data access instead of hoping every endpoint remembered to check. But get a policy wrong early on and you either lock out legitimate users or leak data you meant to protect, and it’s not always obvious which mistake you made until something breaks in a way that’s hard to reproduce.
My honest advice: start without RLS turned on while you’re still shaping the data model, and add policies once the shape has settled, rather than trying to get every policy right from the first migration. Test them deliberately once they exist. The happy path working means nothing.
Worth knowing too: Supabase’s free tier can start to look limiting faster than you’d expect, even for a small app, mostly around database size and pausing inactive projects. For something genuinely tiny or close to static, I’d look at Neon’s free Postgres tier instead, or even in-memory SQLite if you’re comfortable seeding it fresh on every deploy. Either gets you started without worrying about a free-tier ceiling before you’ve even shipped.
ORMs and how they change per option
Prisma and Drizzle both talk to any of the three, but the experience isn’t identical. Drizzle’s SQL-like query builder maps cleanly onto SQLite’s simpler feature set and stays fast on serverless cold starts, since it doesn’t need to load a large query engine. Prisma’s schema-first workflow shines more on Postgres, where you’re actually using the extra features (JSON columns, full-text search) it has first-class support for. Neither pairing is a hard rule, plenty of projects mix Prisma with SQLite and it works fine, but if you’re picking both a database and an ORM at the same time, matching Drizzle to SQLite and either ORM to Postgres is the path with the fewest surprises.
Start with SQLite unless you already know better
If you’re not sure the project will have real users yet, start with SQLite or Turso and migrate later; you’ll have spent zero time on database ops in the meantime. Reach for Supabase specifically to skip the API layer, and go in accepting you’ll spend a weekend actually learning RLS instead of skimming it. Reach for plain Postgres when you want full backend control without running the server yourself.
One thing I’d skip entirely: benchmarking all three before writing any actual feature. I’ve done it, and the numbers didn’t change which one I picked.