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

Calling Rust from the Frontend

~13 min · tauri, invoke, frontend, ipc

Level 0Web Tourist
0 XP0/56 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete
"From the web side, native power is one function: invoke. Everything else is just what you pass and what you await."

One Function to Reach the Core

invoke (from @tauri-apps/api/core) is how the webview calls a registered command. You give it the command name and an object of arguments; it returns a Promise that resolves with whatever the command returned. Because it's a Promise, you await it and wrap it in try/catch exactly like a fetch — the mental model you already have for talking to a server transfers directly.

The camelCase ↔ snake_case Bridge

Here's the convention that surprises everyone once: your Rust parameter is user_id (snake_case, idiomatic Rust), but from JavaScript you pass { userId } (camelCase, idiomatic JS). Tauri maps between them automatically. Send snake_case from JS and the argument silently won't bind. Send camelCase and it just works. Let each language keep its own style; Tauri handles the seam.

Type the Return Where You Can

In TypeScript, invoke is generic: invoke<Note>('get_note', { id }) tells the compiler the resolved value is a Note. This is honest only if your Rust command really returns that shape — there's no compile-time link between the two sides, so the type you write is a promise you're making. The typed-ipc lesson shows how to make that promise harder to break.

Code

invoke: the only door to the core·tsx
import { invoke } from "@tauri-apps/api/core";

// Simple call — note camelCase args mapping to snake_case Rust params.
const greeting = await invoke<string>("greet", { name: "Pippa" });

// Rust param is `user_id`; JS sends `userId`. Tauri bridges the case.
const note = await invoke<Note>("get_note", { userId: 42, noteId: 7 });

// Treat it exactly like fetch: await + try/catch.
try {
  const sum = await invoke<number>("add", { a: 2, b: 3 });
  console.log(sum); // 5
} catch (err) {
  console.error("command failed:", err);
}

External links

Exercise

Wire the add command you wrote last lesson to a button in your frontend: on click, call invoke<number>('add', { a, b }) and render the result. Then deliberately send snake_case ({ a, b } is fine, but try renaming a param to a multi-word one like first_number and sending first_number from JS) and watch it fail — that failure teaches the camelCase rule better than any doc.
Hint
import { invoke } from '@tauri-apps/api/core'. For the failure demo, rename the Rust param to first_number, then send { firstNumber } (works) vs { first_number } (binds wrong) to feel the convention.

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.