CloudFlareにデプロイするためのRemixプロジェクトでテーブルの要素を一覧表示してみる

## 概要

CloudFlare + D1にデプロイすることを前提に作成したRemix + Prismaのプロジェクト（TODOリスト）にてテーブルに格納されている値を画面に表示してみる。

## 前提

下記の内容が完了していること。

https://qiita.com/miriwo/items/bdac2da0394ab6a32657

下記のパッケージが入っていること。

```
prisma
@prisma/client
@prisma/adapter-d1
```

## 方法

### tasksテーブル用のseedを作成

今回は前回までで作成してあったtasksテーブルの情報を一覧的に表示してみようと思う。
ただ、tasksテーブルにはデータは入っていない。ブラウザからinsertする処理も特段設けていないため、seedを使って表示するデータを作っておこうと思う。

1. `seeds`ディレクトリ直下に`tasks_seed.sql`を作成し、下記の様に記載（データのバリデーションを持たせたいため複数のレコードを追加）

    ```seeds/tasks_seed.sql
    -- InsertTable
    INSERT INTO tasks (name, memo, is_complete, task_category_master_id, created_at, updated_at, deleted_at)
    VALUES ('仕事タスク 未完了', '仕事タスク 未完了のメモ', 0, 1, '2021-01-01 00:00:00', '2021-01-01 00:00:00', NULL);
    
    INSERT INTO tasks (name, memo, is_complete, task_category_master_id, created_at, updated_at, deleted_at)
    VALUES ('仕事タスク 完了', '仕事タスク 完了のメモ', 1, 1, '2021-01-01 00:00:00', '2021-01-01 00:00:00', NULL);
    
    INSERT INTO tasks (name, memo, is_complete, task_category_master_id, created_at, updated_at, deleted_at)
    VALUES ('仕事タスク 未完了 メモ無し', NULL, 0, 1, '2021-01-01 00:00:00', '2021-01-01 00:00:00', NULL);
    
    INSERT INTO tasks (name, memo, is_complete, task_category_master_id, created_at, updated_at, deleted_at)
    VALUES ('仕事タスク 削除済み', '仕事タスク 削除済みのメモ', 0, 1, '2021-01-01 00:00:00', '2021-01-01 00:00:00', '2021-01-01 00:00:00');
    
    INSERT INTO tasks (name, memo, is_complete, task_category_master_id, created_at, updated_at, deleted_at)
    VALUES ('勉強タスク 未完了', '勉強タスク 未完了のメモ', 0, 2, '2021-01-01 00:00:00', '2021-01-01 00:00:00', NULL);
    
    INSERT INTO tasks (name, memo, is_complete, task_category_master_id, created_at, updated_at, deleted_at)
    VALUES ('勉強タスク 完了', '勉強タスク 完了のメモ', 1, 2, '2021-01-01 00:00:00', '2021-01-01 00:00:00', NULL);
    
    INSERT INTO tasks (name, memo, is_complete, task_category_master_id, created_at, updated_at, deleted_at)
    VALUES ('勉強タスク 未完了 メモ無し', NULL, 0, 2, '2021-01-01 00:00:00', '2021-01-01 00:00:00', NULL);
    
    INSERT INTO tasks (name, memo, is_complete, task_category_master_id, created_at, updated_at, deleted_at)
    VALUES ('勉強タスク 削除済み', '勉強タスク 削除済みのメモ', 0, 2, '2021-01-01 00:00:00', '2021-01-01 00:00:00', '2021-01-01 00:00:00');
    
    INSERT INTO tasks (name, memo, is_complete, task_category_master_id, created_at, updated_at, deleted_at)
    VALUES ('その他タスク 未完了', 'その他タスク 未完了のメモ', 0, 3, '2021-01-01 00:00:00', '2021-01-01 00:00:00', NULL);
    
    INSERT INTO tasks (name, memo, is_complete, task_category_master_id, created_at, updated_at, deleted_at)
    VALUES ('その他タスク 完了', 'その他タスク 完了のメモ', 1, 3, '2021-01-01 00:00:00', '2021-01-01 00:00:00', NULL);
    
    INSERT INTO tasks (name, memo, is_complete, task_category_master_id, created_at, updated_at, deleted_at)
    VALUES ('その他タスク 未完了 メモ無し', NULL, 0, 3, '2021-01-01 00:00:00', '2021-01-01 00:00:00', NULL);
    
    INSERT INTO tasks (name, memo, is_complete, task_category_master_id, created_at, updated_at, deleted_at)
    VALUES ('その他タスク 削除済み', 'その他タスク 削除済みのメモ', 0, 3, '2021-01-01 00:00:00', '2021-01-01 00:00:00', '2021-01-01 00:00:00');
    ```

1. プロジェクトルート直下の`execute_seeds.sh`を下記のように編集

    ```execute_seeds.sh
    #!/bin/bash
    
    # SQLファイルのリスト
    sql_files=(
        # NOTE: seedファイルを追加したらこちらにも追加
        "./seeds/task_category_masters_seed.sql"
        "./seeds/tasks_seed.sql"
    )
    
    # 各SQLファイルを実行
    for file in "${sql_files[@]}"; do
        npx wrangler d1 execute todo-cloudflare-d1 --local --file "$file"
    done
    ```

1. 下記を実行してマイグレーションのリセットとseedを実行してテストデータを格納（本コマンドはこちらで自前で追加した → [CloudFlareにデプロイするためのRemixプロジェクトでフレッシュマイグレーション + seed実行の仕組みを作ってみる - 更に便利になりそうなコマンドを追加](https://qiita.com/miriwo/items/bdac2da0394ab6a32657#%E6%9B%B4%E3%81%AB%E4%BE%BF%E5%88%A9%E3%81%AB%E3%81%AA%E3%82%8A%E3%81%9D%E3%81%86%E3%81%AA%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%82%92%E8%BF%BD%E5%8A%A0)）

    ```sh
    npm run local-migrate-reset-and-seed
    ```

1. table plusなどでローカルのD1（.wrangler/stage/v3/d1/miniflare-D1DatabaseObject直下の.sqliteファイルを指定）を確認して、下記のようにtasksテーブルにデータが入っていることを確認

    ![CleanShot 2024-06-15 at 11.58.09@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/5a286549-1709-adb1-c2a1-9f07125575d2.png)

### tasksテーブルの情報を画面に表示

1. 画面表示用ファイルを追加（`/tasks`でアクセスすることを想定）

    ```sh
    touch app/routes/tasks.tsx
    ```

1. 下記の様に記載

    ```app/routes/tasks.tsx
    import type { MetaFunction } from "@remix-run/cloudflare";
    
    export const meta: MetaFunction = () => {
      return [
        { title: "tasks" },
        {
          name: "description",
          content: "tasks",
        },
      ];
    };
    
    export default function Index() {
      return (
        <div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.8" }}>
          <h1>tasks</h1>
          <ul>
            <li>
                タスク1
            </li>
            <li>
                タスク2
            </li>
          </ul>
        </div>
      );
    }
    ```

1. `npm run dev`を実行してブラウザから`/tasks`にアクセスし、問題なく下記の様に表示されていることを確認

    ![CleanShot 2024-06-15 at 10.39.38@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/d76fb323-7735-2b88-a224-3128b84f0b63.png)

1. tasksテーブルにアクセスして取得するファイルを作成

    ```sh
    touch app/routes/tasks-loader.ts
    ```

1. `app/routes/tasks-loader.ts`を下記の様に記載

    ```app/routes/tasks-loader.ts
    import type { LoaderFunctionArgs } from "@remix-run/cloudflare";
    
    export const loader = async ({ context }: LoaderFunctionArgs) => {
      try {
        return await context.db.tasks.findMany();
      } catch (error) {
        console.error("Failed to load tasks:", error);
        throw new Response("Internal Server Error", { status: 500 });
      }
    }
    ```

1. `app/routes/tasks.tsx`を修正してtasksの情報を取得して表示

    ```app/routes/tasks.tsx
    import type { MetaFunction } from "@remix-run/cloudflare";
    import { loader } from "./tasks-loader";
    export { loader };
    import { useLoaderData } from "@remix-run/react";
    
    export const meta: MetaFunction = () => {
      return [
        { title: "tasks" },
        {
          name: "description",
          content: "tasks",
        },
      ];
    };
    
    interface Task {
      id: string;
      name: string;
      memo: string;
      is_complete: boolean;
      task_category_master_id: string;
      created_at: string;
      updated_at: string;
    }
    
    export default function Index() {
      const tasks = useLoaderData<Task[]>();
      return (
        <div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.8" }}>
          <h1>tasks</h1>
          <ul>
            {tasks.map((task: Task) => (
              <li key={task.id}>
                <div>id: {task.id}</div>
                <div>name: {task.name}</div>
                <div>memo: {task.memo}</div>
                <div>is complete: {task.is_complete}</div>
                <div>task category master id: {task.task_category_master_id}</div>
                <div>created at: {task.created_at}</div>
                <div>updated at: {task.updated_at}</div>
              </li>
            ))}
          </ul>
        </div>
      );
    }
    ```

1. /tasksにアクセスし、下記のように表示されていれば一覧表示は実装完了

    ![CleanShot 2024-06-15 at 12.02.55@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/189e5814-6e14-bb9e-f647-e2a999ed03eb.png)