The next evolution of SQLite is here! Read Announcement

In serverless computing, functions are ephemeral, stateless, and geographically distributed. Serverless functions spin up on demand, execute a task, and disappear. However, the functions almost always need access to durable, consistent data, which means talking to a database.
The conventional approach is straightforward: issue a SQL query over the network, wait for a response, and proceed. Every read, every write, every query traverses the network. For read-heavy workloads, this per-query network cost can become a dominant factor in response time.
In contrast, an in-process database like SQLite eliminates those round-trips by colocating the data with the function. But SQLite has traditionally been a poor fit for serverless: it assumes a persistent filesystem, which ephemeral functions do not provide.
Turso's partial sync capability bridges this gap. It replicates a subset of the database into the serverless function for fast local reads, while writes go directly to the cloud for durability and consistency. As a result, you get SQLite-like read performance in all of your serverless functions.
Most databases today support SQL over HTTP for database access in serverless environments. In this model, the serverless function issues SQL queries over HTTP to a database server.
To build intuition around this model, consider a content management system deployed on Vercel Functions. We have a database on a remote server with a schema representing users and posts:
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE posts (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL,
body TEXT NOT NULL,
author_id INTEGER REFERENCES users(id),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
We then have, for example, a page that renders a post together with author name, requiring us to perform a SELECT query with a JOIN operation between the posts and users tables. The content management system will also have a page for publishing new posts, which requires an INSERT operation to the posts table.
The @tursodatabase/serverless driver lets you query your database using the conventional client-server approach. The driver communicates with Turso Cloud using the fetch() API with no platform-specific dependencies. For example, a SELECT query to fetch post title and author name looks as follows:
import { connect } from "@tursodatabase/serverless";
const conn = connect({
url: process.env.TURSO_DATABASE_URL,
authToken: process.env.TURSO_AUTH_TOKEN,
});
const stmt = conn.prepare(`
SELECT p.title, u.name
FROM posts p
JOIN users u ON p.author_id = u.id
WHERE p.id = ?
`);
const post = await stmt.get([123]);
This model works well, especially when the database is co-located near the function's compute region. However, even with optimal database placement, every page render still pays for a network round-trip to fetch the post and its author. For a content-heavy site where the vast majority of requests are reads, this per-query network cost becomes the dominant factor in response time, as illustrated in Figure 1.
Turso's partial sync amortizes this per-query network cost by replicating SQLite database pages into the serverless function itself. Reads execute against the local copy with no network round-trips. Writes go directly to the remote database for durability and consistency. If you've used Cloudflare D1, this model will feel familiar. What the @tursodatabase/vercel-experimental package provides is the same semantics of fast local reads combined with remote writes.
As shown in Figure 2, the driver fetches a subset of the database pages locally to the serverless function, not for every query. Once the pages are local, only updates will incur a network round-trip.
SELECT reads locally against the synced pages. An INSERT is sent directly to the remote Turso database (green dot).The @tursodatabase/vercel-experimental package implements this approach.
You can install the package with:
npm install @tursodatabase/vercel-experimental
You then need to configure the following environment variables in your Vercel project:
| Variable | Description |
|---|---|
TURSO_API_TOKEN | API token for your Turso account |
TURSO_ORG | Your Turso organization slug |
TURSO_GROUP | The database group to scope access to |
TURSO_DATABASE | The name of the database to use |
To mint an API token, use the Turso CLI:
turso auth api-tokens mint my-vercel-token
Then, find your organization slug:
turso org list
Finally, add the environment variables to your Vercel project as secrets:
vercel env add TURSO_API_TOKEN
vercel env add TURSO_ORG
vercel env add TURSO_GROUP
vercel env add TURSO_DATABASE
Note: The
TURSO_GROUPenvironment variable scopes all database access to a single group. Even though the API token grants access to the entire organization, an attacker who controls the database name can only access databases within the configured group. Of course, the database name is still recommended to be stored as a secret environment variablewhen possible.
With this setup, the serverless function can now execute read queries locally:
import { createDb } from "@tursodatabase/vercel-experimental";
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const id = searchParams.get("id");
const db = await createDb(process.env.TURSO_DATABASE);
try {
const result = await db.query(`
SELECT p.title, p.body, u.name
FROM posts p
JOIN users u ON p.author_id = u.id
WHERE p.id = ?
`, [id]);
return Response.json(result.rows[0] ?? null);
} finally {
await db.close();
}
}
Writes are sent directly to the remote Turso database, ensuring immediate durability:
import { createDb } from "@tursodatabase/vercel-experimental";
import { revalidatePath } from "next/cache";
export async function addPost(formData: FormData) {
"use server";
const db = await createDb(process.env.TURSO_DATABASE);
try {
await db.execute("INSERT INTO posts (title, body, author_id) VALUES (?, ?, ?)", [
formData.get("title"),
formData.get("body"),
formData.get("author_id"),
]);
} finally {
await db.close();
}
revalidatePath("/posts");
}
When createDb() is called, the driver uses the Turso Cloud API to locate the named database within the configured group. If the database does not yet exist, the driver creates it automatically. The driver then generates a group-scoped JWT token for authentication, which grants access to all databases in the group. Both the resolved database URL and the token are cached in memory for the lifetime of the function execution to avoid redundant API calls.
This auto-provisioning makes it straightforward to implement a database-per-tenant pattern. Instead of routing all tenants through a single shared database, each user — or each AI agent session — gets its own isolated SQLite database. Simply derive the database name from a tenant identifier:
// Per-user database
const db = await createDb(`user-${userId}`);
// Per-agent-session database
const db = await createDb(`session-${sessionId}`);
Because the driver creates databases on demand, there is no provisioning step, no migration to run, and no connection routing to configure. Each tenant's data is fully isolated at the SQLite level, and every database independently benefits from the same partial-sync reads described above.
Each call to createDb() returns a singleton: if the same database name is requested twice within a single function invocation, the second call returns the existing instance. On open, the driver immediately pulls the latest state from the remote database so that subsequent reads reflect the most recent committed data.
By default, db.close() just releases the connection. However, if you opened the database with remote writes disabled, the driver pushes any outstanding local changes to the remote server before closing the connection. To guard against forgotten close() calls, the package registers a callback with Vercel's waitUntil() API. If the database is not closed within 5 seconds of function completion, the driver emits a warning and attempts to push any pending changes. This safety net prevents silent data loss in case of a bug.
On a serverless function cold start, the driver does not download the entire database. Instead, it fetches a small prefix of the SQLite file — by default, the first 128 KiB — which is enough to bootstrap the local copy with the database schema and frequently accessed pages.
Subsequent queries that touch pages not yet present locally trigger on-demand fetches in 128 KiB segments. The driver stores the fetched pages in the serverless function's ephemeral /tmp directory, so they survive across invocations within the same execution environment. When Vercel recycles the environment and discards/tmp, the next invocation bootstraps again from the 128 KiB prefix. The approach keeps serverless cold-start cost proportional to the working set, not the total database size.
Once pages are local, read queries (db.query()) execute entirely in-process against the local SQLite file. There is no network hop, no connection pool, and no SQL execution on a server — just a direct SQLite read.
Write operations (db.execute()) are not local by default. SQL statements such as INSERT, UPDATE, or DELETE are forwarded directly to the remote Turso database, bypassing the local copy. This approach guarantees that writes are immediately durable and visible to all other readers, at the cost of a network round-trip per write.
However, for workloads that benefit from write batching, the driver also supports a local-write mode (remoteWrites: false). In this mode, the driver applies writes to the local SQLite copy and sets a dirty flag. The accumulated changes are pushed to the remote in a single batch when the db.push() or db.close() methods are called. The batching trades off consistency for lower per-write latency, which can be useful for bulk ingestion or background tasks where eventual visibility is acceptable.
Every network round-trip you eliminate is latency your users never feel. For read-heavy serverless workloads on Vercel Functions, @tursodatabase/vercel-experimental turns database reads into local SQLite reads with network hops amortized. Writes still go to the remote database for durability, giving you the database semantics you expect. Of course, if your runtime lacks even ephemeral filesystem access, @tursodatabase/serverless gives you the same Turso databases over HTTP with zero platform dependencies on serverless.
The @tursodatabase/vercel-experimental package is out today as an experimental release, and we welcome feedback and contributions on GitHub. Please do join our Discord for questions, comments, and feedback.