For a while now Turso has supported Embedded Replicas, which is the ability to embed your Turso database locally and read from it with zero network latency, even while offline. But one of the most common requests from Turso users has been to be able to write to the local replica as well.
Today Turso is announcing a private beta for Offline Writes — the ability to write to your local replicas first, whether they are online or offline, and have your data automatically synced to the Cloud.
This capability opens up a whole host of new possibilities with Turso, including the ability to build robust Local First applications with a simplified database backend. Especially when you pair it with multi-tenancy, such as a database-per-user architecture.
The private beta will start next week. If you are interested in joining the private beta, request access and let us know about your use case.
Historically, SQLite has been a database that excels at running local, embedded databases, because the database is just a file. For mobile devices, this means on-device databases. For servers, this means a fast and scalable database that does away with the need for any caches and makes any N+1 issues go away.
Turso takes advantage of this with Embedded Replicas: your local embedded databases, on device or server can now be kept in sync with your Cloud database, and any changes are propagated to all replicas.
But up until today, our sync was unidirectional: while you can always read from the database, even if offline, writes happen directly to the Cloud and are propagated later. This has two consequences:
With today’s announcement, the local database will be able to accept writes that are fast as a file and work offline, and are later synchronized to the Cloud.
One of the things Offline Writes unlock is the ability to create on-device local-first applications (in-browser is planned for the future). Local First architectures allow for fast and responsive applications that are resilient to network failures.
Compared to other local first applications, Turso’s architecture allows for a simpler solution because it always synchronizes the entire database. When paired with a centralized big database, local first sync engines need to be smart about which data to transfer. With Turso’s multitenant architecture, you can control which data goes into which database, and then transfer the entire database to the device.
This makes it easier to reason about which data will be present, and gives users a familiar SQL approach to data manipulation.
SQLite itself already solves the offline writes part because as an in-process database, you can just write locally. However, SQLite itself does not solve the sync problem of getting the writes to the cloud. Sync to cloud is important for durability because fundamentally a write on a device can be easily lost if the device breaks or is lost.
Turso’s Offline Writes feature allows your application to write to the local database, and get your changes into the SQLite Write Ahead Log (WAL). When your application has connectivity, it can push the WAL changes to a remote server on the cloud, guaranteeing durability. Likewise, if your device is out of sync, it can pull WAL changes from the remote server to a local device.
One of the first things people want to know when talking about offline writes is how will Offline Writes handle write conflicts, where two or more writes are performed in different clients. While this is still early and a work in progress, we’re trying to keep it simple with the model of pushing and pulling WAL changes.
This works well in two cases:
As it turns out, a lot of applications that appear multi-writer on the surface can be seen as serialized writers. One simple example is failover systems. You can have multiple writers, but the second will only start if the first fails to begin with.
When there are simultaneous writers, both writers will generate their own WAL changes. For those, we allow the first push to go through, and expose an API that lets the application perform conflict resolution, with a variety of user-defined strategies.
The basic scenario where conflicts are not expected looks as follows:
import { createClient } from '@libsql/client';
const client = createClient({
url: 'local.db',
syncUrl: 'libsql://remote.turso.io',
authToken: '...',
});
await client.execute('INSERT INTO users VALUES (?)', ['Alice']);
await client.execute('INSERT INTO users VALUES (?)', ['Bob']);
await db.sync({ strategy: SyncStrategy.FAIL_ON_CONFLICT });
This is the simplest possible strategy, and because no other writers are expected, sync
will just fail.
Additionally, we plan to support the following conflict resolution strategies:
// Silently discards local changes if there’s a conflict.
await db.sync({ strategy: SyncStrategy.DISCARD_LOCAL });
// Rebase local changes if there’s a conflict - think git rebase
await db.sync({ strategy: SyncStrategy.REBASE_LOCAL });
// Manual conflict resolution - any custom strategy
await db.sync({
strategy: SyncStrategy.MANUAL_RESOLUTION,
conflictResolver: async ({ localData, remoteData }) => {
// Implement your conflict resolution logic here
// Return the resolved data
},
});
Note: This API is subject to change during the beta period.
We are eager to onboard users with interesting use cases that will help us shape the future of Offline Writes and local first development with Turso.
Request access today, and let us know about your use case.