Building a Fast Global Serverless API with Turso and endpts
Turso is an edge-hosted, distributed database based on libSQL, a fork of SQLite. It's designed to minimize query latency for global applications by running read-replicas in several regions around the world.
This makes Turso a great choice for building serverless APIs that need to be fast and globally available. In this article, we'll build a fast, global, serverless API with Turso and endpts.
Creating a Turso Database
Prerequisite: you will need to install the Turso CLI on your machine.
Our database will contain a list of the top web frameworks and a few details about each one. We'll use this database to build a simple API that returns the list of frameworks and allows us to create new entries. The schema and sample data we will be using is based on this Turso sample repository.
Let's start off by creating the database named topwebframeworks
:
turso db create topwebframeworks --location iad
This will create a primary database named topwebframeworks
in Ashburn, Virginia (US) (iad
) — the default region for endpts Serverless Functions.

Now let's connect to our database to create our schema and populate it with some data:
turso db shell topwebframeworks
Once connected, we can add our sample data:
-- create frameworks table
CREATE TABLE frameworks (
id INTEGER PRIMARY KEY,
name VARCHAR (50) NOT NULL,
language VARCHAR (50) NOT NULL,
url TEXT NOT NULL,
stars INTEGER NOT NULL
);
-- "name" column unique index
CREATE UNIQUE INDEX idx_frameworks_name ON frameworks (name);
-- "url" column unique index
CREATE UNIQUE INDEX idx_frameworks_url ON frameworks (url);
-- seed some data
INSERT INTO frameworks(name, language, url, stars) VALUES
("Vue.js" , "JavaScript", "https://github.com/vuejs/vue", 203000),
("React", "JavaScript", "https://github.com/facebook/react", 206000),
("Angular", "TypeScript", "https://github.com/angular/angular", 87400),
("ASP.NET Core", "C#", "https://github.com/dotnet/aspnetcore", 31400),
("Express", "JavaScript", "https://github.com/expressjs/express", 60500),
("Django", "Python", "https://github.com/django/django", 69800),
("Ruby on Rails", "Ruby", "https://github.com/rails/rails", 52600),
("Spring", "Java", "https://github.com/spring-projects/spring-framework", 51400),
("Laravel", "PHP", "https://github.com/laravel/laravel", 73100),
("Flask", "Python", "https://github.com/pallets/flask", 62500),
("Ruby", "Ruby", "https://github.com/ruby/ruby", 41000),
("Symfony", "PHP", "https://github.com/symfony/symfony", 28200),
("CodeIgniter", "PHP", "https://github.com/bcit-ci/CodeIgniter", 18200),
("CakePHP", "PHP", "https://github.com/cakephp/cakephp", 8600),
("Qwik", "TypeScript", "https://github.com/BuilderIO/qwik", 16400);
Once you've run the commands successfully, you can exit the shell by typing
.exit
Creating a read replica
We currently have a single primary database in Ashburn, Virginia (US) (iad
). Now imagine we have a user in Europe who wants to use our API. Each time an API request is made, it will have to travel all the way to the East Coast of the United States and back, which could add a few hundred milliseconds of latency.
To serve our EU users faster, we can create a read-replica in Europe. This will allow us to serve read requests from the replica, which is much closer to our EU users and our serverless functions that we will be deploying there too.
turso db replicate topwebframeworks fra

The read replica will be created in Frankfurt, Germany (fra
), containing the same data as the primary database. Now, when a user in Europe makes a request to our API, Turso will automatically route the query to the replica in Frankfurt, which is much closer to the user.
Of course, you can choose to create more read replicas wherever you have the majority of your users.
Building our API endpoints
The endpts-samples/turso-db repository contains the code for this article that can be deployed directly to endpts. Feel free to clone the template or follow along below.
Now that we have our database set up, let's move on to building our global, serverless API. First, we'll need to create a new endpts project:
npm create endpts@latest turso-db

and install the @libsql/client
package to connect to our Turso database:
cd turso-db
npm install --save @libsql/client
To be able to connect to our database, we'll need to get the URL and token from the Turso CLI:
turso db show topwebframeworks --url
turso db tokens create topwebframeworks -e none
The -e
flag specifies the expiration date of the token. In this case, we're setting it to none
so that the token never expires.
Copy the URL and token and add them to the .env
file in the root of your project:
TURSO_DB_URL=...
TURSO_DB_AUTH_TOKEN=...
The endpts dev server will automatically load the .env
file and make the
environment variables available to your functions on process.env
.
Let's fire up the dev server and start adding our routes:
npm run dev
First, we'll need to create a client to connect to our database. We will create it in the lib/
directory in the root of our project so that we can reuse it in multiple endpoints:
// lib/db.ts
import { createClient } from '@libsql/client/web'
export const getClient = () => {
const url = process.env.TURSO_DB_URL
const authToken = process.env.TURSO_DB_AUTH_TOKEN
if (!url || !authToken) {
throw new Error(
'Please fill the TURSO_DB_URL and TURSO_DB_AUTH_TOKEN env variables'
)
}
return createClient({
url,
authToken,
})
}
The client will use the TURSO_DB_URL
and TURSO_DB_AUTH_TOKEN
environment variables to connect to our database.
Now let's create our first endpoint, GET /frameworks
, that will query the database and return a JSON list of the top web frameworks:
// routes/get_frameworks.ts
import { getClient } from '../lib/db.js'
import type { Route } from '@endpts/types'
export default {
method: 'GET',
path: '/frameworks',
async handler() {
const client = getClient()
const { rows: frameworks } = await client.execute(
'SELECT * FROM frameworks ORDER BY stars DESC'
)
return Response.json(frameworks)
},
} satisfies Route
We can run a quick test to make sure the endpoint works as expected:
curl http://localhost:3000/frameworks

Similarly, we can create a POST /frameworks
endpoint that will insert a new framework into the database:
// routes/post_framework.ts
import { getClient } from '../lib/db.js'
import type { Route } from '@endpts/types'
export default {
method: 'POST',
path: '/frameworks',
async handler(req) {
const { name, language, url, stars } = await req.json()
if (!name || !language || !url || !stars) {
return Response.json({ message: 'Missing fields!' }, { status: 400 })
}
const client = getClient()
const { rows: frameworks } = await client.execute({
sql: 'INSERT INTO frameworks(name, language, url, stars) VALUES(?, ?, ?, ?) RETURNING *',
args: [name, language, url, stars],
})
return Response.json(frameworks[0])
},
} satisfies Route
We can test the endpoint by sending a POST
request to http://localhost:3000/frameworks
with a JSON body:
curl http://localhost:3000/frameworks \
-d '{ "name": "endpts", "language": "TypeScript", "url": "https://endpts.io", "stars": 50000 }'
Deploying our API globally
The last step is to deploy our API to the cloud to the regions closest to our database.
Head over to the endpts dashboard and create a new project — you can connect your GitHub account to automatically deploy your code on every push or use the manual deployment option and specify a repository URL (e.g.: https://github.com/endpts-samples/turso-db
):

Once you've created the project, you will need to add the database URL and token as environment variables. This can be done under the Settings tab of the project:

The final step is to select the Deployment Regions for our API. Generally, it's best to keep the API and database as close as possible to minimize the round trip times. With that, let's select the same regions as our database:

We are expanding the regions in our edge network during the public beta. If you'd like to see a specific region added, please let us know!
Finally, we can push to the repository and endpts will clone the repository, build it, and deploy it to the regions we've selected in just a couple of minutes:

Once the deployment has completed, we can get the deployment URLs and test our API in the cloud:

Conclusion
In this article, we've seen how to create a global, serverless API that connects to a multi-region Turso database. The endpts platform takes care of deploying your serverless functions around the globe while Turso makes it easy to replicate your database to multiple regions worldwide. This keeps your data close to the serverless functions, giving your users a fast and responsive experience wherever they are.