【Nuxt3】ちょっとしたタスク管理アプリを作ってみる

# サンプルイメージ
作りかけですが、備忘録として<b>Nuxt3</b>を使ったアプリを作ってみます。
どういったものかといいますと、下記の画面から登録するタスク名を入力します。
`inputタグ`のすぐ下にチェックボックス(TrueまたはFalse)をチェックを入れるとデータベースPostgreSQLに登録されます。
<b>①タスク一覧画面</b>
![vue1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/64bf280d-4d1d-afd2-f42b-16b118261b25.png)

また、前述の位置ら㎜画面のID列のボタンをクリックすると、こちら②の詳細画面に遷移して陸得るとパラメータからID値を画面に表示するというものです。

<b>②詳細画面(作りかけ)</b>
![vue2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/7be0ad36-58e3-aad5-1a3c-957f996a50c4.png)

# 使用する技術スタック
|ソフトウェア|
|-----------|
|Nuxt3      |
|TypeScriot |
|TailwindCSS|
|Prisma     |
|PostgreSQL |

# ディレクトリ構造

```
プロジェクト名
├── pages/
│   ├── index.vue
│   └── detail.vue
├── server/
│   └── api/
│       ├── create.ts
│       └── task.ts
└── prisma/
    ├── migrate
    └── schema.prisma
```

# アプリ作成前の準備
今回、Nuxt3とデータベースを接続するために使うORMマッパーは、<b>Prisma</b>を使います。
こちらをプロジェクト直下のコマンド画面でインストールしておきましょう。

https://www.prisma.io/docs/getting-started/quickstart-sqlite


タスクの情報をデータベースに入れるので、PostgreSQLの接続情報を入れましょう。

```.env
DATABASE_URL="postgresql://(ユーザー名):(パスワード)@localhost:5432/(データベース名)"
```
今回使用するために更新したデータベース情報はこちら↓
```.env
# Environment variables declared in this file are automatically made available to Prisma.
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema

# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings

DATABASE_URL="postgresql://postgres:postgres@localhost:5432/nuxtdb"
```

## スキーマ定義を作成する

```prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model Task {
  id         Int      @id @default(autoincrement())
  task       String
  completed  Boolean
  created_at DateTime @default(now())
}

```

# (フロント側)一覧画面の作成
さて、準備が整ったのでまずは一覧画面を作ってみます。

タスク名が空文字を入力した場合は、登録しない処理も追加しておきました。

```pages/index.vue
  // 空のタスクは送信しない
  if (!task.value) {
    //警告メッセージを表示する
    taskErrorWarning.value = 'タスク名を入力してください。';
    return
  }; 

  //以降の処理は省略
```

全体のコードはこちら↓

```pages/index.vue
<script setup lang="ts">
import { ref } from 'vue';

// 新しいタスクを保持するローカル変数
const task = ref('');
//エラーメッセージをref定義する
const taskErrorWarning = ref('');
//チェックボックスをref定義する
const checked = ref(false);

// 全タスクを取得する
const { data: tasks } = useFetch('/api/task');

const handleCreateData = async (e: Event) => {
  // フォーム送信によるページリロードを防ぐ
  e.preventDefault(); 

  //警告メッセージを削除する
  taskErrorWarning.value = '';
  
  // 空のタスクは送信しない
  if (!task.value) {
    //警告メッセージを表示する
    taskErrorWarning.value = 'タスク名を入力してください。';
    return
  }; 

  const newTask = await useFetch('/api/create', {
    method: 'POST',
    body: {
      task: task.value,
      checked:checked.value,
    },
  });

  if(!newTask){
    //データベース登録失敗の場合
    let failValue = document.getElementById('failMag');
    failValue.innerHTML= '登録に失敗しました。';
    return;
  }

  let successValue = document.getElementById('successMag');
  successValue.innerHTML = '登録に成功しました。';
  console.log(task.value);
};
</script>

<template>
  <div class="flex items-center justify-center min-h-screen bg-gray-100">
    <div>
      <span id="successMag" class="h-14"></span>
      <span id="failMag" class="h-14"></span>
    </div>
    <div class="w-full max-w-sm p-6 bg-white rounded-lg shadow-md">
        <h1>タスク登録</h1>
        <form @submit.prevent="handleCreateData">
          <div>
            <input v-model="task" class="shadow apperrance-none border rounded" placeholder="タスクを入力" maxlength="10"/>
            <div class="inpurWarningArea">
              <span v-if="taskErrorWarning" class="label text-warning text-red-500">{{taskErrorWarning}}</span>
            </div>        
          </div>
          <div>
            <input type="checkbox" v-model="checked"/>
          </div>
          <div class="flex items-center justify-between">
            <button type="submit" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
              タスクを登録
            </button>
          </div>
      </form>
    </div>   
    <table class="table-auto">
      <thead>
        <tr>
          <th scope="col" class="border border-black px-4 py-4">ID</th>
          <th scope="col" class="border border-black px-4 py-4">タスク</th>
          <th scope="col" class="border border-black px-4 py-4">進捗</th>
          <th scope="col" class="border border-black px-4 py-4">更新日時</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="task in tasks" :key="task.id">
          <td class="border border-black px-4 py-4">
            <a :href="`/detail?id=${task.id}`" class="bg-green-500 text-white font-bold py-2 px-4 rounded">{{task.id}}</a>
          </td>
          <td class="border border-black px-4 py-4">{{task.task}}</td>
          <td class="border border-black px-4 py-4">
            <span v-if="task.completed == true" class="bg-blue-500 text-white font-bold py-2 px-4 rounded">完了</span>
            <span v-if="task.completed == false" class="bg-red-500 text-white font-bold py-2 px-4 rounded">まだ</span>
          </td>
          <td class="border border-black px-4 py-4">{{task.created_at}}</td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

```
つぎに、詳細画面です(作りかけです...)
一覧画面のID列をクリックしてリクエストパラメータにIDを付与します。
そのID値を`vue-routerコンポーネント`で受け取って画面に表示します。
Nuxt3では、`vue-routerコンポーネント`は標準でついているのでパッケージ管理ツール`yarn`や`npm`でのインストールは不要です。

```pages.detail.vue
<script setup lang="ts">
import {useRoute} from 'vue-router'
// 現在のルート情報を取得
const route = useRoute();

// クエリパラメータからidを取得
const taskId = route.query.id;

</script>
<template>
    <div>
        <h1>タスクの詳細ページ</h1>
        <h2>タスクのID値は、{{taskId}}</h2>    
    </div>
    
    
</template>
```

# サーバ側の作成
データベース登録するためのコード↓
```server/api/create.ts
import { PrismaClient  } from "@prisma/client"

const prisma = new PrismaClient()

export default defineEventHandler(async (event) => {
  const body = await readBody(event);

  //データベース登録完了フラグを用意する
  let flag:boolean = false;
  
  try{
    const newTask = await prisma.task.create({
      data: {
        task: body.task,
        completed: body.checked,//boolean値
      }
    });
    flag = true;
    return flag;
  }catch(error){
    return flag;
  }
  
})
```
データベースからレコードを取得するためのコード↓
```server/api/task.ts
import { PrismaClient } from "@prisma/client";

export default defineEventHandler(async(event)=>{
    const prisma = new PrismaClient();
    if(event.node.req.method === 'GET'){
        const tasks = await prisma.task.findMany();
        return tasks;
    }
    if(event.node.req.method === 'POST'){
        const body = await readBody(event);
        const newTask = await prisma.task.findMany({
            select:{
                id:true,
                task:true,
                completed:true
            },
            orderBy:{
                id:'asc'
            }
        })
              
        console.log("セレクトしたタスクは、" + newTask);
        return newTask;
    }
});
```

以上です。