| name | tauri-apps |
| description | Tauri desktop app development — Rust backend, web frontend, native APIs, small binary size, cross-platform. Use when working with tauri apps. |
| domain | development |
| author | oyi77 |
| license | Apache-2.0 |
| subdomain | software-development |
| tags | ["api","apps","coding","software-engineering","tauri","testing"] |
| version | 1.0.0 |
Overview
Tauri is a framework for building lightweight, secure desktop applications with a web-based frontend and a Rust backend. Unlike Electron, Tauri uses the OS's native WebView, resulting in significantly smaller binaries (typically <10MB) and lower memory usage.
Capabilities
- Build desktop apps with React/Vue/Svelte/Solid frontend + Rust backend
- Native OS API access (filesystem, shell, notifications, system tray)
- Tiny binary size (~3-10MB vs Electron's ~150MB+)
- Low memory usage (~30MB vs Electron's ~100MB+)
- Cross-platform: Windows, macOS, Linux
- Auto-update mechanism built in
- Secure IPC between frontend and Rust commands
- Custom protocols, file associations, deep links
- Sidecar and shell command execution
When to Use
Trigger phrases:
-
"tauri apps"
-
"Tauri desktop app development — Rust backend, web frontend, native APIs, small b"
-
Need desktop app with small footprint
-
Want native performance without bundling Chromium
-
Security-sensitive applications (Rust backend)
-
System tray apps, menu bar utilities
-
Replacing Electron for resource-constrained environments
-
Need native filesystem/shell access from web UI
When NOT to Use
- Task is about deployment, not development (use deploy skills)
- Task is about code review, not writing (use review skills)
- You need to understand existing code first (use research skills)
- Task is about testing only (use test skills)
- Requirements are unclear (clarify first)
- Task is trivially simple (single line fix)
Pseudo Code
The tauri-apps workflow follows a standard pipeline pattern.
Core flow:
# tauri-apps primary flow
input = prepare(raw_data)
result = process(input, config={apis, apps, backend, binary, cross})
validate(result)
deliver(result)
Error handling:
on error:
log(error_details)
retry_with_backoff(max=3)
if still_failing: alert_and_escalate()
Project Setup
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
npm create tauri-app@latest my-app
cd my-app
npm run tauri dev
npm run tauri build
Tauri Commands (Rust ↔ Frontend IPC)
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}!", 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]
async fn save_data(key: String, value: String, app: tauri::AppHandle) -> Result<(), String> {
let app_dir = app.path_resolver().app_data_dir().unwrap();
let file_path = app_dir.join(format!("{}.json", key));
std::fs::write(file_path, value).map_err(|e| e.to_string())
}
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet, read_file, save_data])
.(tauri::generate_context!())
.();
}
Frontend Invocation
import { invoke } from '@tauri-apps/api/tauri';
import { open, save } from '@tauri-apps/api/dialog';
import { readTextFile, writeTextFile } from '@tauri-apps/api/fs';
import { appDataDir, join } from '@tauri-apps/api/path';
const greeting = await invoke<string>('greet', { name: 'World' });
const filePath = await open({
multiple: false,
filters: [{ name: 'Text', extensions: ['txt', 'md'] }],
});
if (filePath) {
const content = await readTextFile(filePath as string);
console.log(content);
}
const savePath = await save({ filters: [{ name: 'JSON', extensions: ['json'] }] });
if (savePath) {
await writeTextFile(savePath, .(data, , ));
}
System Tray
use tauri::{SystemTray, SystemTrayMenu, SystemTrayMenuItem, CustomMenuItem};
fn main() {
let tray_menu = SystemTrayMenu::new()
.add_item(CustomMenuItem::new("show", "Show Window"))
.add_item(CustomMenuItem::new("hide", "Hide Window"))
.add_native_item(SystemTrayMenuItem::Separator)
.add_item(CustomMenuItem::new("quit", "Quit"));
let system_tray = SystemTray::new().with_menu(tray_menu);
tauri::Builder::default()
.system_tray(system_tray)
.on_system_tray_event(|app, event| {
match event {
tauri::SystemTrayEvent::MenuItemClick { id, .. } => {
match id.as_str() {
"show" => {
let window = app.get_window("main").unwrap();
window.show().unwrap();
}
"hide" => {
let window = app.().();
window.().();
}
=> std::process::(),
_ => {}
}
}
_ => {}
}
})
.(tauri::generate_context!())
.();
}
Auto Update
[dependencies]
tauri = { version = "1", features = ["updater"] }
fn main() {
tauri::Builder::default()
.setup(|app| {
Ok(())
})
.run(tauri::generate_context!())
.expect("error running tauri app");
}
import { checkUpdate, installUpdate } from '@tauri-apps/api/updater';
const update = await checkUpdate();
if (update.shouldUpdate) {
await installUpdate();
}
Tauri Events
app.emit_all("progress-update", 75).unwrap();
import { listen } from '@tauri-apps/api/event';
const unlisten = await listen<number>('progress-update', (event) => {
console.log('Progress:', event.payload);
});
Error Handling
| Error | Cause | Fix |
|---|
Cargo build failed | Missing Rust dependencies | Check rustup update, install system deps |
WebView not found | Missing system WebView (Linux) | Install libwebkit2gtk-4.0-dev |
Permission denied | Tauri allowlist not configured | Add command to tauri.conf.json allowlist |
Command not found | Rust command not registered | Add to invoke_handler in main.rs |
Build failed: code signing | macOS signing issue | Set signing identity in Xcode or env var |
Common Patterns
Proven patterns for tauri-apps usage.
- Batch processing: Process multiple items in parallel for throughput
- Retry with backoff: Handle transient failures gracefully
- Rate limiting: Respect API limits with configurable delays
- Logging: Structured logging for debugging and audit trails
Window Management
use tauri::Manager;
let window = tauri::WindowBuilder::new(
&app, "settings", tauri::WindowUrl::App("/settings".into())
).title("Settings").inner_size(600.0, 400.0).build()?;
Persistent Store
import Store from 'tauri-plugin-store-api';
const store = new Store('.settings.dat');
await store.set('theme', 'dark');
await store.save();
const theme = await store.get<string>('theme');
Shell Commands
use tauri::api::process::{Command, CommandEvent};
#[tauri::command]
async fn run_script(script: String) -> Result<String, String> {
let (mut rx, _child) = Command::new("sh")
.args(["-c", &script])
.spawn()
.map_err(|e| e.to_string())?;
let mut output = String::new();
while let Some(event) = rx.recv().await {
match event {
CommandEvent::Stdout(line) => output.push_str(&line),
CommandEvent::Stderr(line) => output.push_str(&format!("ERR: {}", line)),
_ => {}
}
}
Ok(output)
}
Custom Protocol
{
"build": {
"beforeDevCommand": "npm run dev",
"beforeBuildCommand": "npm run build",
"devPath": "http://localhost:5173",
"distDir": "../dist"
},
"tauri": {
"bundle": {
"identifier": "com.example.myapp",
"icon": ["icons/icon.png"]
},
"windows": [{
"title": "My App",
"width": 1024,
"height": 768
}]
}
How to Use
- Understand the requirement and existing codebase patterns
- Design the solution with error handling and testability in mind
- Implement incrementally with tests for each change
- Verify against expected outcomes (manual and automated)
- Document usage, edge cases, and integration points
- Review with team before merging to shared branches
Red Flags
- Skipping tests to ship faster: Untested code breaks in production when you least expect it
- No error handling in production code: Unhandled errors crash services and lose user data
- Hardcoded configuration values: Hardcoded values prevent environment switching and leak secrets
- Ignoring security implications: Missing input validation, auth bypasses, and injection vulnerabilities
- Over-engineering simple solutions: Premature abstraction adds complexity without proportional benefit
Verification
Process
- Analyze the task requirements
- Apply domain expertise
- Verify output quality
Anti-Rationalization Table
| Rationalization | Reality |
|---|
| "Tests slow me down" | Bugs slow you down 10x more. Tests are speed, not overhead. |
| "I will refactor later" | Technical debt compounds. Refactor as you go. |
| "It works on my machine" | If it is not in CI, it does not work. Ship proof, not claims. |