Skip to main content

Command Palette

Search for a command to run...

Rust + Sqlx + Sqlite

Updated
Rust + Sqlx + Sqlite

Untuk sekedar ingin membuat aplikasi sederhana, mungkin tidak memerlukan database server seperti PostgreSQL atau Mysql. Kita bisa memilih untuk menggunakan database sederhana seperti SQLite Database yang mana outputnya berupa file. Jadi kita bisa mengikutkan file tersebut ke dalam Git Repository sebagai bagian dari code.

Setup Workspace

Di ekosistem Rust, untuk menginisialisasi directory workspace bisa menggunakan popular package manager yaitu menggunakan Cargo. Pada tulisan ini saya menggunakan Cargo dan rustc version 1.84.1

$ cargo --version
cargo 1.84.1 (66221abde 2024-11-19)

$ rustc --version
rustc 1.84.1 (e71f9a9a9 2025-01-27)

Selanjutnya dengan membuat workspace baru dengan nama dbconn sebagai contoh

$ cargo new dbconn
$ cd dbconn

Agar code bisa terkoneksi ke sqlite, maka diperlukan sqlx. Jalankan command berikut untuk menambahkan sqlx. Pada saat tulisan ini dibuat, sqlx terbaru menggunakan version v0.8.3

$ cargo add sqlx@0.8.3 --features runtime-tokio,sqlite

Pada command tersebut saya menambahkan option --features. Option ini digunakan agar cargo hanya meng-compile fitur-fitur yang diperlukan saja, sehingga binary yang dihasilkan akan lebih kecil dari pada meng-compile keseluruhan fitur yang mana tidak digunakan. Jika kita buka file Cargo.toml maka kurang lebih flag akan terlihat seperti ini

[dependencies]
sqlx = { version = "0.8.3", features = ["runtime-tokio", "sqlite"] }

Selanjutnya karena async pada Rust bisa menggunakan beberapa package library, seperti tokio atau async-std. Pada tulisan ini, akan menggunakan tokio karena pada setup sqlx kita sudah menambahkan fitur yang di-enable adalah runtime-tokio.

$ cargo add tokio@1 --features full

Setelah semua package yang diperlukan ter-download, selanjutnya jalan cargo check guna memastikan semua dependencies terdownload.

$ cargo check

Setup SQLite Database

Sebelum melakukan perubahan code pada main.rs, kita perlu setup database sqlite terlebih dahulu. Karena kita belum menggunakan migration tool apapun untuk meng-inisialisasi database, maka perlu melakukannya secara manual dengan langkah seperti berikut.

$ sqlite3 db.sql
sqlite> create table users (id int primary key, name varchar)
sqlite> insert into users (id, name) values (1, 'Sample User')
sqlite> select * from users;
1|Sample User

Setup Connection and Query

Setelah selesai membuat sqlite database. Kita coba kembali ke directory /tmp/dbconn. Dimana pada directory ini terdapat file src/main.rs yang berisi code start started Rust seperti ini.

fn main() {
   println!("Hello, world!")
}

Pada step ini kita perlu merevisi code tersebut agar dapat membuka koneksi ke database yang sudah dibuat sebelumnya. Seperti code di bawah ini.

use sqlx::sqlite::SqlitePoolOptions;

#[tokio::main]
async fn main() -> Result<(), sqlx::Error> {
    let mut pool = SqlitePoolOptions::new()
        .max_connections(5)
        .connect("sqlite:db.sql")
        .await?;

    Ok(())
}

Pada code tersebut terdapat sebuah variable dengan nama pool, dimana variable ini menyimpan instance dari SqlitePoolOptions. Pool ini merupakan kumpulan koneksi yang masih terhubung ke database yang mana memiliki maximal 5 koneksi. Selain itu untuk dapat melakukan query ke database, diperlukan async/await. Maka perlu menambahkan macro tokio::main pada function main.

Selanjutnya perlu membuat struct yang nantinya digunakan sebagai result dari query_as dari sqlx.

use sqlx::{ prelude::FromRow, sqlite::SqlitePoolOptions};

#[derive(Debug, FromRow)]
struct User {
   id: i64,
   name: String
}

Gunakan struct tersebut sebagai type annotation Vec<User> pada result query, sehingga hasil dari query akan di-casting ke dalam bentuk Struct tersebut seperti ini

async fn main() -> Result<(), sqlx::Error> {
	...
	let result = query_as::<_, User>(r#"select * from users"#)
		.fetch_all(&pool)
		.await?;
	...
}

Maka secara keseluruhan function main akan seperti berikut

use sqlx::{ prelude::FromRow, sqlite::SqlitePoolOptions, query_as };

#[derive(Debug, FromRow)]
struct User {
    id: i64,
    name: String
}

#[tokio::main]
async fn main() -> Result<(), sqlx::Error> {
    let mut pool = SqlitePoolOptions::new()
        .max_connections(5)
        .connect("sqlite:db.sql")
        .await?;

    let result = query_as::<_, User>(r#"select * from users"#).fetch_all(&pool).await?;
    
    for item in result.iter() {
        println!("id: {}, name: {}", item.id, item.name);
    }

    Ok(())
}

Setelah semua tersetup, selanjutnya menjalankan cargo run untuk mendapatkan result.

$ cargo run
...
Compiling dbconn v0.1.0 (/tmp/dbconn)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.28s
     Running `target/debug/dbconn`
id: 1, name: Sample User

Kesimpulan

Untuk membuka koneksi dari Rust ke SQLite database bisa dengan mudah menggunakan package sqlx.

Rust Exploring

Part 1 of 1