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.

DesktopCross-platformRustWebViewLightweight

GitHub Overview

tauri-apps/tauri

Build smaller, faster, and more secure desktop and mobile applications with a web frontend.

Stars94,586
Watchers525
Forks2,967
Created:July 13, 2019
Language:Rust
License:Apache License 2.0

Topics

desktop-apphigh-performancemobile-appnative-apprustweb-frontendwebview

Star History

tauri-apps/tauri Star History
Data as of: 7/16/2025, 12:20 PM

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

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