タスケット開発日記:デー
タベース設定とMixhost連携で
二人共有タスク管理アプリが動き出す

関連記事

1. はじめに

前回までのタスケット開発では、基本的なUI設計と機能実装を行いました。タスケットとは、タスクを「まずすること」と「予定すること」の2つの領域に分け、二人で共有できるタスク管理アプリです。今回は、アプリの中核となるデータ保存と共有の仕組みに焦点を当てます。

2. データベース設計と構造

タスケットのデータベースは、大きく分けて2つのテーブルで構成されています。「テーブル」とは、データを表形式で保存する場所のことです。

一つ目の「タスケット」テーブルには、タスケットそのものの情報を保存します:

CREATE TABLE IF NOT EXISTS taskets (
    id VARCHAR(10) PRIMARY KEY,
    title VARCHAR(100) NOT NULL,
    created_at DATETIME NOT NULL,
    password_hash VARCHAR(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Code language: PHP (php)

二つ目の「タスク」テーブルには、各タスクの詳細情報を保存します:

CREATE TABLE IF NOT EXISTS tasks (
    id INT AUTO_INCREMENT PRIMARY KEY,
    tasket_id VARCHAR(10) NOT NULL,
    title TEXT NOT NULL,
    is_urgent BOOLEAN DEFAULT 1,
    is_important BOOLEAN DEFAULT 0,
    is_completed BOOLEAN DEFAULT 0,
    created_at DATETIME NOT NULL,
    created_by VARCHAR(50),
    completed_at DATETIME,
    completed_by VARCHAR(50),
    FOREIGN KEY (tasket_id) REFERENCES taskets(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Code language: PHP (php)

このような設計は、大きな本棚と小さなノートに例えられます。タスケットテーブルは本棚を表し、タスクテーブルはその本棚に収められた個々のノートのようなものです。各ノート(タスク)は特定の本棚(タスケット)に属しています。

3. Mixhostでのデータベース設定

タスケットをレンタルサーバーのMixhostで動かすためには、いくつかの設定が必要です。

まず、Mixhostの管理画面からデータベースを作成します。名前は「tasket_db」、文字コードは「utf8mb4」を選びます。utf8mb4は絵文字なども含めた幅広い文字を扱えるエンコーディングです。

次に、データベースにアクセスするためのユーザーを作成し、適切な権限を設定します。ここで注意したいのは、config.phpファイルに記載されているデータベースパスワードです。初期設定では「your_password」というプレースホルダーになっていますが、これを実際のパスワードに変更しないとアプリは動きません。

これは家の鍵を例にすると分かりやすいでしょう。データベースという家に入るためには、正しいカギ(パスワード)が必要です。「your_password」は「ここにカギを入れてください」という張り紙のようなもので、実際のカギではありません。

4. ユーザー認証の仕組み

タスケットの認証システムはとてもシンプルです。複雑なユーザー管理ではなく、タスケットIDとパスワードの組み合わせで認証を行います。

新しいタスケットを作成すると、10文字のランダムなIDが生成されます:

$tasket_id = generate_random_id(10);
Code language: PHP (php)

設定したパスワードは、安全のためにハッシュ化してデータベースに保存します:

$password_hash = password_hash($password, PASSWORD_DEFAULT);
Code language: PHP (php)

ハッシュ化とは、元のデータを一方向の変換処理にかけて、元に戻せない形に変える技術です。これは、食材をミキサーにかけてスムージーにするようなものです。スムージーから元の食材を取り出すことはできませんが、同じ食材を使えば同じ味のスムージーができます。

アプリにアクセスする際、URLは次のような形式になります:

https://app.chiilabo.jp/tasket/?id=ABC123XYZ#key-パスワード
Code language: JavaScript (javascript)

IDはURLのパラメータとして、パスワードはURLのハッシュ部分(#以降)として渡されます。ハッシュ部分はサーバーに送信されないため、パスワードがサーバーのログに残りにくい利点があります。

5. ブラウザ間のタスク同期の仕組み

タスケットの最も興味深い部分は、異なるブラウザ間でタスクを同期する仕組みです。それを、実際の流れに沿って説明します。

5.1. タスク追加時の流れ

ブラウザAで新しいタスクを追加すると、次のような処理が行われます:

  1. 一時的なローカルIDでタスクオブジェクトを作成します
  2. メモリ上のタスク配列に追加します
  3. 画面表示を更新します
  4. ローカルストレージにタスク情報を保存します
  5. タスケットIDがある場合(共有モード)、サーバーにも保存します
// タスク追加の簡略化した擬似コード
function タスク追加(タスク内容, 緊急=true, 重要=false):
    // タスクオブジェクトを作成
    新タスク = {
        id: 'local_' + 現在時刻,
        title: タスク内容,
        is_urgent: 緊急,
        is_important: 重要,
        is_completed: false,
        created_at: 現在時刻,
        created_by: ユーザー名
    }
    
    // メモリとローカルストレージに保存
    タスク配列.追加(新タスク)
    画面更新()
    ローカルストレージに保存()
    
    // サーバーにも保存(共有モードの場合)
    if (タスケットIDあり):
        サーバーに保存(新タスク)
Code language: JavaScript (javascript)

この流れは、ノートに新しい情報を書き込み、それをコピーして友達にも渡すようなものです。まず自分のノート(ローカルストレージ)に書き、次に友達用のコピー(サーバーデータベース)も用意します。

5.2. ブラウザB側での同期取得

一方、ブラウザBでは、定期的にサーバーに新しいタスクがないか問い合わせます:

// 定期的なタスク取得の擬似コード
function 定期的なタスク取得():
    // 30秒ごとに実行
    fetch(`api/get_tasks.php?id=${タスケットID}&since=${最終更新時刻}`)
        .then(レスポンス処理):
            if (成功):
                新しいタスクをマージ(新タスク)
                最終更新時刻を更新
                画面更新()
Code language: JavaScript (javascript)

これは、友達に「前回から何か新しいメモを書いた?」と30秒ごとに尋ねるようなものです。新しいメモがあれば、自分のノートにも追加します。

特筆すべきは、「最終更新時刻」を使って必要な情報だけを取得している点です。これにより、通信量を抑えて効率的に同期ができます。

5.3. タスク編集と削除の同期

タスクの編集や削除も、基本的に同じ流れで同期されます。例えば、タスクを編集した場合:

// タスク編集の擬似コード
function タスク編集(タスクID, 新タイトル):
    タスク = タスク配列から検索(タスクID)
    if (タスクあり):
        タスク.title = 新タイトル
        画面更新()
        ローカルストレージに保存()
        if (共有モード AND ローカルIDでない):
            サーバーに更新を送信(タスク)
Code language: JavaScript (javascript)

タスクを削除した場合も同様の手順を踏みます。まずローカルの情報を更新し、次にサーバーも更新します。ブラウザBは次の同期タイミングで変更を取得します。

6. オフライン対応の工夫

タスケットの面白い特徴として、インターネットに接続していなくても使える「オフライン対応」があります。

これを実現しているのが「ローカルストレージ」です。ローカルストレージとは、ブラウザがデータを保存できる小さな倉庫のようなものです。インターネットに接続していなくても、この倉庫にデータを出し入れできます。

タスケットでは、全てのタスク操作をまずローカルストレージに保存します。インターネット接続がある場合のみ、その変更をサーバーにも送信します。これにより、オフラインでも支障なく作業ができます。

さらに、PWA(Progressive Web Application)技術を使い、Service Workerがオフライン時にもアプリのリソースを提供します。Service Workerは、インターネットと端末の間に立つ配達員のような存在です。インターネットが切れても、以前取得した情報を配達してくれます。

// PWA Service Workerの簡略化したコード
self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request)
      .then(response => {
        // キャッシュにあればそれを返す
        if (response) return response;
        
        // なければネットワークにリクエスト
        return fetch(event.request)...
      })
  );
});
Code language: PHP (php)

7. セキュリティ対策

タスケットには、いくつかの基本的なセキュリティ対策が施されています。

まず、SQLインジェクション対策として、データベースクエリにはプリペアドステートメントを使用しています。これは、SQLコマンドとデータを分離して処理する方法です。料理に例えると、レシピ(SQLコマンド)と材料(データ)を別々に用意してから調理するようなものです。

$stmt = $db->prepare($query);
$stmt->bind_param('si', $completed_by, $task_id);
$stmt->execute();
Code language: PHP (php)

XSS(クロスサイトスクリプティング)対策としては、HTMLエスケープ関数を用意しています:

function h($str) {
    return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
}
Code language: PHP (php)

これは、特殊な文字を無害な形に変換する処理です。悪意のあるコードが紛れ込んでも、ただのテキストとして扱われるようになります。

CSRF(クロスサイトリクエストフォージェリ)対策として、トークンによる検証も実装されています。これは、偽の注文書を見分けるために注文書に特別なスタンプを押すようなものです。正規のリクエストにだけ、そのスタンプがあります。

8. まとめ

タスケットのデータ保存と同期の仕組みを掘り下げてみると、シンプルながらも効果的な設計が見えてきます。ローカルストレージとサーバーデータベースを組み合わせることで、オフライン対応と共有機能を両立させています。定期的なポーリングとタイムスタンプベースの同期により、効率的なデータ更新を実現しています。

データベース設計からユーザー認証、ブラウザ間同期まで、Webアプリケーションの基本的な仕組みがコンパクトに実装されていることが分かります。今回はMixhostというレンタルサーバーでの設定も含め、実用的なアプリケーション開発の一端を紹介しました。