Building Native Desktop Apps with Tauri 2 and Rust
A practical guide to building small, fast, native Windows desktop apps using Tauri 2, Rust, and React TypeScript — based on real production experience.
- Tauri 2 ships 4–6 MB binaries vs Electron's 120–150 MB — a 25× size difference.
- The trade-off is Rust: anything touching the OS must be written in Rust.
- The frontend is pure React + TypeScript — no Rust required there.
- For small utility tools where binary size and startup time matter, Tauri is the right call.
When I built Draft — a local-first AI text refiner for Windows — I chose Tauri 2 over Electron. Six months later, that decision still looks right. This post covers what I learned building a production desktop app with Tauri 2, and why it is my default choice for native Windows tooling in 2026.
What is Tauri 2?
Tauri is a framework for building native desktop applications using web technologies for the frontend (HTML, CSS, JavaScript) and Rust for the backend. Unlike Electron, which ships a full copy of Chromium with every app, Tauri uses the operating system’s own WebView — WebView2 on Windows, WKWebView on macOS. The result is dramatically smaller binaries and lower memory usage.
Tauri 2, released in late 2024, added first-class mobile support (iOS and Android) alongside the desktop targets, unified permissions model, and a completely redesigned plugin system.
Why Tauri over Electron?
The honest answer: binary size and startup time.
A minimal Electron app ships around 120–150 MB. A minimal Tauri app ships around 4–6 MB. For a small utility tool like Draft — something a user installs and forgets is running — that difference matters significantly.
Memory usage follows the same pattern. Electron keeps a full Chromium process alive, consuming 80–120 MB of RAM at idle. Tauri’s Rust backend idles at roughly 10–20 MB.
The trade-off is that Tauri requires Rust knowledge. The frontend is pure web (React + TypeScript in my case), but anything that touches the OS — file system, window management, system tray, hotkeys — is written in Rust. If you are not comfortable with Rust’s borrow checker, the initial learning curve is steep.
Project setup
# Prerequisites: Rust (rustup), Node.js
cargo install create-tauri-app --locked
npm create tauri-app@latest my-app
Choose the React + TypeScript frontend template. Tauri 2 scaffolds a project with two distinct parts:
src/— your React frontendsrc-tauri/— the Rust backend
my-app/
├── src/ # React + TypeScript frontend
│ ├── App.tsx
│ └── main.tsx
├── src-tauri/ # Rust backend
│ ├── src/
│ │ └── main.rs
│ ├── Cargo.toml
│ └── tauri.conf.json
└── package.json
Calling Rust from TypeScript
The core of Tauri is the command system. You define functions in Rust and call them from the frontend using invoke.
Rust (src-tauri/src/main.rs):
use tauri::command;
#[command]
fn refine_text(input: String, mode: String) -> Result<String, String> {
// process the input — call an AI API, parse text, etc.
Ok(format!("Refined: {}", input))
}
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![refine_text])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
TypeScript (src/App.tsx):
import { invoke } from '@tauri-apps/api/core';
async function refine(text: string) {
const result = await invoke<string>('refine_text', {
input: text,
mode: 'clean',
});
console.log(result);
}
The invoke call is type-safe when you define the return type with the generic parameter. Errors thrown in Rust propagate as rejected promises on the TypeScript side.
Global hotkeys
One of Draft’s core features is a system-wide hotkey (Ctrl+Shift+D) that works in any application. Tauri 2 handles this with the global-shortcut plugin.
cargo add tauri-plugin-global-shortcut
use tauri_plugin_global_shortcut::{Code, GlobalShortcutExt, Modifiers, Shortcut};
fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
.setup(|app| {
let ctrl_shift_d = Shortcut::new(
Some(Modifiers::CTRL | Modifiers::SHIFT),
Code::KeyD,
);
app.handle().global_shortcut().on_shortcut(ctrl_shift_d, |_app, _shortcut, _event| {
// trigger the refine workflow
println!("Hotkey fired");
})?;
Ok(())
})
.run(tauri::generate_context!())
.expect("error running application");
}
Window configuration
Tauri 2 controls window properties in tauri.conf.json. For a floating utility like Draft, I want a small, always-on-top window with no native title bar:
{
"app": {
"windows": [
{
"label": "main",
"title": "Draft",
"width": 580,
"height": 420,
"decorations": false,
"alwaysOnTop": true,
"transparent": true,
"resizable": false,
"visible": false
}
]
}
}
Setting visible: false on startup and showing the window only when the hotkey fires keeps the app out of the way until needed.
The permissions model
Tauri 2 introduced a capability-based permissions system. Every plugin capability your app uses must be explicitly declared in src-tauri/capabilities/. This is more secure than Electron’s model, where any renderer can call any Node.js API.
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": ["main"],
"permissions": [
"core:path:default",
"core:window:default",
"global-shortcut:allow-register",
"global-shortcut:allow-unregister"
]
}
Declare only what you need. If a permission is not listed, the API call returns an error — even in development.
Build and distribution
npm run tauri build
The output is a signed .exe installer (NSIS) and a portable .exe. For auto-updates, Tauri has a built-in updater that checks a JSON endpoint you host anywhere.
Binary size for Draft in release mode: 7.2 MB installer, 4.1 MB portable.
When not to use Tauri
- You need broad Linux desktop support with complex system integrations — WebView2 is Windows/macOS-first and Linux WebView support varies by distribution.
- Your team has no Rust experience and cannot invest in learning it — the compile errors are not friendly to beginners.
- You need to ship in weeks, not months — Electron’s larger ecosystem means faster access to pre-built solutions for many common problems.
Conclusion
Tauri 2 is the right tool when you want small, fast, native-feeling Windows desktop apps and are comfortable writing Rust for system-level work. For utilities like Draft — tools that live in the background and respond to hotkeys — the small binary and low memory footprint are genuine advantages users notice.
The frontend stays as familiar React + TypeScript, so the bulk of the UI work feels normal. The Rust layer only needs to cover OS-specific needs: hotkeys, file access, window control, and anything performance-critical.
If you are building something similar, feel free to reach out or check the Draft source for reference.