【03】Next.js14でTodoアプリ作成(INSERT編)

# はじめに
Next.jsの知識を深めるために、Todoアプリを作ることにしました。そのメモを残しておきます。今回はデータ挿入についてのメモを残します。何か間違っていることなどございましたらコメントいただけますと、とても喜びます。

<details><summary>今までの記事</summary>

環境構築編

https://qiita.com/naoyuki2/items/e974c630c6cbd3c55254

【01】Next.js14でTodoアプリ作成(CREATE編)

https://qiita.com/naoyuki2/items/7474d448de7769905d82

【02】Next.js14でTodoアプリ作成(SELECT編)

https://qiita.com/naoyuki2/items/2d8ad09f767f818e8860

</details>

# 今回のゴール

データ挿入ができる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3582046/75dd9f60-48c7-47ae-4abf-5499f8a49117.png)


# １．APIの作成(POSTメソッド)

### POSTメソッドの記述

`src/app/api/todo/route.ts`に`POST`メソッドを記述していく。`GET`メソッドがすでにあるのでその下に書く。

```ts:src/app/api/todo/route.ts
export async function POST(request: Request) {
    // todoテーブルに新規レコードを追加
    const body = await request.json()
    try {
        const todo: todo = await prisma.todo.create({
            data: {
                title: body.title,
            },
        })
        return NextResponse.json(todo)
    } catch (error) {
        return NextResponse.json(error)
    }
}
```

### コード解説
```tsx
export async function POST(request: Request) {
```
この関数はHTTPリクエストを引数として受け取る。

`Request`という型がNext.jsで用意されているらしく、`import`せずとも使えるらしい。反対に`Response`という型もある。

```tsx
const body = await request.json():
```

この行は、受け取ったレスポンスからJSONデータを非同期に解析する。この中に`title`が入っている。

```tsx
const todo: todo = await prisma.todo.create({
    data: {
        title: body.title,
    },
})
```
`Prisma`の`create`関数を使用して新しい`todo`レコードをデータベースに挿入する。SQLでいう`INSERT INTO "todo" (title) values (body.title)`みたいなこと。

```tsx
return NextResponse.json(todo)
```
新しく作成された"todo"レコードをJSON形式で返す。

# ２．フロントエンドの実装

全部載せてるんで見ずらい。ごめんなさい。`+`がついている緑の行を前回から新たに追加した。

### コードの全体

```diff_tsx:src/app/page.tsx
'use client'

import { useEffect, useState } from 'react'
import { todo } from '@prisma/client'

export default function Home() {
    const [todos, setTodos] = useState<todo[]>([])
+   const [inputVal, setInputVal] = useState<string>('')

    useEffect(() => {
        const getTodo = async () => {
            const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/todo`)
            const data = await res.json()
            setTodos(data)
        }
        getTodo()
    }, [])

+   const postTodo = async () => {
+       if (!inputVal) return alert('タイトルを入力してください')
+       const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/todo`, {
+           method: 'POST',
+           headers: {
+               'Content-Type': 'application/json',
+           },
+           body: JSON.stringify({
+               title: inputVal,
+           }),
+       })
+       const data = await res.json()
+       setTodos([...todos, data])
+       setInputVal('')
+   }

    return (
        <>
+           <input
+               placeholder="タイトル入力"
+               value={inputVal}
+               onChange={(e) => setInputVal(e.target.value)}
+           />
+           <button onClick={() => postTodo()}>追加</button>
            {todos.map((todo) => (
                <div key={todo.id}>
                    <p>{todo.id}</p>
                    <p>{todo.title}</p>
                    <p>{todo.content}</p>
                    <p>{todo.isDone}</p>
                    <p>{new Date(todo.createdAt).toLocaleString()}</p>
                    <p>{new Date(todo.updatedAt).toLocaleString()}</p>
                </div>
            ))}
        </>
    )
}
```

### コードの解説

```tsx
const [inputVal, setInputVal] = useState<string>('')
```

タイトルを入力する`input`要素の`value`属性を管理するために`useState`を使用する。

```tsx:postTodo
const postTodo = async () => {
    if (!inputVal) return alert('タイトルを入力してください')...
```

`postTodo`関数を実行時に`inputVal`の中身が空だった場合はアラートを出し、`return`があるのでここで関数が終了する。

```tsx:postTodo
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/todo`, {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
    },
    body: JSON.stringify({
        title: inputVal,
    }),
})
```

[前々回の記事](https://qiita.com/naoyuki2/items/2d8ad09f767f818e8860#01nextjs14%E3%81%A7todo%E3%82%A2%E3%83%97%E3%83%AA%E4%BD%9C%E6%88%90create%E7%B7%A8)で`.env`ファイルにAPIルートを書いているため、今回も流用する。

今回は`POST`メソッドなので`method`タイプを記述する。

`JSON`形式でやり取りするため、`headers`にこのような記述をする。

`body`には`API`に渡すものを記述する。今回は`title`のみでいいので`title`情報を保持している`inputVal`を渡す。

```tsx:
const data = await res.json()
setTodos([...todos, data])
setInputVal('')
```

返ってきたレスポンスを`JSON`形式で`data`に代入する。

`useState`の`setTodos`で`todos`にデータを入れる。

`...todos`はスプレッド構文といって配列の中身を展開することができる。`todos`を展開して、`data`を新しく追加している。

https://zenn.dev/axpensive/articles/6f35d3da03044f

`setInputVal('')`で`input`要素を空にして次のタイトルを入力しやすいようにしている。

```tsx:
<input
    placeholder="タイトル入力"
    value={inputVal}
    onChange={(e) => setInputVal(e.target.value)}
/>
```

`value`には`useState`の`inputVal`を設定する。

そして、`onChange`イベントを設定することで`input`要素の中身が変更されるたびに`inputVal`の値を更新している。

```tsx:
<button onClick={() => postTodo()}>追加</button>
```

`onClick`イベントにより、先ほど作った`postTodo`関数を呼び出している。

# ３．挿入できるか試す！

これでおそらくデータの挿入ができるはず！！

空だったら下のようにアラートが出る。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3582046/40b50557-0f01-6896-83d5-468d14d7822a.png)

`テスト`と入力して、`追加`をクリックすると下に`テスト`というデータが追加されるはず。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3582046/3622d984-0694-0dae-3112-0a46f1db415c.png)

# おわりに

`POST`はまだ簡単な気がする。`UPDATE`や`DELETE`は条件が増えて大変そう。