September 28, 2023
SQLite on the Edge: Prisma Support for Turso is in Early Access
Turso is an edge-hosted, distributed database that's based on libSQL, an open-source and open-contribution fork of SQLite, enabling you to bring data closer to your application and minimize query latency.
We’re excited to share that Prisma ORM is adding Early Access support for Turso. Let’s dive in!
What is Turso, and how is it different from SQLite?
SQLite is a self-contained, file-based open-source database known for its portability, reliability, and performance, even in environments with low memory. It’s also the perfect fit for small web applications because of its speed.
However, scaling SQLite introduces challenges:
- Manual backups
- Lack of out-of-the-box replication
- Difficulties persisting data in serverless environments
- Hosting difficulties in multi-server setups
Some of these challenges can be solved by tools such as LiteFS, which provides replication and database backups for SQLite.
On the other hand, Turso solved the above challenges by creating libSQL — a fork of SQLite that adds features that SQLite does not support yet. libSQL allows you to distribute and replicate SQLite, connect over HTTP, perform async operations, and embed SQLite as part of other programs as a primary database or replica of the primary database.
Prisma + Turso = 🚀
While Prisma has supported SQLite from its first release in 2019, libSQL differs from SQLite. For example, libSQL uses HTTP to connect to the database and uses a remote file over a local one, making Prisma and Turso incompatible until now.
Today, we’re excited to announce that Prisma support for Turso is now available in Early Access!
import { PrismaClient } from '@prisma/client'import { PrismaLibSQL } from '@prisma/adapter-libsql'import { createClient } from '@libsql/client'
// Create a new instance of the libSQL database clientconst libsql = createClient({ // @ts-expect-error url: process.env.TURSO_DATABASE_URL, authToken: process.env.TURSO_AUTH_TOKEN})
// Create a Prisma "adapter" for libSQLconst adapter = new PrismaLibSQL(libsql)// Pass the adapter option to the Prisma Client instanceconst prisma = new PrismaClient({ adapter })
export default prisma
Get started with Prisma and Turso
To start using Turso in your project, you must first enable the driverAdapters
Preview feature flag. This will allow you to query your database using Turso’s driver adapter.
The
driverAdapters
feature flag is part of the driver adapter initiative we're working on to enable you use other database drivers to connect to your database. Example driver adapters include PlanetScale, Neon, and libSQL. We’ll share more details soon! If you have not yet, fill out this survey, and leave us your email address for updates.
Prerequisites
You will need to have the following tools installed:
You can set up a project using try-prisma
if you don’t have an existing project using SQLite. Navigate to your working directory and copy the command below to set up the project:
npx try-prisma@latest --template typescript/script --path . --name turso-prisma --install npmCopy
Navigate to the project and open it on your preferred code editor:
cd turso-prismaCopy
Create a database on Turso
First, create a database on Turso that your application will use. This step is necessary for creating the credentials required to configure Turso’s database client.
To create a database, run the following command on your terminal:
turso db create turso-prismaCopyTurso will also create a database in the region closest to your location.
Create an authentication token that will allow you to connect to the database:
turso db tokens create turso-prismaCopyNext, reveal the connection string details to connect to your database:
turso db show turso-prismaCopyTake note of the authentication token and connection string which will be used to connect to your database in the next step.
Connect to Turso using Prisma
To get started using Turso:
Enable the
driverAdapters
Preview feature flag in your Prisma schema:generator client {provider = "prisma-client-js"previewFeatures = ["driverAdapters"]}datasource db {provider = "sqlite"url = "file:./dev.db"}CopyCreate or update your
.env
file with the environment variables with the values from the “Create a database on Turso” step:TURSO_AUTH_TOKEN="eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9..."TURSO_DATABASE_URL="libsql://turso-prisma-random-user.turso.io"CopyCreate the initial migration:
npx prisma migrate diff --from-empty --to-schema-datamodel prisma/schema.prisma --script > migration.sqlCopyApply the migration to your Turso database:
turso db shell turso-prisma < migration.sqlCopyInstall the latest version of Prisma Client:
npm install @prisma/client@latestCopyInstall the libSQL database client and the driver adapter for Prisma Client:
npm install @prisma/adapter-libsqlnpm install @libsql/clientCopyUpdate your Prisma Client instance with the following snippet:
import { PrismaClient } from '@prisma/client'// 1. Import libSQL and the Prisma libSQL driver adapterimport { PrismaLibSQL } from '@prisma/adapter-libsql'import { createClient } from '@libsql/client'// 2. Instantiate libSQLconst libsql = createClient({// @ts-expect-errorurl: process.env.TURSO_DATABASE_URL,authToken: process.env.TURSO_AUTH_TOKEN})// 3. Instantiate the libSQL driver adapterconst adapter = new PrismaLibSQL(libsql)// Pass the adapter option to the Prisma Client instanceconst prisma = new PrismaClient({ adapter })Copy
And that’s it!
You can now start querying your Turso database using Prisma Client.
If you cloned the typescript/script
example, you can run npm run dev
to insert and query data to confirm your changes were successful.
Where to go from here
The setup above uses a single remote database. You can take it a step further by setting up database replicas. Turso automatically picks the closest replica to your app for read queries when you create replicas. No additional logic is required to define how the routing of the read queries should be handled. Write queries will be forwarded to the primary database.
Try it out and share your feedback! We encourage you to create an issue if you find something missing or run into a bug.
Beyond remote SQLite: embedded replicas
While Turso allows you to replicate SQLite globally, what if you could eliminate the extra network hop from your app to the remote replica? What if… you could move the database inside your application?
With Turso's newly announced embedded replicas, you can have a copy of your primary, remote database inside your application, similar to how an embedded/local SQLite database works. You can try this out in your application using Prisma’s new support for Turso.
How do embedded replicas work?
When your app initially establishes a connection to your database, the remote primary database will fulfill the query:
Then Turso will (1) create an embedded replica inside your application and (2) copy data from your primary database to the replica so it is locally available:
The embedded replica will fulfill subsequent read queries. The libSQL client provides a sync()
method which you can invoke to ensure the embedded replica's data remains fresh.
With embedded replicas, this setup guarantees your app is fast because the data will be readily available locally.
Like a read replica setup you may be familiar with, write operations are forwarded to the primary remote database and executed before being propagated to all embedded replicas.
- Write operations propagation are forwarded to the database.
- Database responds to the server with the updates from 1.
- Write operations are propagated to the database replica.
Impact of embedded replicas on your queries
To demonstrate the speed of embedded replicas, we created two example apps using the same primary database - and one variant uses an embedded replica.
For this sample test, the following query will be used:
await prisma.post.findMany({ where: { published: true }, select: { id: true, title: true, author: true // relation }})
Below are screenshots of the timings for the same query on application startup:
Without an embedded replica:
Time: 154.390 ms
With an embedded replica:
Time: 7.883 ms
The query response significantly drops from 154.390 ms to 7.883 ms.
If you would like to try it out yourself, you can find the two example apps up on GitHub:
What can you use an embedded replica for?
Embedded replicas are a relatively new feature of Turso, but some of the use cases you could use them for include:
- Improving read performance of your APIs: Embedded replicas can eliminate the network cost of connecting to a remote database server with an embedded database with Prisma Client.
- Replacement for your caching service: Embedded replicas can be paired with a Prisma Client extension for query responses, making your app faster because the data is always kept fresh.
It will be interesting to see what other use cases you will come up with for this new approach to database replicas.
Try it out yourself!
We can't wait to see what you build with Turso and Prisma. We encourage you to try it out and let us know your thoughts.
We’re also working on supporting other serverless database drivers and edge function deployments. Take this short survey and leave us your email address for updates.
Be sure to share with us what you build on Twitter or Discord. 🙌