Skip to content

Tauri Framework Desktop GUI

Rust Backend + Web Frontend — Desktop apps nhẹ hơn Electron 10x

Tauri vs Electron

┌─────────────────────────────────────────────────────────────────────┐
│                    TAURI vs ELECTRON COMPARISON                      │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  Metric           │  Electron        │  Tauri                      │
│  ─────────────────┼──────────────────┼─────────────────────────    │
│  Bundle Size      │  150-300MB       │  3-10MB                     │
│  Memory Usage     │  100-300MB       │  20-50MB                    │
│  Backend          │  Node.js         │  Rust (native)              │
│  Frontend         │  Chromium        │  System WebView             │
│  Security         │  Node + Chrome   │  Rust + Sandboxed WebView   │
│  Startup Time     │  3-5 seconds     │  0.5-1 second               │
│                                                                     │
│  Popular Apps:                                                      │
│  Electron: VS Code, Discord, Slack, Figma Desktop                   │
│  Tauri: 1Password, Notion (new), Codeium, Lapce                    │
└─────────────────────────────────────────────────────────────────────┘

Architecture

┌──────────────────────────────────────────────────────────────────┐
│                         TAURI APP                                │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│   ┌────────────────────────┐     ┌────────────────────────────┐  │
│   │     RUST BACKEND       │     │      WEB FRONTEND          │  │
│   │                        │     │                            │  │
│   │  • Business Logic      │◀───▶│  • UI (React/Vue/Svelte)   │  │
│   │  • File System Access  │ IPC │  • User Interactions       │  │
│   │  • Database            │     │  • State Management        │  │
│   │  • System APIs         │     │  • Rendering               │  │
│   │  • Crypto/Security     │     │                            │  │
│   │                        │     │                            │  │
│   └────────────────────────┘     └────────────────────────────┘  │
│            │                               │                      │
│            │                               │                      │
│   ┌────────▼────────────────────────────────▼────────────────┐   │
│   │                    SYSTEM WEBVIEW                         │   │
│   │          (Edge/WebKit - NOT bundled Chromium)            │   │
│   └───────────────────────────────────────────────────────────┘   │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

1. Project Setup

Tạo project mới

bash
# Cài Tauri CLI
cargo install create-tauri-app

# Tạo app (chọn frontend framework)
cargo create-tauri-app

# Hoặc thêm Tauri vào existing frontend project
cd my-react-app
npm install @tauri-apps/cli @tauri-apps/api
npx tauri init

Cấu trúc thư mục

my-tauri-app/
├── src/                    # Frontend (React/Vue/Svelte)
│   ├── App.tsx
│   ├── main.tsx
│   └── ...
├── src-tauri/              # Rust backend
│   ├── Cargo.toml
│   ├── tauri.conf.json     # Tauri config
│   ├── src/
│   │   ├── main.rs
│   │   └── lib.rs
│   └── icons/
├── package.json
└── vite.config.ts

2. Tauri Commands (IPC)

Command = Rust function callable từ JavaScript.

Định nghĩa Command trong Rust

rust
// src-tauri/src/main.rs

use tauri::Manager;

#[tauri::command]
fn greet(name: &str) -> String {
    format!("Hello, {}! From Rust 🦀", name)
}

#[tauri::command]
async fn read_file(path: String) -> Result<String, String> {
    std::fs::read_to_string(&path)
        .map_err(|e| e.to_string())
}

#[tauri::command]
fn get_system_info() -> SystemInfo {
    SystemInfo {
        os: std::env::consts::OS.to_string(),
        arch: std::env::consts::ARCH.to_string(),
        cpus: num_cpus::get(),
    }
}

#[derive(serde::Serialize)]
struct SystemInfo {
    os: String,
    arch: String,
    cpus: usize,
}

fn main() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![
            greet,
            read_file,
            get_system_info,
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

Gọi Command từ JavaScript

typescript
// src/App.tsx
import { invoke } from '@tauri-apps/api/core';

function App() {
  const [greeting, setGreeting] = useState('');
  const [systemInfo, setSystemInfo] = useState<SystemInfo | null>(null);
  
  const handleGreet = async () => {
    // Type-safe invocation
    const result = await invoke<string>('greet', { name: 'World' });
    setGreeting(result);
  };
  
  useEffect(() => {
    invoke<SystemInfo>('get_system_info').then(setSystemInfo);
  }, []);
  
  const handleReadFile = async () => {
    try {
      const content = await invoke<string>('read_file', { 
        path: '/path/to/file.txt' 
      });
      console.log(content);
    } catch (error) {
      console.error('Failed to read file:', error);
    }
  };
  
  return (
    <div>
      <button onClick={handleGreet}>Greet</button>
      <p>{greeting}</p>
      {systemInfo && (
        <p>Running on {systemInfo.os} ({systemInfo.arch})</p>
      )}
    </div>
  );
}

interface SystemInfo {
  os: string;
  arch: string;
  cpus: number;
}

3. State Management (Rust Side)

Managed State

rust
use std::sync::Mutex;
use tauri::State;

// Application state
struct AppState {
    counter: Mutex<i32>,
    db: Mutex<Database>,
}

#[tauri::command]
fn increment(state: State<'_, AppState>) -> i32 {
    let mut counter = state.counter.lock().unwrap();
    *counter += 1;
    *counter
}

#[tauri::command]
async fn get_users(state: State<'_, AppState>) -> Result<Vec<User>, String> {
    let db = state.db.lock().unwrap();
    db.get_all_users().map_err(|e| e.to_string())
}

fn main() {
    tauri::Builder::default()
        .manage(AppState {
            counter: Mutex::new(0),
            db: Mutex::new(Database::connect("sqlite:app.db")?),
        })
        .invoke_handler(tauri::generate_handler![increment, get_users])
        .run(tauri::generate_context!())
        .expect("error");
}

4. Events (Bidirectional Communication)

Emit từ Rust sang JavaScript

rust
use tauri::Manager;

#[tauri::command]
async fn start_long_task(window: tauri::Window) -> Result<(), String> {
    for i in 0..100 {
        // Emit progress event to frontend
        window.emit("progress", ProgressPayload { 
            percent: i,
            message: format!("Processing step {}...", i),
        }).unwrap();
        
        tokio::time::sleep(std::time::Duration::from_millis(100)).await;
    }
    
    window.emit("completed", ()).unwrap();
    Ok(())
}

#[derive(Clone, serde::Serialize)]
struct ProgressPayload {
    percent: u32,
    message: String,
}

Listen trong JavaScript

typescript
import { listen } from '@tauri-apps/api/event';

useEffect(() => {
  const unlisten = listen<ProgressPayload>('progress', (event) => {
    setProgress(event.payload.percent);
    setMessage(event.payload.message);
  });
  
  const unlistenComplete = listen('completed', () => {
    setProgress(100);
    setMessage('Done!');
  });
  
  return () => {
    unlisten.then(f => f());
    unlistenComplete.then(f => f());
  };
}, []);

5. File System Access

Tauri API (Frontend)

typescript
import { open, save } from '@tauri-apps/plugin-dialog';
import { readTextFile, writeTextFile } from '@tauri-apps/plugin-fs';

async function openFile() {
  const selected = await open({
    multiple: false,
    filters: [{ name: 'Text', extensions: ['txt', 'md'] }]
  });
  
  if (selected) {
    const content = await readTextFile(selected as string);
    return content;
  }
}

async function saveFile(content: string) {
  const filePath = await save({
    filters: [{ name: 'Text', extensions: ['txt'] }]
  });
  
  if (filePath) {
    await writeTextFile(filePath, content);
  }
}
rust
use std::path::PathBuf;

#[tauri::command]
async fn process_files(paths: Vec<PathBuf>) -> Result<ProcessResult, String> {
    let mut results = vec![];
    
    for path in paths {
        let content = tokio::fs::read_to_string(&path)
            .await
            .map_err(|e| e.to_string())?;
        
        // Process file...
        let processed = process_content(&content);
        results.push(processed);
    }
    
    Ok(ProcessResult { items: results })
}

6. Security Best Practices

Capability-based Security (Tauri v2)

json
// src-tauri/capabilities/main.json
{
  "identifier": "main",
  "description": "Main window capabilities",
  "windows": ["main"],
  "permissions": [
    "core:default",
    "fs:read-files",
    "fs:write-files",
    "dialog:open",
    "dialog:save"
  ]
}

Input Validation

rust
#[tauri::command]
fn save_config(config: UserConfig) -> Result<(), String> {
    // ALWAYS validate input from frontend
    validate_config(&config)?;
    
    // Sanitize paths
    let safe_path = sanitize_path(&config.data_dir)?;
    
    // Save...
    Ok(())
}

fn validate_config(config: &UserConfig) -> Result<(), String> {
    if config.name.len() > 100 {
        return Err("Name too long".into());
    }
    // More validations...
    Ok(())
}

7. Building & Distribution

Build for current platform

bash
# Development
npm run tauri dev

# Production build
npm run tauri build

Cross-platform builds

yaml
# .github/workflows/release.yml
name: Release
on:
  push:
    tags: ['v*']

jobs:
  release:
    strategy:
      matrix:
        platform: [macos-latest, ubuntu-22.04, windows-latest]
    runs-on: ${{ matrix.platform }}
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v2
      - uses: actions/setup-node@v4
      - uses: dtolnay/rust-toolchain@stable
      - uses: tauri-apps/tauri-action@v0
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Bảng Tóm tắt

FeatureAPI/Approach
Rust → JSwindow.emit(), #[tauri::command] return
JS → Rustinvoke(), Events
StateState<T> + Mutex
File System@tauri-apps/plugin-fs or Rust commands
Dialogs@tauri-apps/plugin-dialog
SecurityCapabilities, input validation