Setup Turso in Rust
Apa itu Turso
Singkatnya Turso itu engine database SQL in-process yang compatible dengan SQLite. Turso sangat ideal untuk aplikasi modern karena mendukung fitur embedded replica (Local-First). Turso bisa membaca/menulis ke database lokal SQLite dengan sangat cepat, lalu melakukan sinkronisasi (push/pull) ke server cloud di background.
Berikut adalah catatan bagaimana melakukan setup state database, melakukan sinkronisasi, dan menjalankan operasi CRUD sederhana di Rust.
Mendefinisikan State Database
Karena Rust memiliki aturan kepemilikan (ownership) dan konkurensi yang ketat, koneksi database yang akan dipakai secara bersamaan oleh banyak fungsi (atau thread) harus dibungkus menggunakan Arc dan tokio::sync::Mutex.
use turso::Connection;
use turso::sync::Database;
use std::sync::Arc;
use tokio::sync::Mutex;
// DbState menyimpan koneksi dan instance database yang bisa dibagikan
// (cloned) dengan aman ke berbagai request/task.
#[derive(Clone)]
pub struct DbState {
pub conn: Arc<Mutex<Connection>>,
pub db: Arc<Mutex<Database>>,
}
/// Inisialisasi Database Embedded Replica
pub async fn init_db(local_db_path: &str, turso_url: String, turso_token: String) -> Result<DbState, String> {
// Build database instance
let db = turso::sync::Builder::new_remote(local_db_path)
.with_remote_url(turso_url)
.with_auth_token(turso_token)
.build()
.await
.map_err(|e| e.to_string())?;
// Buat koneksi dari instance database tersebut
let conn = db.connect().await.map_err(|e| e.to_string())?;
// Tarik data awal (Pull) dari Turso Cloud ke lokal
if let Err(e) = db.pull().await {
eprintln!("[Offline Mode] Gagal menarik data awal dari Turso: {}", e);
} else {
println!("[Online Mode] Berhasil menarik data awal dari Turso.");
}
// Bungkus dengan Arc<Mutex<...>> agar thread-safe
Ok(DbState {
conn: Arc::new(Mutex::new(conn)),
db: Arc::new(Mutex::new(db))
})
}
Fungsi Sinkronisasi Manual (Push & Pull)
Dalam pendekatan local-first, bekerja dengan data lokal terlebih dahulu. Untuk menyelaraskan data lokal dengan cloud, bisa dengan cara memanggil metode push() dan pull() pada instance Database.
/// Fungsi untuk melakukan sinkronisasi dua arah
pub async fn sync_db_cloud(db_state: &DbState) -> Result<String, String> {
// Kunci mutex untuk mendapatkan akses ke instance database
let db = db_state.db.lock().await;
// Push perubahan lokal ke cloud
db.push().await.map_err(|e| format!("Gagal mengirim (push) data: {}", e))?;
// Pull perubahan terbaru dari cloud ke lokal
db.pull().await.map_err(|e| format!("Gagal menarik (pull) data: {}", e))?;
Ok("Data berhasil disinkronisasi penuh".to_string())
}
Operasi CRUD (Contoh: Tabel Pelanggan)
use serde::{Serialize, Deserialize};
use turso::params;
use crate::db_set::DbState; // Sesuaikan dengan struktur folder
#[derive(Serialize, Deserialize, Debug)]
pub struct Pelanggan {
pub id: Option<i64>,
pub nama: String,
pub telepon: String,
pub alamat: String,
}
#[derive(Serialize, Deserialize)]
pub struct NewPelanggan {
pub nama: String,
pub telepon: String,
pub alamat: String,
}
// CREATE
pub async fn new_pelanggan(db_state: &DbState, input: NewPelanggan) -> Result<String, String> {
let conn = db_state.conn.lock().await;
conn.execute(
r#"
INSERT INTO pelanggan (nama, telepon, alamat)
VALUES (?1, ?2, ?3)
"#,
params![input.nama, input.telepon, input.alamat]
)
.await
.map_err(|e| e.to_string())?;
Ok("Pelanggan baru berhasil ditambahkan".to_string())
}
// READ
pub async fn list_pelanggan(db_state: &DbState) -> Result<Vec<Pelanggan>, String> {
let conn = db_state.conn.lock().await;
let mut rows = conn.query(
"SELECT id, nama, telepon, alamat FROM pelanggan ORDER BY nama ASC",
()
)
.await
.map_err(|e| e.to_string())?;
let mut pelanggans = Vec::new();
while let Some(row) = rows.next().await.map_err(|e| e.to_string())? {
pelanggans.push(Pelanggan {
id: row.get(0).ok(),
nama: row.get(1).unwrap_or_default(),
telepon: row.get(2).unwrap_or_default(),
alamat: row.get(3).unwrap_or_default(),
});
}
Ok(pelanggans)
}
// UPDATE
pub async fn edit_pelanggan(db_state: &DbState, id: i64, input: NewPelanggan) -> Result<String, String> {
let conn = db_state.conn.lock().await;
let rows_affected = conn.execute(
r#"
UPDATE pelanggan
SET nama = ?1, telepon = ?2, alamat = ?3
WHERE id = ?4
"#,
params![input.nama, input.telepon, input.alamat, id]
)
.await
.map_err(|e| e.to_string())?;
if rows_affected == 0 {
return Err("Pelanggan tidak ditemukan".to_string());
}
Ok("Pelanggan berhasil diupdate".to_string())
}
// DELETE
pub async fn delete_pelanggan(db_state: &DbState, id: i64) -> Result<String, String> {
let conn = db_state.conn.lock().await;
let rows_affected = conn.execute(
r#"DELETE FROM pelanggan WHERE id = ?1"#,
params![id]
)
.await
.map_err(|e| e.to_string())?;
if rows_affected == 0 {
return Err("Pelanggan tidak ditemukan".to_string());
}
Ok("Pelanggan berhasil dihapus".to_string())
}
Main
pub mod db_set;
pub mod pelanggan;
use db_set::{init_db, sync_db_cloud};
use pelanggan::{new_pelanggan, list_pelanggan, edit_pelanggan, delete_pelanggan, NewPelanggan};
use dotenvy::dotenv;
use std::env;
#[tokio::main]
async fn main() {
// Muat variabel environment
dotenv().ok();
let turso_url = env::var("TURSO_SYNC_URL").expect("Variabel TURSO_SYNC_URL tidak ditemukan");
let turso_token = env::var("TURSO_AUTH_TOKEN").expect("Variabel TURSO_AUTH_TOKEN tidak ditemukan");
let local_db_path = "./app-local.db";
// Inisialisasi Database
let db_state = match init_db(local_db_path, turso_url, turso_token).await {
Ok(state) => state,
Err(e) => {
eprintln!("Gagal menginisialisasi database: {}", e);
return;
}
};
// CREATE: new pelanggan
let tambah_req = new_pelanggan(&db_state, NewPelanggan {
nama: "Budi Santoso".to_string(),
telepon: "08123456789".to_string(),
alamat: "Jl. Merdeka No. 1".to_string(),
}).await;
println!("CREATE: {:?}", tambah_req);
// READ: list pelanggan
if let Ok(daftar) = list_pelanggan(&db_state).await {
println!("{} pelanggan:", daftar.len());
for p in daftar {
println!(" - [{}] {} (Telp: {})", p.id.unwrap_or(0), p.nama, p.telepon);
}
}
// UPDATE: ID 1
let update_req = edit_pelanggan(&db_state, 1, NewPelanggan {
nama: "Budi Santoso (Updated)".to_string(),
telepon: "08999999999".to_string(),
alamat: "Jl. Merdeka No. 1A".to_string(),
}).await;
println!("UPDATE: {:?}", update_req);
// DELETE: Menghapus pelanggan dengan ID 1
// let delete_req = delete_pelanggan(&db_state, 1).await;
// println!("DELETE: {:?}", delete_req);
// SYNC
let sync_status = sync_db_cloud(&db_state).await;
println!("SYNC: {:?}", sync_status);
}
"Sesungguhnya yang menyebabkan ilmu hilang adalah lupa dan tidak mengulanginya."
Imam Az-Zuhri rahimahullah
Tags:
Referensi:
Catatan Terkait:

Copyright 2026. All rights reserved.