DEV Community

David Rickard
David Rickard

Posted on

Tauri + SQLite

For years, SQLite has been my go-to solution for storing any kind of structured local data for apps. The ability to update multiple pieces of data in a transaction has been extremely useful.

Naturally, when building my app in Tauri, I wanted to use SQLite from my Rust backend code.

A bit of poking around led to Rusqlite for an ergonomic SQLite wrapper.

But there's a couple other questions to answer in order to tie it in with Tauri.

Where do you store the SQLite file?

Inside setup() we can create an app handle and call app_handle.path_resolver().app_data_dir(). That's the designated place to store our app data. From there we can initialize our DB file.

How do we access the Connection from a Tauri command?

Tauri has a built-in ability to store app state. We'll set up struct to hold it:

pub struct AppState {
  pub db: std::sync::Mutex<Option<Connection>>,
}
Enter fullscreen mode Exit fullscreen mode

The Mutex protects access to the connection object. The idea is that you get the Connection, run a query on it, then release the Mutex.

In setup() we can populate this app state with the DB connection.

let app_state: State<AppState> = handle.state();
*app_state.db.lock().unwrap() = Some(db);
Enter fullscreen mode Exit fullscreen mode

We can also add a convenience trait to easily access the Connection object without jumping through the State fetch and Mutex lock.

pub trait ServiceAccess {
  fn db<F, TResult>(&self, operation: F) -> TResult where F: FnOnce(&Connection) -> TResult;
}

impl ServiceAccess for AppHandle {
  fn db<F, TResult>(&self, operation: F) -> TResult where F: FnOnce(&Connection) -> TResult {
    let app_state: State<AppState> = self.state();
    let db_connection_guard = app_state.db.lock().unwrap();
    let db = db_connection_guard.as_ref().unwrap();

    operation(db)
  }
}
Enter fullscreen mode Exit fullscreen mode

Inside Tauri commands we can supply a app_handle: AppHandle argument, which will be automatically populated by the Tauri framework. From there we can call our db() trait method on app_handle to do database operations. Anything returned from the closure will be passed through.

let my_result = app_handle.db(|db: &Connection| {
    /* Do something with the DB connection and return a value */
});
Enter fullscreen mode Exit fullscreen mode

I pass the AppHandle through to any other modules that need it, so they also have access to the Connection. You can also call app_handle.clone() in case you run into ownership issues.

Full example code

https://github.com/RandomEngy/tauri-sqlite

Top comments (3)

Collapse
 
johnyepthomi profile image
JohnYepthomi

Tauri has a sqlite plugin at "github.com/tauri-apps/tauri-plugin...".
Could you make a post using the plugin as an update to this?

Collapse
 
siirko profile image
Siirko

github.com/tauri-apps/plugins-work...

tauri plugin for sql haven't the feature yet to be called from backend side, hope this will be implemented soon

Collapse
 
eduwr profile image
Eduardo Wronscki

Exactly, this is the main reason I'm not using tauri plugin.

Nice article, it was very helpful.