Skip to main content
In this TypeScript quickstart you will learn how to:
  • Retrieve database credentials
  • Install the libSQL client
  • Connect to a remote Bunny Database
  • Execute a query using SQL

Quickstart

1

Retrieve database credentials

You will need an existing database to continue. If you don’t have one, create one.Navigate to Dashboard > Edge Platform > Database > [Select Database] > Access to find your database URL and generate an access token.
You should store these as environment variables to keep them secure.
2

Install @libsql/client

Install the libSQL client package:
npm install @libsql/client
3

Initialize a new client

Create a client instance with your database URL and auth token:
import { createClient } from "@libsql/client/web";

const client = createClient({
  url: process.env.DB_URL,
  authToken: process.env.DB_TOKEN,
});
4

Execute a query using SQL

You can execute a SQL query against your database by calling execute():
await client.execute("SELECT * FROM users");
If you need to use placeholders for values, you can do that:
await client.execute({
  sql: "SELECT * FROM users WHERE id = ?",
  args: [1],
});

Using with Bunny Edge Scripting

You can connect Edge Scripts to your database by adding credentials as environment variables directly from the database dashboard. See Edge Scripting for step-by-step instructions on connecting your script to Bunny Database.
import { createClient } from "@libsql/client/web";

const client = createClient({
  url: process.env.DB_URL,
  authToken: process.env.DB_TOKEN,
});

const result = await client.execute("SELECT * FROM users");
When using Edge Scripting, your database credentials are automatically available as DB_URL and DB_TOKEN environment variables after generating a token from the database dashboard.

Using with Magic Containers

You can connect Magic Container apps to your database by adding credentials as environment variables directly from the database dashboard. See Magic Containers for step-by-step instructions on connecting your app to Bunny Database.
import { createClient } from "@libsql/client/web";

const client = createClient({
  url: process.env.DB_URL,
  authToken: process.env.DB_TOKEN,
});

const result = await client.execute("SELECT * FROM users");
When using Magic Containers, your database credentials are automatically available as DB_URL and DB_TOKEN environment variables after generating a token from the database dashboard.

Response

Each query method returns a Promise<ResultSet>:
PropertyTypeDescription
rowsArray<Row>An array of Row objects containing the row values, empty for write operations
columnsArray<string>An array of strings with the names of the columns in the order they appear in each Row
rowsAffectednumberThe number of rows affected by a write statement, 0 otherwise
lastInsertRowidbigint | undefinedThe ID of a newly inserted row, or undefined if there is none for the statement

Placeholders

libSQL supports the use of positional and named placeholders within SQL statements:
const result = await client.execute({
  sql: "SELECT * FROM users WHERE id = ?",
  args: [1],
});
libSQL supports the same named placeholder characters as SQLite — :, @ and $.

Batch Transactions

A batch consists of multiple SQL statements executed sequentially within an implicit transaction. The backend handles the transaction: success commits all changes, while any failure results in a full rollback with no modifications.
const result = await client.batch(
  [
    {
      sql: "INSERT INTO users VALUES (?)",
      args: ["Kit"],
    },
    {
      sql: "INSERT INTO users VALUES (?)",
      args: ["Sam"],
    },
  ],
  "write",
);

Transaction Modes

ModeSQLite commandDescription
writeBEGIN IMMEDIATEThe transaction may execute statements that read and write data. Write transactions executed on a replica are forwarded to the primary instance, and can’t operate in parallel.
readBEGIN TRANSACTION READONLYThe transaction may only execute statements that read data (select). Read transactions can occur on replicas, and can operate in parallel with other read transactions.
deferredBEGIN DEFERREDThe transaction starts in read mode, then changes to write as soon as a write statement is executed. This mode change may fail if there is a write transaction currently executing on the primary.

Interactive Transactions

Interactive transactions in SQLite ensure the consistency of a series of read and write operations within a transaction’s scope. These transactions give you control over when to commit or roll back changes, isolating them from other client activity.
MethodDescription
execute()Similar to execute() except within the context of the transaction
commit()Commits all write statements in the transaction
rollback()Rolls back the entire transaction
close()Immediately stops the transaction
const transaction = await client.transaction("write");

try {
  // Read the current balance
  const result = await transaction.execute({
    sql: "SELECT balance FROM accounts WHERE id = ?",
    args: [1],
  });

  const currentBalance = result.rows[0].balance as number;
  const newBalance = currentBalance - 100;

  // Validate and update based on the read value
  if (newBalance < 0) {
    throw new Error("Insufficient funds");
  }

  await transaction.execute({
    sql: "UPDATE accounts SET balance = ? WHERE id = ?",
    args: [newBalance, 1],
  });

  await transaction.commit();
} catch (e) {
  await transaction.rollback();
}
Interactive transactions in libSQL lock the database for writing until committed or rolled back, with a 5-second timeout. They can impact performance on high-latency or busy databases.