Tauri Adapter
Build native desktop applications with Rust as the backend and any web framework as the frontend. Tauri apps are 10× smaller than Electron with native OS performance.
Tauri 2 · Rust Backend · Native OS
Rust backend
Your app logic runs as a native Rust binary — memory-safe, fast, and with direct OS API access.
Tiny bundles
A Tauri app with React frontend is typically 5–15MB. Electron equivalent: 150–200MB.
Security model
Fine-grained capability system: each window only gets the OS permissions it explicitly declares.
Cross-platform
Single Rust + web codebase. Builds for macOS, Linux, and Windows from a single CI workflow.
Commands
Rust functions decorated with #[tauri::command] are callable from JavaScript via invoke().
Events
Bidirectional event system between Rust and JS. Emit events from Rust and listen in React.
Setup
bash
# Prerequisites: Rust stable toolchain curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh npm create lunx-dev@latest my-tauri-app -- --framework tauri --ts cd my-tauri-app && npm install && npm run dev
Project structure
text
my-tauri-app/ ├── src/ # React/Vue/Svelte frontend │ ├── main.tsx │ └── App.tsx ├── src-tauri/ # Rust backend │ ├── src/ │ │ ├── main.rs # Tauri app entry │ │ ├── lib.rs # Commands and event handlers │ │ └── db.rs # SQLite database (optional) │ ├── Cargo.toml │ └── tauri.conf.json # App metadata and permissions ├── lunx.config.ts └── package.json
Rust commands
rustsrc-tauri/src/lib.rs
use tauri::State; use std::sync::Mutex; pub struct AppState { pub db_path: Mutex<String>, } // Commands are callable from JavaScript via invoke() #[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] async fn greet(name: &str) -> String { format!("Hello, {}! Built with Tauri + Lunx.", name) } #[tauri::command] async fn get_system_info() -> serde_json::Value { serde_json::json!({ "os": std::env::consts::OS, "arch": std::env::consts::ARCH, "cores": num_cpus::get(), }) } pub fn run() { tauri::Builder::default() .invoke_handler(tauri::generate_handler![ read_file, greet, get_system_info, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); }
Calling Rust from JavaScript
typescriptsrc/App.tsx
import { invoke } from '@tauri-apps/api/tauri' import { open } from '@tauri-apps/api/dialog' import { useState } from 'react' export default function App() { const [greeting, setGreeting] = useState('') const [fileContent, setFileContent] = useState('') async function greet() { // Calls the Rust greet() command const result = await invoke<string>('greet', { name: 'Lunx' }) setGreeting(result) } async function openFile() { // Opens native OS file picker dialog const path = await open({ multiple: false, filters: [{ name: 'Text', extensions: ['txt', 'md'] }] }) if (typeof path === 'string') { const content = await invoke<string>('read_file', { path }) setFileContent(content) } } return ( <div className="p-8"> <button onClick={greet}>Greet with Rust</button> <p>{greeting}</p> <button onClick={openFile}>Open File</button> <pre>{fileContent}</pre> </div> ) }
TypeScript types from Rust
Lunx auto-generates TypeScript types from your Rust command signatures using
tauri-specta. The invoke() calls are fully typed — catch mismatches at compile time before they reach the user.