C.W.K.
Stream
Lesson 03 of 06 · published

Two Brains, One App

~13 min · tauri, ipc, architecture, process-model

Level 0Web Tourist
0 XP0/56 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete
"The webview can ask. Only the core can act."

Who Runs What

Picture two brains. The webview brain thinks in the language of the screen: DOM, events, layout, your React tree. It is sandboxed — it cannot open a file, spawn a process, or read the network the way a server can. The core brain thinks in the language of the machine: files, sockets, OS APIs, threads. It is fully privileged. Tauri's entire design is about letting these two cooperate without letting the untrusted one (the webview, which might render remote or attacker-influenced content) reach straight into the privileged one.

So they don't call each other's functions directly. They send messages. The webview calls invoke('do_thing', { ... }); Tauri routes that to a Rust function you marked as a #[tauri::command]; the function runs in the core and returns a value that travels back to the webview as a resolved Promise.

Everything That Crosses Is Data

Because the two halves don't share memory, anything that crosses the bridge is serialized — turned into plain data (think JSON) on one side and rebuilt on the other. You cannot hand the webview a live file handle or a Rust struct with a database connection inside it. You hand it numbers, strings, arrays, and objects. This is the same discipline you already know from talking to an HTTP API: the wire carries data, not live references. Internalize it now and the bridge track will feel obvious.

Why This Is the Security Story Too

The message boundary isn't just plumbing — it's the fence. The webview can only invoke the exact commands you registered, and (as you'll see in security) only the ones a capability grants it. A bug or an injected script in your frontend can't rm -rf the disk, because the frontend has no disk access to abuse — it can only ask the core, and the core only answers requests it was told to allow.

Code

The bridge, drawn·text
         WEBVIEW (sandboxed)                CORE (privileged, Rust)
         your React / Svelte / HTML         the native process
  ┌───────────────────────────┐      ┌──────────────────────────┐
  │  invoke("read_note", {id}) │ ───► │  #[tauri::command]        │
  │                           │ IPC  │  fn read_note(id) -> ...  │
  │  Promise resolves with    │ ◄─── │  reads file, returns data │
  │  plain data               │      │                          │
  └───────────────────────────┘      └──────────────────────────┘
         can ASK                              can ACT
  no filesystem, no sockets            files, sockets, OS, threads
Crossing the bridge, from the web side·tsx
// Frontend (TypeScript). This is the ONLY way to reach native power.
import { invoke } from "@tauri-apps/api/core";

// Returns a Promise — the call hops to Rust and back.
const note = await invoke<string>("read_note", { id: 42 });
//    ^ plain data came across the bridge, not a live handle

External links

Exercise

List three things your dream app needs to do that the browser sandbox forbids — e.g. 'read a folder of images,' 'run ffmpeg,' 'write a settings file.' For each, write the shape of the message you'd send: a command name and the data in and out. You're sketching your IPC surface before you write a line of Rust.
Hint
Good command shapes are verbs with plain-data arguments: read_folder(path) -> string[], transcode(input, output) -> bool. If your argument is a 'live object,' rethink it — only data crosses.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.