Membangun CatatanKu di Tauri 2: memahami arsitektur Rust + WebView, menulis commands sebagai jembatan IPC, memanfaatkan plugin ecosystem, dan mempraktikkan app Tauri pertama yang ringan dan aman

Setelah di episode 3 kita menguasai event loop, widget, dan UI state, kini saatnya memakai fondasi itu di framework pertama: Tauri 2. Tauri adalah pendekatan "Rust + Web": UI dibangun dengan HTML/CSS/JS (React, Svelte, atau Vue), tetapi backend adalah aplikasi Rust native yang berjalan langsung di sistem — bukan Node.js.
Mengapa Tauri menarik di 2026? Ukuran biner kecil (WebView sistem, bukan Chromium bundel), konsumsi memori hemat, dan keamanan model capability-based yang membatasi permukaan API. Episode ini membangun versi pertama CatatanKu di Tauri: scaffold project, struktur dasar, commands (IPC), dan pengenalan plugin ecosystem. Kita bandingkan juga praktiknya dengan Electron di episode 5.
invoke (request/response) dan emit/listen (event) yang menghubungkan keduanya.Perbedaan besar dari Electron: di Electron, backend adalah proses Node.js (runtime JS). Di Tauri, backend adalah binary Rust murni — tidak ada runtime kedua, dan akses sistem dikontrol ketat lewat izin.
Cek bahwa toolchain episode 0 sudah siap, lalu buat project dengan template resmi:
npm create tauri-app@latest catatanku -- --template react-ts
cd catatanku
bun install
bun run tauri devbun run tauri dev akan membuka jendela dengan halaman React default. Sampai tahap ini, kalian melihat UI web biasa — kekuatan Tauri mulai terasa begitu kita menambahkan command Rust di belakangnya.
catatanku/
├─ src/ # UI React (webview)
│ ├─ App.tsx
│ └─ main.tsx
├─ src-tauri/ # Backend Rust
│ ├─ src/
│ │ ├─ main.rs # entry point aplikasi
│ │ └─ lib.rs # setup, commands, plugins
│ ├─ Cargo.toml
│ ├─ tauri.conf.json # konfigurasi, window, bundler
│ └─ capabilities/
│ └─ default.json # izin (capabilities)Perhatikan capabilities/default.json — ini jantung model keamanan Tauri. Setiap akses (command, plugin, URL) harus diizinkan secara eksplisit. Kita bahas pengerasannya di episode 14.
Command adalah fungsi Rust yang bisa dipanggil dari frontend. Mari kita tambahkan create_note dan list_notes:
use std::sync::Mutex;
use tauri::State;
#[derive(Default)]
struct AppState {
notes: Mutex<Vec<String>>,
}
#[tauri::command]
fn create_note(state: State<AppState>, title: String) -> Result<u32, String> {
let mut notes = state.notes.lock().map_err(|e| e.to_string())?;
notes.push(title);
Ok(notes.len() as u32)
}
#[tauri::command]
fn list_notes(state: State<AppState>) -> Vec<String> {
state.notes.lock().unwrap().clone()
}
pub fn run() {
tauri::Builder::default()
.manage(AppState::default())
.invoke_handler(tauri::generate_handler![create_note, list_notes])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}State<AppState> adalah cara Tauri membagikan state aplikasi antar command. Mutex menjaga data aman diakses dari thread berbeda — konsep ownership Rust bekerja untuk keamanan concurrency.
import { invoke } from "@tauri-apps/api/core";
import { useState } from "react";
function App() {
const [notes, setNotes] = useState<string[]>([]);
const [title, setTitle] = useState("");
async function addNote() {
await invoke("create_note", { title });
setNotes(await invoke<string[]>("list_notes"));
}
return (
<main>
<input value={title} onChange={(e) => setTitle(e.target.value)} />
<button onClick={addNote}>Tambah</button>
<ul>{notes.map((n, i) => <li key={i}>{n}</li>)}</ul>
</main>
);
}
export default App;invoke("create_note", { title }) melewati IPC: argumen di-serialize (JSON), dikirim ke Rust, hasilnya dikembalikan. Nama argumen di JS (title) harus cocok dengan nama parameter Rust — perhatikan konvensi camelCase/snake_case Tauri yang bisa kalian atur.
Tip
Untuk state sederhana, Mutex<Vec<String>> cukup. Untuk data persisten yang benar-benar tersimpan di disk, kita ganti dengan SQLite di episode 8 — state di memori hilang saat aplikasi ditutup.
Salah satu kekuatan Tauri 2 adalah plugin system — paket resmi dan komunitas yang menambah kemampuan tanpa menulis semua dari nol:
tauri-plugin-store → persistent key-value store
tauri-plugin-sql → integrasi SQLite/MySQL/Postgres
tauri-plugin-updater → auto-update lintas platform
tauri-plugin-fs → akses filesystem dengan izin
tauri-plugin-shell → jalankan proses eksternal
tauri-plugin-clipboard-manager → clipboard & global shortcutInstall plugin cukup dengan menambahkan crate di Cargo.toml, mendaftarkannya di Builder, dan mengizinkan capability-nya di capabilities/default.json:
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"windows": ["main"],
"permissions": [
"core:default",
"store:default",
"sql:default"
]
}Prinsipnya: plugin hanya bisa digunakan jika kedua sisi (Rust builder + capability) diizinkan. Ini bagian dari model keamanan "allow by default, deny by default" — tidak ada yang bisa dipanggil tanpa izin eksplisit.
rename_all = "camelCase" atau pastikan konsisten.generate_handler! akan error "command not found" saat dipanggil.Inti yang harus dibawa pulang:
State + Mutex.capabilities/.Di episode 5 selanjutnya kita akan membangun CatatanKu yang sama di Electron (Node.js) — main/renderer process, IPC, dan packaging — agar kalian bisa membandingkan langsung dua pendekatan terpopuler. Sampai jumpa di episode 5!