はじめに
これまでの記事では、期限付き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回:期限付きタスクを追加・削除する