Tauri
Next-generation desktop framework combining Rust backend with web frontend. Significantly lighter than Electron (2.5-3MB) with enhanced security. Enables integration with any frontend framework.
GitHub Overview
tauri-apps/tauri
Build smaller, faster, and more secure desktop and mobile applications with a web frontend.
Topics
Star History
Framework
Tauri
Overview
Tauri is a lightweight desktop app development framework built with Rust. Uses OS native WebView, achieving 90% memory reduction and 75% size reduction compared to Electron.
Details
Tauri is a next-generation desktop application framework developed in the Rust language. Rapidly growing with over 70,000 GitHub stars in 2025, it's gaining attention as a new choice for lightweight, high-performance, secure desktop app development.
Tauri takes a different approach from Electron by using OS-native WebViews (Windows: WebView2, macOS: WKWebView, Linux: WebKitGTK) instead of bundling Chromium. This enables significant reduction in application size and optimization of memory usage.
The frontend can use any web framework (React, Vue, Angular, Svelte, etc.), while the backend is written in Rust. By combining Rust's safety and performance with web technology UIs, it enables development of high-performance, secure desktop applications.
Advantages & Disadvantages
Advantages
- Lightweight & High Performance: 90% memory reduction, 75% size reduction vs Electron
- OS Native: Uses platform-specific WebViews
- Security Focused: Rust safety and sandboxing
- Flexible Frontend: Free choice of React, Vue, Angular, Svelte, etc.
- Fast Startup: Near-native app startup times
- Small Bundle: Significantly smaller distribution package size
- Growing Community: Active development and community support
Disadvantages
- Rust Learning Curve: Requires Rust knowledge for backend development
- Relatively New: Less mature ecosystem compared to Electron
- WebView Constraints: Dependent on platform WebView implementations
- Platform Differences: Potential behavioral differences due to WebView variations
- Debugging Complexity: Requires knowledge of both Rust and web technologies
Key Links
- Tauri Official Site
- Tauri Official Documentation
- Tauri CLI
- create-tauri-app
- Tauri GitHub Repository
- Tauri Community
Code Examples
Basic Tauri App
// src-tauri/src/main.rs
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use tauri::Manager;
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}! You've been greeted from Rust!", name)
}
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
Tauri Configuration File
// src-tauri/tauri.conf.json
{
"build": {
"beforeBuildCommand": "npm run build",
"beforeDevCommand": "npm run dev",
"devPath": "http://localhost:3000",
"distDir": "../dist"
},
"package": {
"productName": "my-tauri-app",
"version": "0.1.0"
},
"tauri": {
"allowlist": {
"all": false,
"fs": {
"all": true
},
"dialog": {
"all": true
}
},
"bundle": {
"active": true,
"targets": "all",
"identifier": "com.tauri.dev",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/icon.ico"
]
},
"security": {
"csp": null
},
"windows": [
{
"fullscreen": false,
"height": 600,
"resizable": true,
"title": "my-tauri-app",
"width": 800
}
]
}
}
Frontend (React Example)
// src/App.tsx
import { useState } from "react";
import { invoke } from "@tauri-apps/api/tauri";
import "./App.css";
function App() {
const [greetMsg, setGreetMsg] = useState("");
const [name, setName] = useState("");
async function greet() {
setGreetMsg(await invoke("greet", { name }));
}
return (
<div className="container">
<h1>Welcome to Tauri!</h1>
<div className="row">
<form
onSubmit={(e) => {
e.preventDefault();
greet();
}}
>
<input
id="greet-input"
onChange={(e) => setName(e.currentTarget.value)}
placeholder="Enter a name..."
/>
<button type="submit">Greet</button>
</form>
</div>
<p>{greetMsg}</p>
</div>
);
}
export default App;
Defining Rust Commands
// src-tauri/src/main.rs
use std::fs;
use tauri::State;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct Person {
name: String,
age: u32,
}
#[tauri::command]
fn read_file(path: String) -> Result<String, String> {
fs::read_to_string(path).map_err(|e| e.to_string())
}
#[tauri::command]
fn write_file(path: String, content: String) -> Result<(), String> {
fs::write(path, content).map_err(|e| e.to_string())
}
#[tauri::command]
fn get_person_info(name: String, age: u32) -> Person {
Person { name, age }
}
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![
greet,
read_file,
write_file,
get_person_info
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
File Operations Frontend
// src/FileManager.tsx
import { useState } from "react";
import { invoke } from "@tauri-apps/api/tauri";
import { open, save } from "@tauri-apps/api/dialog";
function FileManager() {
const [fileContent, setFileContent] = useState("");
const [currentFile, setCurrentFile] = useState<string | null>(null);
const openFile = async () => {
try {
const filePath = await open({
filters: [
{ name: "Text", extensions: ["txt"] },
{ name: "All", extensions: ["*"] }
]
});
if (filePath) {
const content = await invoke<string>("read_file", {
path: filePath
});
setFileContent(content);
setCurrentFile(filePath as string);
}
} catch (error) {
console.error("Error opening file:", error);
}
};
const saveFile = async () => {
try {
let filePath = currentFile;
if (!filePath) {
filePath = await save({
filters: [
{ name: "Text", extensions: ["txt"] }
]
});
}
if (filePath) {
await invoke("write_file", {
path: filePath,
content: fileContent
});
setCurrentFile(filePath);
}
} catch (error) {
console.error("Error saving file:", error);
}
};
return (
<div>
<div>
<button onClick={openFile}>Open File</button>
<button onClick={saveFile}>Save</button>
</div>
<textarea
value={fileContent}
onChange={(e) => setFileContent(e.target.value)}
placeholder="File content..."
rows={20}
cols={80}
/>
{currentFile && <p>Current file: {currentFile}</p>}
</div>
);
}
export default FileManager;
State Management and Events
// src-tauri/src/main.rs
use std::sync::Mutex;
use tauri::{Manager, State};
struct AppState {
counter: Mutex<i32>,
}
#[tauri::command]
fn increment_counter(state: State<AppState>) -> i32 {
let mut counter = state.counter.lock().unwrap();
*counter += 1;
*counter
}
#[tauri::command]
fn get_counter(state: State<AppState>) -> i32 {
*state.counter.lock().unwrap()
}
#[tauri::command]
fn emit_custom_event(app: tauri::AppHandle, message: String) {
app.emit_all("custom-event", message).unwrap();
}
fn main() {
tauri::Builder::default()
.manage(AppState {
counter: Mutex::new(0),
})
.invoke_handler(tauri::generate_handler![
increment_counter,
get_counter,
emit_custom_event
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
Cargo.toml Configuration
# src-tauri/Cargo.toml
[package]
name = "my-tauri-app"
version = "0.1.0"
description = "A Tauri App"
authors = ["you"]
license = ""
repository = ""
edition = "2021"
[build-dependencies]
tauri-build = { version = "1.0", features = [] }
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
tauri = { version = "1.0", features = ["api-all"] }
[features]
default = ["custom-protocol"]
custom-protocol = ["tauri/custom-protocol"]
Project Creation and Execution
# Install Tauri CLI
npm install -g @tauri-apps/cli
# or
cargo install tauri-cli
# Create new Tauri project
npm create tauri-app
# or
yarn create tauri-app
# or
pnpm create tauri-app
# Navigate to project directory
cd tauri-app
# Install dependencies
npm install
# Start development server
npm run tauri dev
# Production build
npm run tauri build
# Run with Rust only
cd src-tauri
cargo run
# Release build with Rust
cargo run --release