Skip to content

Migrating from Turso to Layerbase

4 min readTursolibSQLSQLiteDatabases

Turso made libSQL easy to host: a managed SQLite-compatible database you reach over the network, with a generous free tier. The catch shows up on real workloads, because the meter runs per row read and per row written. A busy app, an analytics query that scans a table, or a write-heavy job (a trading bot logging ticks, say) burns through the monthly allowance faster than you would expect, and the usual fix is to jump into a higher-limit tier rather than a flat price you can forecast.

If you would rather pay a flat per-instance price for the same database, libSQL on Layerbase Cloud is a direct swap. It is the same libSQL server (sqld) under the hood, so your schema, your SQL, and your @libsql/client code all come along. The only real work is copying your data and pointing the client at a new URL.

Contents

What Actually Changes

libSQL is libSQL on both ends, so most of your stack is untouched. Two things differ:

  • The pricing model. Turso meters per row read and written (with fixed-plan options on top). Layerbase libSQL is flat per instance: the bill is the same whether you read a thousand rows a month or a billion. For anything past a hobby workload, this is the whole reason to move.
  • The connection. You point @libsql/client at a new URL and auth token. That is usually a one-line env change.

What does not change: your tables, indexes, SQL, transactions, and the SQLite semantics your app relies on. The client library stays the same.

One honest note for write-heavy workloads: a flat instance has a fixed memory and CPU budget instead of a request meter. The difference is that you can see it and size it. Layerbase warns you as a database approaches its memory limit and lets you move up a tier or add headroom, rather than capping you at an opaque request count. If you are running something write-intensive, start on a plan with real headroom (Pro) rather than the entry tier.

Set Up libSQL Locally with SpinDB

Stand up libSQL locally first so you can load your dump and verify it before touching anything in production. SpinDB runs it with one CLI, no Docker. (What is SpinDB?)

Install SpinDB:

bash
npm i -g spindb    # npm
pnpm add -g spindb # pnpm

Create a libSQL instance and start it:

bash
spindb create turso-migration -e libsql --start

Get its URL:

bash
spindb url turso-migration
text
http://127.0.0.1:8080

You now have a local libSQL server to load your data into and test against.

Copy Your Data

libSQL speaks SQLite, so the cleanest path is a SQL dump. Export your Turso database to a .sql file:

bash
turso db shell your-database ".dump" > turso-dump.sql

That file is plain SQL: CREATE TABLE statements followed by INSERTs. As a sanity check, a tiny schema looks like this:

sql
CREATE TABLE trades (
  id INTEGER PRIMARY KEY,
  symbol TEXT NOT NULL,
  side TEXT NOT NULL,
  price REAL NOT NULL,
  created_at TEXT NOT NULL DEFAULT (datetime('now'))
);

INSERT INTO trades (symbol, side, price, created_at)
VALUES ('BTC-USD', 'buy', 64250.0, '2026-06-28T17:00:00Z');

Load the dump into your local SpinDB instance to confirm it restores cleanly:

bash
spindb shell turso-migration < turso-dump.sql

Verify the row counts match what Turso reported:

sql
SELECT count(*) FROM trades;
text
count(*)
--------
1

Once the local copy checks out, you load the same dump into your Layerbase database (next section).

Point Your App at Layerbase

Create a libSQL database on Layerbase Cloud. The dashboard gives you a connection URL (an https://...cloud.layerbase.dev host) and an auth token. Load your dump into it the same way, then change your app's connection.

If your code looks like this on Turso:

ts
import { createClient } from '@libsql/client'

const client = createClient({
  url: process.env.TURSO_DATABASE_URL!,
  authToken: process.env.TURSO_AUTH_TOKEN!,
})

The only change is the two environment variables:

bash
# Before (Turso)
TURSO_DATABASE_URL=libsql://your-db.turso.io
TURSO_AUTH_TOKEN=...

# After (Layerbase)
LIBSQL_DATABASE_URL=https://your-db.cloud.layerbase.dev
LIBSQL_AUTH_TOKEN=...
ts
const client = createClient({
  url: process.env.LIBSQL_DATABASE_URL!,
  authToken: process.env.LIBSQL_AUTH_TOKEN!,
})

Your queries, transactions, and migrations are unchanged. It is the same client talking to the same kind of server.

What to Test

Before you cut over, confirm:

  • Row counts match for every table, between Turso and the new database.
  • Your hot queries run and return the same results.
  • Write throughput holds under your real load. This is the one to watch if Turso's limits were your reason for leaving. Run your actual write pattern (the bulk insert, the tight logging loop) and confirm latency stays flat. If you see memory pressure, Layerbase tells you, and you size up a tier instead of hitting a request wall.
  • Auth and connection limits behave the way your app expects from the new URL.

The Managed Path: Layerbase Cloud

Spin up managed libSQL on Layerbase Cloud and you get a flat per-instance price with no per-row meter, daily backups, and your choice of scale-to-zero (it sleeps when idle and wakes on the next connection) or always-on (pinned, never sleeps). The same libSQL you already build against, priced so a busy month does not surprise you.

If you want to prototype locally first, SpinDB runs the identical engine on your machine, so you can develop against a local libSQL and deploy the same schema to the cloud when you are ready.

Something not working?