【ファイル保存編】Tauriでローカルにデータを保存する – Todoアプリを作る④

Tauri×Next.jsでのTodoアプリ作成

はじめに

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

【機能編】Todoの追加・削除機能を実装しよう – Todoアプリを作る③
はじめに前回のUI編では、Tauri × Next.jsの構成で、ユーザーがタスクを入力・追加できるインターフェースを実装しました。今回はその追加機能として、タスクに「期限日」を設定できるフォームの拡...

今回は、TauriのRustバックエンドを使ってローカルファイルにTodoデータを保存・読み込みする処理を追加し、アプリ終了後もデータを保持できるようにしていきます。

また、今までNext.js側の修正のみでしたが、今回からはRust側のソースを使っていきましょう

Rustコードの追加(保存・読み込み処理)

Rustの処理を呼び出すにはtauri commandを使ってNext.js側から呼び出すようにします。

Calling Rust from the Frontend
The cross-platform app building toolkit

以下の処理を 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回:期限付きタスクを追加・削除する

タイトルとURLをコピーしました