레퍼런스
Turso의 TypeScript 레퍼런스예요. 네 가지 패키지가 각각 어떤 상황에 맞는지, 연결·쿼리·트랜잭션·동기화·암호화 API를 하나씩 살펴볼게요.
출처: 문서
본문
Turso는 네 가지 TypeScript 패키지를 제공해요:
@tursodatabase/database |
@tursodatabase/sync |
@tursodatabase/serverless |
@libsql/client |
|
|---|---|---|---|---|
| Use case | 로컬/임베디드 데이터베이스 | 로컬 데이터베이스 + 클라우드 sync | 원격 Turso 데이터베이스 (서버, 컨테이너, serverless, edge) | 원격 libSQL 데이터베이스, ORM 지원 (Drizzle, Prisma) |
| Engine | Turso (rewrite) | Turso (rewrite) | Turso | libSQL (SQLite fork) |
| Dependencies | Native (Node.js, WASM) | Native (Node.js) | fetch만 — 네이티브 의존성 없음 |
Node.js 또는 /web 서브패스 필요 |
| Concurrent writes | Yes (MVCC) | Yes (MVCC) | Yes (MVCC) | Not supported |
| Sync | — | push/pull (local-first) | — | Embedded Replicas (쓰기는 클라우드 primary로) |
| ORM support | Drizzle (beta) | — | — | Drizzle, Prisma 등 |
새 프로젝트를 시작하나요? 로컬/임베디드 용도에는 @tursodatabase/database, 로컬 + 클라우드 sync에는 @tursodatabase/sync, 네트워크를 넘어 원격 Turso 데이터베이스에 접근할 때는 @tursodatabase/serverless를 쓰세요(Node.js 서버, Docker 컨테이너, serverless 함수, 엣지 런타임). 원격 libSQL 데이터베이스에 연결하거나 ORM을 쓰나요? @libsql/client를 사용하세요 — 프로덕션에서 검증됐고 Drizzle, Prisma 등이 지원해요. Drizzle은 로컬/임베디드 용도의 @tursodatabase/database에 대한 베타 지원도 있고요.
호환성이 확인된 런타임 환경은 다음과 같아요:
- Node.js 12 이상
- Deno
- CloudFlare Workers
- Netlify & Vercel Edge Functions
@tursodatabase/database
로컬·임베디드 용도예요. 동시 쓰기(MVCC)와 비동기 I/O를 지원하는 Turso Database 엔진 기반이에요.
Installing
npm install @tursodatabase/database
Initializing
import { connect } from "@tursodatabase/database";
const db = await connect("app.db");
인메모리 데이터베이스도 지원해요:
const db = await connect(":memory:");
Querying
const stmt = db.prepare("SELECT * FROM users");
const users = await stmt.all();
const insert = db.prepare("INSERT INTO users (username) VALUES (?)");
await insert.run("alice");
Encryption
encryption 옵션으로 로컬 데이터베이스를 저장 시점(at rest)에 암호화할 수 있어요:
import { connect } from "@tursodatabase/database";
const db = await connect("encrypted.db", {
encryption: {
cipher: "aegis256",
hexkey: "b1bbfda4f589dc9daaf004fe21111e00dc00c98237102f5c7002a5669fc76327",
},
});
지원 암호: aegis256, aegis256x2, aegis128l, aegis128x2, aegis128x4, aes256gcm, aes128gcm.
암호화된 데이터베이스는 표준 SQLite 데이터베이스로는 읽을 수 없어요 — 반드시 Turso Database 엔진으로 열어야 해요.
참고로, Turso Cloud 데이터베이스도 bring-your-own-key로 암호화할 수 있어요 — 자세히 보기.
@tursodatabase/sync
클라우드 sync가 있는 로컬 데이터베이스용이에요. 모든 읽기와 쓰기는 로컬에서 일어나고, push()로 변경을 클라우드에 보내고 pull()로 원격 변경을 가져와요.
Installing
npm install @tursodatabase/sync
Initializing
import { connect } from "@tursodatabase/sync";
const db = await connect({
path: "./app.db",
url: process.env.TURSO_DATABASE_URL,
authToken: process.env.TURSO_AUTH_TOKEN,
});
첫 실행 때 로컬 데이터베이스가 원격에서 자동으로 bootstrap돼요. 전체 내용은 Turso Sync에서 확인하세요.
Push and Pull
// Push local writes to Turso Cloud
await db.push();
// Pull remote changes to local database
const changed = await db.pull();
Checkpoint
sync 상태를 유지하면서 로컬 WAL을 압축해 디스크 사용량을 제한해요:
await db.checkpoint();
Stats
const s = await db.stats();
console.info({
cdcOperations: s.cdcOperations,
mainWalSize: s.mainWalSize,
networkReceivedBytes: s.networkReceivedBytes,
networkSentBytes: s.networkSentBytes,
lastPullUnixTime: s.lastPullUnixTime,
lastPushUnixTime: s.lastPushUnixTime,
revision: s.revision,
});
@tursodatabase/serverless
원격 Turso Cloud 데이터베이스에 연결하는 모든 애플리케이션에 추천하는 패키지예요 — Node.js 서버, Docker 컨테이너, serverless 함수(AWS Lambda, Vercel Functions), 엣지 런타임(Cloudflare Workers, Deno Deploy). fetch만 사용하기 때문에 네이티브 의존성이 전혀 없고, fetch가 있는 곳이라면 어디서든 동작해요.
Installing
npm install @tursodatabase/serverless
Initializing
import { connect } from "@tursodatabase/serverless";
const conn = connect({
url: process.env.TURSO_DATABASE_URL,
authToken: process.env.TURSO_AUTH_TOKEN,
});
@libsql/client API와의 호환성이 필요하면 compat 모듈을 쓰세요:
import { createClient } from "@tursodatabase/serverless/compat";
const client = createClient({
url: process.env.TURSO_DATABASE_URL,
authToken: process.env.TURSO_AUTH_TOKEN,
});
Querying
const stmt = await conn.prepare("SELECT * FROM users");
const rows = await stmt.all();
const stmt2 = await conn.prepare("SELECT * FROM users WHERE id = ?");
const row = await stmt2.get([1]);
@libsql/client
@libsql/client 패키지는 수년간 Turso Cloud를 구동해 온 SQLite 오픈소스 포크인 libSQL 기반이에요. 프로덕션에서 검증이 끝났고, libSQL 데이터베이스 원격 접근, Drizzle을 넘어선 ORM 연동(예: Prisma), 기존 @libsql/client 기반 코드베이스 작업에 맞는 선택이에요.
참고로, @libsql/client Embedded Replicas에서는 읽기가 로컬에서 일어나고 쓰기는 클라우드 primary로 전송된 뒤 복제본에 반영돼요. Embedded Replicas는 완전히 지원돼요. sync가 필요한 새 프로젝트에는 Turso Sync의 @tursodatabase/sync를 추천해요.
Installing
@libsql/client 패키지를 설치하면 돼요: npm install @libsql/client
Initializing
import { createClient } from "@libsql/client";
export const turso = createClient({
url: process.env.TURSO_DATABASE_URL,
authToken: process.env.TURSO_AUTH_TOKEN,
});
참고로, libsql을 로컬에서 쓰거나 sqlite 파일을 다룬다면 authToken은 넘기지 않아도 돼요.
In-Memory Databases
import { createClient } from "@libsql/client";
const client = createClient({
url: ":memory:",
});
Local Development
SQLite 파일 경로를 createClient에 넘겨서 로컬에서 작업할 수 있어요:
import { createClient } from "@libsql/client";
const client = createClient({
url: "file:path/to/db-file.db",
authToken: "...",
});
주의할 점은, @libsql/client/web은 로컬 파일 URL을 지원하지 않아요.
Embedded Replicas
오프라인 쓰기, 양방향 sync, 다중 writer 수렴이 필요한 워크로드에는 @tursodatabase/sync를 추천해요 — 읽기와 쓰기가 모두 로컬에서 일어나고, push() / pull()로 명시적으로 동기화하거든요.
Turso Database URL을 syncUrl에 넘겨서 embedded replicas를 쓸 수 있어요:
import { createClient } from "@libsql/client";
const client = createClient({
url: "file:path/to/db-file.db",
syncUrl: "libsql://[databaseName]-[organizationSlug].turso.io",
authToken: "...",
});
Manual Sync
await client.sync();
Periodic Sync
import { createClient } from "@libsql/client";
const client = createClient({
url: "file:path/to/db-file.db",
syncUrl: "libsql://[databaseName]-[organizationSlug].turso.io",
syncInterval: 60,
authToken: "...",
});
Encryption
새 프로젝트에서는 로컬 암호화에 @tursodatabase/database를 추천해요 — Turso Database 엔진 기반이라 성능이 좋고 동시 쓰기를 지원하거든요.
암호화된 데이터베이스는 원시 데이터로 보이고 표준 SQLite 데이터베이스로는 읽을 수 없어요. 모든 작업에 libSQL 클라이언트를 써야 해요 — 자세히 보기.
Concurrency
기본적으로 클라이언트는 최대 20개의 동시 요청을 처리해요:
import { createClient } from "@libsql/client";
const client = createClient({
concurrency: 10,
});
Response
아래 나열된 각 메서드는 Promise<ResultSet>을 반환해요:
| Property | Type | Description |
|---|---|---|
rows |
Array<Row> |
행 값을 담은 Row 객체 배열, 쓰기 작업에서는 비어 있음 |
columns |
Array<string> |
각 Row에 나타나는 순서대로 컬럼 이름을 담은 문자열 배열, 쓰기 작업에서는 비어 있음 |
rowsAffected |
number |
쓰기 문장이 영향을 준 행 수, 그 외에는 0 |
lastInsertRowid |
bigint | undefined |
새로 삽입된 행의 ID, 문장에 없으면 undefined |
Simple query
문자열이나 객체를 execute()에 넘겨 SQL 문을 실행할 수 있어요:
String
const result = await client.execute("SELECT * FROM users");
Object
const result = await client.execute({
sql: "SELECT * FROM users WHERE id = ?",
args: [1],
});
Placeholders
libSQL은 SQL 문 안에서 positional 및 named 플레이스홀더를 지원해요:
Positional
const result = await client.execute({
sql: "SELECT * FROM users WHERE id = ?",
args: [1],
});
const result = await client.batch(
[
{
sql: "INSERT INTO users VALUES (?)",
args: ["Iku"],
},
],
"write",
);
Named
const result = await client.execute({
sql: "INSERT INTO users VALUES (:name)",
args: { name: "Iku" },
});
const result = await client.batch(
[
{
sql: "INSERT INTO users VALUES (:name)",
args: { name: "Iku" },
},
],
"write",
);
참고로, libSQL은 SQLite와 같은 named 플레이스홀더 문자를 지원해요 — :, @, $.
Transaction Modes
| Mode | SQLite command | Description |
|---|---|---|
write |
BEGIN IMMEDIATE |
트랜잭션이 데이터를 읽고 쓰는 문장을 실행할 수 있어요. 복제본에서 실행된 write 트랜잭션은 primary로 전달되며, 병렬로 동작할 수 없어요. |
read |
BEGIN TRANSACTION READONLY |
트랜잭션이 데이터를 읽는 문장(select)만 실행할 수 있어요. read 트랜잭션은 복제본에서 일어날 수 있고, 다른 read 트랜잭션과 병렬로 동작할 수 있어요. |
deferred |
BEGIN DEFERRED |
트랜잭션이 read 모드로 시작했다가, write 문장이 실행되는 즉시 write로 바뀌어요. primary에서 write 트랜잭션이 실행 중이면 이 모드 전환이 실패할 수 있어요. |
Batch Transactions
배치는 암묵적 트랜잭션 안에서 여러 SQL 문을 순차적으로 실행하는 거예요. 백엔드가 트랜잭션을 처리해요: 성공하면 모든 변경이 커밋되고, 실패하면 수정 없이 완전히 롤백돼요.
const result = await client.batch(
[
{
sql: "INSERT INTO users VALUES (?)",
args: ["Iku"],
},
{
sql: "INSERT INTO users VALUES (?)",
args: ["Iku 2"],
},
],
"write",
);
Interactive Transactions
SQLite의 인터랙티브 트랜잭션은 트랜잭션 범위 안에서 일련의 읽기·쓰기 작업의 일관성을 보장해요. 커밋이나 롤백 시점을 직접 제어할 수 있고, 다른 클라이언트 활동으로부터 격리되죠.
| Method | Description |
|---|---|
execute() |
execute()와 같지만 트랜잭션 컨텍스트 안에서 실행돼요 |
commit() |
트랜잭션의 모든 write 문장을 커밋해요 |
rollback() |
트랜잭션 전체를 롤백해요 |
close() |
트랜잭션을 즉시 중단해요 |
Rollback Usage
try {
const userId = "user123";
const withdrawalAmount = 500;
const transaction = await client.transaction("write");
const balanceResult = await transaction.execute({
sql: "SELECT balance FROM accounts WHERE userId = ?",
args: [userId],
});
const currentBalance = balanceResult.rows[0]["balance"] as number;
if (currentBalance >= withdrawalAmount) {
await transaction.execute({
sql: "UPDATE accounts SET balance = balance - ? WHERE userId = ?",
args: [withdrawalAmount, userId],
});
} else {
console.log("Insufficient funds");
await transaction.rollback();
return;
}
await transaction.commit();
} catch (e) {
console.error(e);
}
Batch Usage
let transaction: Transaction | null = null;
try {
const records = [
{ name: "Alice", age: 30 },
{ name: "Bob", age: 25 },
{ name: "Charlie", age: 35 },
];
transaction = await client.transaction("write");
for (const record of records) {
await transaction.execute({
sql: "INSERT INTO people (name, age) VALUES (?, ?)",
args: [record.name, record.age],
});
}
await transaction.commit();
} catch (e) {
console.error(e);
if (transaction) await transaction.rollback();
}
Conditional Usage
try {
const productId = "prod456";
const newPrice = 150;
const transaction = await client.transaction("write");
const productResult = await transaction.execute({
sql: "SELECT price FROM products WHERE productId = ?",
args: [productId],
});
const currentPrice = productResult.rows[0]["price"] as number;
if (currentPrice > newPrice) {
await transaction.execute({
sql: "UPDATE products SET price = ? WHERE productId = ?",
args: [newPrice, productId],
});
} else {
console.log("New price is not lower than current price");
await transaction.rollback();
return;
}
await transaction.commit();
} catch (e) {
console.error(e);
}
주의할 점은, libSQL의 인터랙티브 트랜잭션은 커밋되거나 롤백될 때까지 데이터베이스에 쓰기 잠금을 걸며, 타임아웃은 5초예요. 지연 시간이 높거나 바쁜 데이터베이스에서는 성능에 영향을 줄 수 있어요.
ATTACH
ATTACH 문으로 현재 커넥션에 여러 데이터베이스를 붙일 수 있어요:
import { createClient } from "@libsql/client";
const client = createClient({
url: process.env.TURSO_DATABASE_URL,
authToken: process.env.TURSO_AUTH_TOKEN,
});
const txn = await db.transaction("read");
await txn.execute('ATTACH "<database-id>" AS attached');
const rs = await txn.execute("SELECT * FROM attached.users");
참고로, ATTACH 허용 설정과 데이터베이스를 attach할 권한이 있는 토큰 생성을 잊지 마세요 — 자세히 보기
더 알아보기 (Learn more)
- TypeScript 퀵스타트 — 패키지 선택부터 빠르게 시작
- Drizzle ORM 연동 — ORM과 함께 쓰기
- Prisma ORM 연동 — Prisma 어댑터 설정
- Turso Sync 사용법 — push/pull과 충돌 해결 상세
- Embedded Replicas — 로컬 읽기 복제본 개념 정리