Back to Blog

Tauri SQLite Database Tutorial - Build a Contacts Manager App | React + Rust

Sandy LaneSandy Lane

Video: Tauri SQLite Database Tutorial - Build a Contacts Manager App | React + Rust by Taught by Celeste AI - AI Coding Coach

Watch full page →

Tauri SQLite Database Tutorial - Build a Contacts Manager App | React + Rust

This tutorial demonstrates how to integrate a SQLite database into a Tauri desktop app using Rust and React. You'll learn to set up SQLite with the tauri-plugin-sql, configure database migrations in Rust, and implement full CRUD operations from a React frontend.

Code

// Rust backend: src-tauri/src/lib.rs
use tauri_plugin_sql::{Migration, MigrationKind, SqlPlugin};

#[tauri::command]
async fn add_contact(db: tauri_plugin_sql::Database, name: String, email: String) -> Result<(), String> {
  db.execute("INSERT INTO contacts (name, email) VALUES (?, ?)", &[&name, &email])
    .await
    .map_err(|e| e.to_string())?;
  Ok(())
}

#[tauri::command]
async fn get_contacts(db: tauri_plugin_sql::Database) -> Result, String> {
  db.select("SELECT id, name, email FROM contacts", &[])
    .await
    .map_err(|e| e.to_string())
}

// Define database migrations
fn migrations() -> Vec {
  vec![Migration {
    version: 1,
    description: "Create contacts table",
    sql: "CREATE TABLE IF NOT EXISTS contacts (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, email TEXT NOT NULL);",
    kind: MigrationKind::Up,
  }]
}

fn main() {
  tauri::Builder::default()
    .plugin(SqlPlugin::new(migrations()))
    .invoke_handler(tauri::generate_handler![add_contact, get_contacts])
    .run(tauri::generate_context!())
    .expect("error while running tauri application");
}

// React frontend: src/App.tsx
import React, { useState, useEffect } from 'react';
import { invoke } from '@tauri-apps/api/tauri';

interface Contact {
  id: number;
  name: string;
  email: string;
}

export default function App() {
  const [contacts, setContacts] = useState([]);
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');

  // Load contacts from SQLite via Tauri backend
  async function loadContacts() {
    const result = await invoke('get_contacts');
    setContacts(result || []);
  }

  // Add a new contact
  async function addContact() {
    if (!name || !email) return;
    await invoke('add_contact', { name, email });
    setName('');
    setEmail('');
    loadContacts();
  }

  useEffect(() => {
    loadContacts();
  }, []);

  return (
    <div>
      <h1>Contacts Manager</h1>
      <input placeholder="Name" value={name} onChange={e => setName(e.target.value)} />
      <input placeholder="Email" value={email} onChange={e => setEmail(e.target.value)} />
      <button onClick={addContact}>Add Contact</button>
      <ul>
        {contacts.map(c => (
          <li key={c.id}>{c.name} - {c.email}</li>
        ))}
      </ul>
    </div>
  );
}

Key Points

  • Use the tauri-plugin-sql crate to add SQLite support and manage migrations in Rust.
  • Define database schema changes as migrations to ensure consistent setup across app launches.
  • Use parameterized queries with placeholders to securely insert and query data.
  • Invoke Rust backend commands from React to perform asynchronous CRUD operations on the database.
  • Manage React state to reflect real-time updates from the SQLite database in the UI.