turso / sqlite / database
SQLite at the edge with Turso: when local reads beat Postgres
This site runs on SQLite through Turso. Embedded replicas, the single-primary constraint, the read-your-writes gotcha, and exactly when this beats a managed Postgres.
- Published
- May 28, 2026
- Read
- 4 min
Contents
This very site runs on SQLite. Not Postgres, not a managed cloud database with a connection pooler in front of it, just libSQL through Turso. People raise an eyebrow when we say that, because SQLite has a reputation as the database you use until you have real traffic. That reputation is out of date for a specific shape of workload, and ours fits it perfectly.
The idea: move the read to the app
The interesting feature is embedded replicas. Your application keeps a local SQLite file that syncs from a remote primary. Reads hit that local file, which means they resolve at disk speed with no network hop at all. Writes go to the remote primary, and the local copy catches up on the next sync.
Think about what that does to a content page. Our blog, case studies, and showcase are read constantly and written rarely. With embedded replicas, the constant reads never leave the machine. There is no round trip to a database in another region, no connection pool to exhaust, no cold connection to warm up. The query is a local file read measured in microseconds.
const client = createClient({
url: 'file:local.db',
syncUrl: process.env.TURSO_DATABASE_URL,
authToken: process.env.TURSO_AUTH_TOKEN,
syncInterval: 60,
})Where it genuinely beats Postgres
This is not a "SQLite is secretly better than Postgres" argument. It is narrower than that. SQLite at the edge wins when:
Your workload is read-heavy and the data is read far more often than it changes. Marketing sites, docs, catalogs, dashboards over mostly-static data.
Your users are spread across regions and you care about tail latency. Local reads turn a cross-continent round trip into a disk read.
You want a database per tenant. Turso is built to run a very large number of small databases, which is awkward and expensive on a single Postgres instance.
You are building something local-first or offline-capable, where a real on-device SQLite file is the point.
The constraints you must respect
The architecture has one primary. Every write goes to that single primary, and reads are served from the nearest replica or the local copy. That is the whole model, and it carries two consequences you need to design around.
First, write latency is a real round trip. A write is not local, it travels to the primary. For a read-heavy app that is a fine trade, because writes are rare. For a write-heavy queue or a chat firehose, it is the wrong tool.
Second, and this is the one that catches people, an embedded replica does not instantly see its own write. You write to the primary, but your local copy will not reflect that change until the next sync. If you write and then immediately read from the local replica expecting your own change, you get stale data. The fix is to route reads that must reflect a just-completed write through to the primary, or trigger a sync. We hit exactly this while seeding content: the page kept showing the old data because the cache and the local copy had not caught up yet.
Watch the row-read meter
Turso bills on rows read and rows written, not on queries or connections. That model is generous, the free tier alone covers hundreds of millions of row reads a month, but it changes how you think about a slow query. An unindexed scan that walks a million rows to return ten burns a million reads, not one. The lesson is the same as it always was with SQLite: index the columns you filter on, and watch for accidental full-table scans. The difference is that here the meter makes the cost visible.
For a studio site like this one, the whole thing is close to free and faster than any Postgres setup we could justify. For your next read-heavy, latency-sensitive project, it is worth a serious look. For your next write-heavy transactional system, reach for Postgres and do not feel bad about it.