はじめに
これまでの記事では、期限付きTodoの追加・削除までを実装してきました。
ただし、現状では、アプリを閉じるとタスクの情報はすべて消えてしまいます。

今回は、TauriのRustバックエンドを使ってローカルファイルにTodoデータを保存・読み込みする処理を追加し、アプリ終了後もデータを保持できるようにしていきます。
また、今までNext.js側の修正のみでしたが、今回からはRust側のソースを使っていきましょう
Rustコードの追加(保存・読み込み処理)
Rustの処理を呼び出すにはtauri commandを使ってNext.js側から呼び出すようにします。

以下の処理を src-tauri/src/lib.rs に追記します。
use serde::{Deserialize, Serialize};
use std::fs::{self, File};
use std::io::{Read, Write};
use std::path::PathBuf;
#[derive(Serialize, Deserialize)]
struct Todo {
    text: String,
    due: String,
}
#[tauri::command]
fn save_todos(todos: Vec) -> Result<(), String> {
    let dir = get_data_directory();
    let path = dir.join("todos.json");
    let json = serde_json::to_string_pretty(&todos).map_err(|e| e.to_string())?;
    fs::create_dir_all(&dir).map_err(|e| e.to_string())?;
    File::create(&path)
        .and_then(|mut f| f.write_all(json.as_bytes()))
        .map_err(|e| e.to_string())?;
    Ok(())
}
#[tauri::command]
fn load_todos() -> Result<Vec, String> {
    let dir = get_data_directory();
    let path = dir.join("todos.json");
    if !path.exists() {
        return Ok(Vec::new());
    }
    let mut file = File::open(&path).map_err(|e| e.to_string())?;
    let mut contents = String::new();
    file.read_to_string(&mut contents)
        .map_err(|e| e.to_string())?;
    serde_json::from_str(&contents).map_err(|e| e.to_string())
}
fn get_data_directory() -> PathBuf {
    PathBuf::from(if cfg!(debug_assertions) {
        "target/debug/data"
    } else {
        "data"
    })
}
このコードでは:
save_todosで Vec<Todo> を JSON に変換して保存load_todosで JSON を読み込んで Vec<Todo> に変換get_data_directoryを使ってアプリのディレクトリに保持するようにしました
次に、関数がNext.js側でも使えるようにします
src-tauri/src/lib.rsのrun()関数に「.invoke_handler(tauri::generate_handler![save_todos, load_todos])」を追加します
pub fn run() {
    tauri::Builder::default()
        .setup(|app| {
            if cfg!(debug_assertions) {
                app.handle().plugin(
                    tauri_plugin_log::Builder::default()
                        .level(log::LevelFilter::Info)
                        .build(),
                )?;
            }
            Ok(())
        })
        .invoke_handler(tauri::generate_handler![save_todos, load_todos])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
これでNext.js側でもsave_todos、load_todosの関数を呼び出せるようになりました。
Next.js側でRust関数を呼び出す
では、tauri commandを使ってnext.js側からrustの関数を呼び出してみましょう
まずは、必要なライブラリからインストールします
npm install @tauri-apps/api
invokeを使った呼び出し
rust側の関数はinvokeを使って呼び出すことができます。
まず、先ほど定義したtauri commandの関数を呼び出す処理を追加するために新しくsrc/commands/Todo.tsを作成します。src/commands/Todo.ts
import { invoke } from "@tauri-apps/api/core";
type Todo = { text: string; due: string };
export class TodoCommand {
  static async saveTodos(todos: Todo[]) {
    await invoke("save_todos", { todos });
  }
  static async loadTodos(): Promise<Todo[]> {
    return await invoke("load_todos");
  }
}
invoke関数では第1引数にはlib.rsのinvoke_handlerで追加した関数名を、第2引数でtauri command関数に与えるパラメータを指定できます。
useEffectで起動時に読み込み
次に、src/components/TodoCard.tsxにuseEffectを追加して画面が呼び出された時にTodoを読み込むようにします
useEffect(() => {
    TodoCommand.loadTodos().then((data) => setTodos(data));
  }, []);
useEffectで保存処理
次は、Todo情報が更新された際に保存するような処理を追加します
  useEffect(() => {
    TodoCommand.saveTodos(todos);
  }, [todos]);
実行結果
では、実行しましょう。
ます、タスクを追加してみます。

追加すると、「src-tauri/target/debug/data/todos.json」が作成されます。
中身を見ると、追加したタスクが記載されています。
[
  {
    "text": "テスト",
    "due": "2025-05-31"
  }
]
では、1度アプリを終了させ、再度起動させましょう。先ほど追加したTodoが残っていますね。
削除しても同様に、消えた状態で保存されています。
まとめ
今回は、TauriのRustバックエンドを活用して、ローカルファイルへの保存・読み込み処理を組み込みました。
これにより、アプリを終了してもデータが保持され、実用的なデスクトップアプリに一歩近づきました。
次回は、実際に作成したアプリをリリース用にビルド、配布を行います。
次の記事:【ビルド&配布編】Tauriアプリをリリースしよう – Todoアプリを作る⑤
🔗 関連記事:
✅ 第1回:Tauriとは?環境構築とプロジェクト初期化
✅ 第2回:Tauri × Next.jsでTodoリスト画面を作ろう
✅ 第3回:期限付きタスクを追加・削除する
  
  
  
  