The three ways the frontend talks to the Rust backend in Tauri, when to use each, and the one gotcha that wasted an afternoon.
Tauri gives you three ways to communicate between the frontend (JS) and backend (Rust). They're not interchangeable.
The most common pattern. Frontend calls a Rust function and awaits the result.
// Rust backend
#[tauri::command]
fn get_config(key: String) -> Result<String, String> {
std::env::var(&key).map_err(|e| e.to_string())
}
// Register it
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![get_config])// Frontend
import { invoke } from "@tauri-apps/api/tauri";
const value = await invoke<string>("get_config", { key: "HOME" });Good for: one-shot requests where you need a response. File reads, config lookups, one-time computations.
Karanveer Singh Shaktawat
Full Stack Engineer & Infrastructure Architect
Building portfolio, contributing to open source, and seeking remote full-time roles with significant technical ownership.
Pick what you want to hear about — I'll only email when it's worth it.
Did this resonate?
Building Docsee — a cross-platform Docker management tool with a Tauri GUI and terminal TUI, and why Rust was the right choice.
The difference between static dispatch (impl Trait) and dynamic dispatch (dyn Trait) — not just syntax, but a real tradeoff.
// Backend emitting to frontend
app.emit_all("file-changed", Payload { path: "/etc/nginx.conf" }).unwrap();// Frontend listening
import { listen } from "@tauri-apps/api/event";
await listen<{ path: string }>("file-changed", (event) => {
console.log("Changed:", event.payload.path);
});Good for: long-running background tasks, file watchers, progress updates. The backend pushes; the frontend reacts.
invoke() serializes arguments and return values through JSON. If your Rust struct has a field named type (a reserved word in JS/TS), the deserialization on the frontend silently fails.
// This causes silent deserialization failure on the JS side
#[derive(Serialize)]
struct FileEvent {
path: String,
type: String, // "type" is reserved in JS
}Rename to event_type or use #[serde(rename = "eventType")]. The error message from Tauri in this case is not helpful — it looks like the command succeeded but you get undefined.
invoke()