Why SQLite?
Most databases need a separate server running alongside your app — PostgreSQL, MySQL, MongoDB, Redis all require you to install, configure, and keep a process running. During development that often means Docker, which eats RAM and adds yet another thing that can break.
SQLite is different. It's just a file.
No server, no setup
The database is up when you run pnpm dev. It's down when you hit Ctrl+C. No Docker, no background processes, no ports to manage, no "is the database running?" debugging.
Want to start fresh? Delete the file. Want a backup? Copy the file. It's that simple.
No port juggling
You don't realize how much pain port mapping causes until you stop dealing with it.
With server-based databases like Postgres, each project runs its own database server in Docker — but only one can use port 5432 on your machine. So you remap the second project to 5433, the third to 5434, and track each in a .env file.
Now multiply that across team members, each running different combinations of projects, each with their own Docker Compose port mappings. It becomes a mess of conflicting .env files and "works on my machine" bugs. SQLite has none of this — each project just has its own .db file.
Perfect for building lots of apps
Imagine you want to build 100 small apps to experiment with ideas. With Postgres, that's 100 database servers you'd need running — or one shared server with 100 databases to manage. With SQLite, each app is just a .db file sitting quietly on disk until you need it.
Not a toy
SQLite handles billions of rows. It powers every iPhone, every Android phone, every browser, and every Mac. It's the most deployed database engine in the world — by a wide margin. Your app won't outgrow it.
Production-ready with Turso
SQLite runs inside your app — it's not a standalone server that accepts connections over the network.
That's great for development, but in production you need your database accessible from a cluster of hosted servers. That's where Turso comes in — it runs SQLite as a hosted service. Your Drizzle queries don't change; you just point to a different URL. Free tier included.
The right trade-off
Could you use Postgres? Sure — it handles more concurrent writes and has more features. But for the vast majority of web apps, SQLite is simpler: no Docker during development, no port conflicts, no connection pool tuning.
In production with Turso, you get a hosted database like any other — but your local dev experience stays frictionless.
You can always migrate later — Drizzle ORM abstracts the SQL dialect, so switching means changing your connection config, not rewriting queries. Most apps never need to.
Advanced: How it compares
| SQLite | PostgreSQL | MySQL | |
|---|---|---|---|
| Server required | No — just a file | Yes | Yes |
| Architecture | In-process, embedded | Multi-process (fork per connection) | Multi-threaded (thread per connection) |
| Concurrency model | Single-writer, multiple-reader (WAL mode) | MVCC, concurrent writers | MVCC (InnoDB), concurrent writers |
| Storage engine | B-tree, single file | Heap tables + separate indexes | Clustered index (InnoDB B-tree) |
| UPDATE behavior | Copy-on-write (new row version) | Row duplication + VACUUM | In-place update + rollback segments |
| Replication | Turso (LiteFS, managed) | Streaming WAL replication | Binary log + redo log (separate) |
| JSON support | JSON functions, json_extract | JSONB with indexing, operators | JSON type with indexing |
| Full-text search | FTS5 extension | Built-in tsvector/tsquery | InnoDB FTS |
| Best for | Read-heavy, most web apps | Heavy concurrent writes, analytics, GIS | High-connection transactional workloads |
Where the others win
PostgreSQL is the right choice when you need heavy concurrent writes (SQLite serializes all writes through a single writer), advanced indexing (GIN, GiST, BRIN), PostGIS for geospatial queries, or analytical workloads with complex joins across large datasets. Its MVCC implementation handles multiple writers without blocking readers.
MySQL (InnoDB) excels at high-connection transactional workloads. Its clustered index embeds rows directly in the B-tree, so primary key lookups are a single I/O. In-place updates with rollback segments avoid the garbage collection overhead that PostgreSQL's VACUUM and SQLite's WAL checkpointing both deal with. When you have thousands of concurrent connections, MySQL's threading model uses less memory per connection than PostgreSQL's forked processes (a few MB per thread vs 5–10 MB per process).