Use Cases (Diperbarui: 2/6/2026)

Membangun App Desktop Tauri dengan Claude Code: Vite, Rust Command, dan Permission

Panduan Tauri v2 dengan Claude Code: Vite, Rust command, invoke, permission file, test, build, dan prompt review.

Membangun App Desktop Tauri dengan Claude Code: Vite, Rust Command, dan Permission

Tauri mengubah UI web menjadi app desktop untuk Windows, macOS, dan Linux. UI tetap bisa dibuat dengan React, Svelte, atau stack frontend lain, sementara pekerjaan yang dekat dengan sistem operasi ditulis di Rust. Pembagian ini cocok untuk Claude Code karena tugas bisa dibuat kecil: satu layar UI, satu Rust command, satu capability, atau satu test.

Artikel ini memakai Tauri v2 dan contoh app catatan lokal. Kita akan membahas kapan memilih Tauri, setup dengan Vite + React atau Svelte, menulis Rust command, memanggilnya dari frontend dengan invoke, membatasi permission file system, menjalankan build/test, membuat prompt review permission, dan mengenali pitfall umum.

Referensi resmi yang dipakai: Tauri Create a Project, Calling Rust from the Frontend, Tauri Capabilities, File System plugin, Vite Getting Started, Cargo test, dan Claude Code setup. Untuk dasar Rust, baca Rust development dengan Claude Code. Untuk perbandingan, lihat Electron desktop app development.

Kapan Memilih Tauri

Tauri cocok jika app butuh bentuk desktop dan akses lokal, tapi kamu tetap ingin produktivitas web UI. Contohnya app catatan pribadi, converter CSV internal, log viewer untuk developer, tool Markdown lokal, atau app offline untuk lapangan dan inventaris.

Jangan memilih Tauri hanya karena terdengar ringan. Kamu tetap perlu mengurus toolchain Rust, packaging, signing, installer, perbedaan OS, identifier app, dan strategi update. Kalau produk hanya login lalu memanggil API server tanpa fitur lokal, web app biasa atau PWA bisa lebih sederhana.

Prompt awal untuk Claude Code sebaiknya membatasi ruang kerja:

We are building a Tauri v2 local notes app.
Do not implement yet.
Split the work into React UI, Rust commands, capabilities, and build/test checks.
File access must stay inside the app data directory.
Do not propose arbitrary path reads or broad file-system permissions.

Dengan ini, Claude Code mulai dari boundary dan bukan langsung memberi permission terlalu luas.

Boundary Arsitektur

Frontend tidak seharusnya menyentuh OS secara langsung. Ia memanggil Rust command lewat invoke; Rust memvalidasi input, mengakses resource lokal, lalu mengembalikan hasil yang jelas.

flowchart LR
  UI["React atau Svelte UI"] --> Invoke["invoke from @tauri-apps/api/core"]
  Invoke --> Command["Rust command"]
  Command --> Guard["path allowlist and validation"]
  Guard --> AppData["app data directory"]
  Command --> Result["typed result"]
  Result --> UI
  Capability["Tauri capability"] --> UI

Hal penting: capability Tauri membatasi API dan plugin Tauri yang bisa dipakai frontend, tetapi tidak otomatis membuat Rust command buatanmu aman. Kalau command menerima path bebas lalu menulis file, app tetap berisiko. Jadi capability dan validasi path di Rust harus direview bersama.

Setup React atau Svelte

Untuk project baru, gunakan jalur resmi create-tauri-app. Pilih React atau Svelte, lalu TypeScript.

npm create tauri-app@latest taskdesk
cd taskdesk
npm install
npm run tauri dev

Untuk project Vite yang sudah ada, siapkan frontend dulu lalu init Tauri. Dokumentasi Vite memakai npm create vite@latest dan versi saat ini membutuhkan Node.js 20.19+ atau 22.12+.

node --version
npm create vite@latest taskdesk -- --template react-ts
cd taskdesk
npm install
npm install -D @tauri-apps/cli@latest
npm install @tauri-apps/api@latest
npx tauri init
npx tauri dev

Untuk Svelte, ubah template saja:

npm create vite@latest taskdesk -- --template svelte-ts

Setelah setup, minta Claude Code membaca dulu:

Read package.json, tauri.conf.json, and src-tauri/src/lib.rs.
Summarize the current project layout and scripts.
Do not modify files yet.

Template Tauri bisa berubah. Membaca hasil generate lebih aman daripada memaksakan struktur lama.

Konfigurasi Tauri Minimal

tauri.conf.json menghubungkan Vite dev server, output build frontend, window, dan capability.

{
  "$schema": "https://schema.tauri.app/config/2",
  "productName": "TaskDesk",
  "version": "0.1.0",
  "identifier": "com.example.taskdesk",
  "build": {
    "beforeDevCommand": "npm run dev",
    "devUrl": "http://localhost:5173",
    "beforeBuildCommand": "npm run build",
    "frontendDist": "../dist"
  },
  "app": {
    "windows": [
      {
        "title": "TaskDesk",
        "width": 1000,
        "height": 700
      }
    ],
    "security": {
      "capabilities": ["main-capability"]
    }
  },
  "bundle": {
    "active": true,
    "targets": "all"
  }
}

Review identifier, devUrl, dan frontendDist. Tiga field ini sering membuat error build terlihat seperti masalah Rust padahal hanya konfigurasi yang tidak cocok.

Rust Command

App catatan perlu read, write, dan list. Contoh berikut fokus pada validasi path: menolak path absolut, .., dan akses keluar dari app data.

// src-tauri/src/note_commands.rs
use serde::Serialize;
use std::{
    fs,
    path::{Component, Path, PathBuf},
};
use tauri::{AppHandle, Manager};

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NoteFile {
    name: String,
    path: String,
    bytes: u64,
    is_dir: bool,
}

fn reject_unsafe_relative(path: &Path) -> Result<(), String> {
    for component in path.components() {
        match component {
            Component::Normal(_) | Component::CurDir => {}
            _ => return Err("use a relative path inside app data".to_string()),
        }
    }
    Ok(())
}

fn app_data_root(app: &AppHandle) -> Result<PathBuf, String> {
    let root = app
        .path()
        .app_data_dir()
        .map_err(|error| format!("failed to get app data dir: {error}"))?;
    fs::create_dir_all(&root).map_err(|error| format!("failed to create app data dir: {error}"))?;
    root.canonicalize()
        .map_err(|error| format!("failed to resolve app data dir: {error}"))
}

fn existing_path(app: &AppHandle, relative: &str) -> Result<PathBuf, String> {
    let root = app_data_root(app)?;
    let requested = Path::new(relative);
    reject_unsafe_relative(requested)?;
    let full = root
        .join(requested)
        .canonicalize()
        .map_err(|error| format!("path does not exist: {error}"))?;

    if !full.starts_with(&root) {
        return Err("path escapes app data".to_string());
    }

    Ok(full)
}

#[tauri::command]
pub fn read_note(app: AppHandle, path: String) -> Result<String, String> {
    let safe_path = existing_path(&app, &path)?;
    fs::read_to_string(safe_path).map_err(|error| format!("failed to read note: {error}"))
}

Daftarkan command di lib.rs:

// src-tauri/src/lib.rs
mod note_commands;

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![
            note_commands::read_note
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

Invoke dari TypeScript

Buat wrapper kecil agar nama command tidak tersebar di banyak komponen.

// src/lib/notesApi.ts
import { invoke } from "@tauri-apps/api/core";

export type NoteFile = {
  name: string;
  path: string;
  bytes: number;
  isDir: boolean;
};

export const notesApi = {
  read(path: string) {
    return invoke<string>("read_note", { path });
  },
  write(path: string, content: string) {
    return invoke<void>("write_note", { path, content });
  },
  list(dir = ".") {
    return invoke<NoteFile[]>("list_notes", { dir });
  },
};

Komponen React pertama bisa dibuat sederhana:

import { useState } from "react";
import { notesApi } from "./lib/notesApi";

export default function App() {
  const [content, setContent] = useState("");
  const [message, setMessage] = useState("Ready");

  async function saveNote() {
    await notesApi.write("daily-note.txt", content);
    setMessage("Saved");
  }

  return (
    <main>
      <textarea value={content} onChange={(event) => setContent(event.target.value)} />
      <button onClick={saveNote}>Save</button>
      <p>{message}</p>
    </main>
  );
}

Permission dan Capability

Kalau frontend memakai File System plugin secara langsung, scope capability harus sempit.

{
  "$schema": "../gen/schemas/desktop-schema.json",
  "identifier": "main-capability",
  "description": "Main window permissions for TaskDesk.",
  "windows": ["main"],
  "permissions": [
    "core:default",
    "fs:default",
    "fs:allow-app-read-recursive",
    "fs:allow-app-write-recursive"
  ]
}

Contoh artikel ini memakai Rust command untuk file, jadi frontend tidak perlu permission luas ke Downloads atau Documents. Prompt review:

Review only. Do not edit files.
Inspect src-tauri/capabilities and src-tauri/src/note_commands.rs.
List every frontend-exposed API and every Rust command.
State which file paths each can touch.
Flag absolute paths, parent-directory traversal, broad wildcards, and writes outside app data.
Suggest the smallest permission set that still supports the feature.

Build, Test, dan Use Case

Pisahkan verifikasi:

npm run build
cd src-tauri
cargo test
cd ..
npm run tauri build

npm run build mengecek Vite, cargo test mengecek Rust, dan npm run tauri build mengecek packaging desktop. Validasi path bisa dikunci dengan test kecil:

#[cfg(test)]
mod tests {
    use super::reject_unsafe_relative;
    use std::path::Path;

    #[test]
    fn rejects_parent_directory() {
        assert!(reject_unsafe_relative(Path::new("../secret.txt")).is_err());
    }
}

Use case yang kuat: app catatan lokal, converter CSV/Markdown, log viewer developer, dan app offline untuk inspeksi, check-in, atau inventaris. Pitfall umum: mengira capability sudah mengamankan Rust command, meminta Claude Code membuat seluruh app sekaligus, membawa config dev ke production, hanya test di satu OS, dan membuka permission file terlalu luas.

CTA dan Catatan Hasil Uji

Untuk membuat workflow ini berulang, lihat ClaudeCodeLab products and templates atau Claude Code training and consultation. Untuk tim, yang perlu distandarkan adalah CLAUDE.md, prompt review, capability, dan command verifikasi.

Pada alur yang diuji untuk artikel ini, urutan paling stabil adalah membuat batas path di Rust dulu, lalu wrapper TypeScript invoke, kemudian review capability. Mulai dari UI terasa cepat, tetapi sering menambah rework ketika lokasi penyimpanan dan permission mulai jelas.

#Claude Code #Tauri #Rust #desktop apps #frontend
Gratis

PDF gratis: cheatsheet Claude Code

Masukkan email dan unduh satu halaman berisi command, kebiasaan review, dan workflow aman.

Kami menjaga datamu dan tidak mengirim spam.

Masa

Tentang penulis

Masa

Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.