【Next.js】WebAPIのNextResponse,NextRequestでPOST通信を実施する

# サンプル
#### ログイン画面でユーザー情報を入力して登録ボタンをクリックします。
![パスワード入力.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/5026d8f2-8952-1ab4-ea39-46f30568ef71.png)

#### データベースに未登録のユーザー(E-mailアドレスが被っていない　※以降で解説)はこのように登録できます。
![postgres登録.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/112edc35-33ae-6b33-e0f4-a27962b46a1f.png)

#### 一方、同じユーザを登録しようとするとサーバ側で検知してMessageやHTTPステータス、User情報をブラウザに返すように設計しています。
![デバッグ画面.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/d74ff890-240b-c966-ae91-096819c25a1c.png)

#### このようにアラート画面でユーザ被りをメッセージ表示します。
![既存ユーザーの場合.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/47b2cab4-c9cc-0f1e-5297-80c52f256288.png)

このようなサンプルを作っていきます。

# Micro Soft Edgeでのブレークポイントの設置方法
react(Next.js)でのブレークポイントの設置方法についてもここで説明しておきます。
#### `F12`または開発ツールでを開きます
![Edgeデバッグ1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/813fdfb5-5d5a-b2d9-6dfe-544c99a9544a.png)

#### Netwokタグを開いて対象のリンクをクリックします。
![Edgeデバッグ2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/ddfddca3-17b6-df8e-4dea-51de4281f493.png)

#### すると、対象のコードが表j氏有れるのでここでブレークポイントを設置します。
![Edgeデバッグ3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/e71a6b6a-df76-f0f8-4f7c-82f5f4972673.png)





# 今回使うNext.jsのバージョン

https://nextjs.org/blog/next-15

# NextRequestとNextResponseとは
Web APIで使用されるライブラリです。
Next.jsではHTTPメソッド(GET,POST,PUT,DELETE)でサーバとの通信を行うときに使用されます。

https://nextjs.org/docs/app/api-reference/functions/next-request

https://nextjs.org/docs/app/api-reference/functions/next-response

# ディレクトリ構造
Next.jsのプロジェクト構成です。
```
NextProject
└─ app
   ├─ admin
   │  └─ userRegister
   │     └─ page.tsx
   ├─ api
   │  └─ userRegister
   │     └─ route.ts
   ├─ components
   │  └─ headers
   │     └─ navbar
   │        └─ page.tsx
   ├─ prisma
   │  └─ schema.prisma
   ├─ next.config.ts
   ├─ package.json
   ├─ package.lock.json
   └─ tailwind.config.mjs
```

# Next.js ver13以降Route Handlerについて

https://nextjs.org/docs/app/building-your-application/routing/route-handlers

Next.js 13以降の`app/api`ディレクトリでは、`route.ts`や`route.js`ファイル内で`HTTPメソッド（GET、POST、PUT、DELETEなど）`を個別に定義することができます。
それぞれのメソッドに対して、対応する関数を作成することが必要です。

例えば、以下のように各HTTPメソッドに対して異なる関数を定義することができます。

また、プロジェクト作成時に`App Router`を使う場合は`api関連のフォルダやファイル`は<b>appディレクトリ直下</b>に配置する必要があります<b>↓</b>。

理由は、Next.jsが特定のHTTPメソッド（GET、POST、PUT、DELETEなど）を処理するために、メソッド名を特定のものとして認識しているからです。

Next.js 13以降では、`app/api`ディレクトリ内でファイル内に定義する関数の名前として、`**HTTPメソッド名（GET、POST、PUT、DELETE）**`を指定する必要があります。
例えば、`POST`メソッドを処理したい場合、その関数名は必ず `export async function POST `でなければなりません。

### なぜHTTPメソッド名を使う必要があるか？
Next.jsは、GETやPOSTなどのHTTPメソッドに対応する関数を動的にルーティングする仕組みを提供しています。
これにより、同じroute.ts内で異なるHTTPメソッドに対する処理を分けることができます。
GETやPOSTといった名前はNext.jsがそのメソッドを特定するために使うため、関数名を自由に変更してしまうとその識別ができず、リクエストが処理されません。

### 結論
Next.js では、<b>HTTPメソッド（GET, POST, PUT, DELETE）</b>に対応する関数名を使う必要があります。

#### route.tsのディレクトリ
```
NextProject
└─ app
   ├─ admin
   │  └─ userRegister
   │     └─ page.tsx
```
#### サンプルコード
```page.tsx
// app/api/userRegister/route.ts
import { NextRequest, NextResponse } from 'next/server';

export async function GET(req: NextRequest) {
  // GETリクエストの処理
  return NextResponse.json({ message: "GETリクエストが成功しました！" });
}

export async function POST(req: NextRequest) {
  // POSTリクエストの処理
  const { userName, email, password } = await req.json();
  
  if (!userName || !email || !password) {
    return NextResponse.json({ error: "全てのフィールドを入力してください。" }, { status: 400 });
  }

  // ユーザー登録処理など
  return NextResponse.json({ message: "ユーザー登録が成功しました！" }, { status: 200 });
}

export async function PUT(req: NextRequest) {
  // PUTリクエストの処理
  return NextResponse.json({ message: "PUTリクエストが成功しました！" });
}

export async function DELETE(req: NextRequest) {
  // DELETEリクエストの処理
  return NextResponse.json({ message: "DELETEリクエストが成功しました！" });
}

```

# 登録画面の実装
さて、前置きが長くなりましたがさっそく実装していきましょう。


# モデルスキーマの構造
`PostgreSQL`との接続するためのモデルスキーマはこちらになります。

https://www.postgresql.org/


今回は`User`を登録します。
ユニークキーには、`email`に対して設定しています。
なので、登録画面で<font color="red"><b>E-mailが同じだとサーバ側で弾かれます。</b></font>

#### スキーマの構成
<b>条件の整理</B>
#### User と Order (1 対多)
1 人のユーザーが複数の注文を持つ関係。
#### Order と Item (1 対多 または 1 対 1)
1 注文に対して、1 つまたは複数のアイテムが関連付けられる関係。

#### Item と Product (多対 1)
複数のアイテムが 1 つの製品に関連する関係。

```schema.prisma
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init

generator client {
  provider = "prisma-client-js"
}

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

model User{
  userId Int @id @default(autoincrement())
  userName String
  email String @unique
  password String
  createdAt DateTime @default(now())

  orders   Order[]  // 1人のユーザーは複数の注文を持つ
}

model Product{
  productId Int @id @default(autoincrement())
  productName String
  description String
  productImagepath String
  createdAt DateTime @default(now())
  updatedAt DateTime @default(now())

  items   Item[]   // 1つの製品は複数のアイテムに関連付けられる
}

model Item{
  itemId Int @id @default(autoincrement())
  quantity Int
  price Int
  createdAt DateTime @default(now())
  updatedAt DateTime @default(now())

  orderId   Int      // Order と 1対多 のリレーション
  order     Order    @relation(fields: [orderId], references: [orderId])
  productId Int      // Product と 多対1 のリレーション
  product   Product  @relation(fields: [productId], references: [productId])
}

model Order{
  orderId Int @id @default(autoincrement())
  total Int
  createdAt DateTime @default(now())
  updatedAt DateTime @default(now())

  userId    Int      // User と 1対多 のリレーション
  user      User     @relation(fields: [userId], references: [userId])
  items     Item[]   // 1つの注文には複数のアイテムが関連付けられる
}

```


### bcrypyjsモジュールをインストール
パスワードハッシュ化のためのモジュール`bcryptjs`をインストールしましょう。
```
npm install bcryptjs
```

https://www.npmjs.com/package/bcryptjs

## フロント画面の実装
登録画面を作っていきます。
WEB APIの`NextResponse`からの返却値をブラウザでも確認したいので、下記のように記載しています。

```app/admin/useRegister/page.tsx
        const response = await fetch('/api/userRegister', {
            method: 'POST',
            headers:{
                'Content-Type': 'application/json',
                cache: 'no-store',
            },
            body:JSON.stringify({
                userName: data.userName,
                email: data.email,
                password: data.password,
            })
        });

        //レスポンスデータをJSON取得する
        const responseJson = await response.json();
        console.log(responseJson); 

        // レスポンスを確認する
        if (response.ok) {
            const result = await response.json();
            alert("ユーザー登録成功");
            console.log('ユーザー登録成功：', result);
        }

        if(response.status === 500){
            alert(responseJson.user.userName + 'は、' + responseJson.message);
        }
        
```
#### 全体のコードはこちら↓

```app/admin/useRegister/page.tsx
'use client' // 付けること
import React, { useState } from "react";
import { useForm } from "react-hook-form";
//import axios from "axios";

export default function Login() {
    // 既定値を準備する
    const defaultValues = {
        userName: '山田太郎',
        email: 'sample@email.com',
        password: ''
    }

    // フォームを初期化する
    const { register,handleSubmit ,formState: { errors } } = useForm({ //handleSubmit
        defaultValues
    });

    const [userName, setUserName] = useState("");
    const [email, setEmail] = useState("");
    const [password, setPassword] = useState("");

    // フォーム送信時に実行される関数
    const onSubmit = async (data:any) => {//onSubmit data: any
        
        //console.log("フォーム送信データ：", data);
        const targetData :any = JSON.stringify({
            "userName": data.userName,
            "email": data.email,
            "password": data.password,
          });
        // Postリクエスト送信
        
        const response = await fetch('/api/userRegister', {
            method: 'POST',
            headers:{
                'Content-Type': 'application/json',
                cache: 'no-store',
            },
            body:JSON.stringify({
                userName: data.userName,
                email: data.email,
                password: data.password,
            })
        });
        //レスポンスデータをJSON取得する
        const responseJson = await response.json();
        console.log(responseJson);                        

        // レスポンスを確認する
        if (response.ok) {
            const result = await response.json();
            alert("ユーザー登録成功");
            console.log('ユーザー登録成功：', result);
        }

        if(response.status === 500){
            alert(responseJson.user.userName + 'は、' + responseJson.message);
        }
    }

    return (
        <div className="flex items-center justify-center min-h-screen rounded-md">
            <form onSubmit={handleSubmit(onSubmit)} className="shadow-md w-96 px-8 py-8 border border-gray-300 rounded-md">
                <div className="w-full">
                    <label className="font-bold">ユーザー名</label>
                    <input
                        type="text"
                        name="userName"
                        className="block rounded-md w-full text-sm border border-gray-300 text-gray-900 px-2 py-2"
                        defaultValue={userName}
                        placeholder=""
                        {...register('userName', {
                            required: '名前は必須です',
                            maxLength: {
                                value: 20,
                                message: '名前は20文字以内にしてください。'
                            }
                        })}
                        onChange={(e) => setUserName(e.target.value)}
                    />
                    <div className="text-red-500">{errors.userName?.message}</div>
                </div>
                <div className="w-full mt-4">
                    <label className="font-bold">E-mail</label>
                    <input
                        type="email"
                        {...register('email', {
                            required: 'メールアドレスは必須です。',
                            pattern: {
                                value: /([a-z\d+\-.]@[a-z\d-]+(?:\.[a-z]+)*)/i,
                                message: 'メールアドレスの形式が不正です。'
                            }
                        })}
                        name="email"
                        defaultValue={email}
                        className="block rounded-md w-full text-sm border border-gray-300 text-gray-900 px-2 py-2"
                        onChange={(e) => setEmail(e.target.value)}
                    />
                    <div className="text-red-500">{errors.email?.message}</div>
                </div>
                <div className="w-full mt-4">
                    <label className="font-bold">パスワード</label>
                    <input
                        type="password"
                        {...register('password', {
                            required: 'パスワードは必須です。'
                        })}
                        name="password"
                        defaultValue={password}
                        className="block rounded-md w-full text-sm border border-gray-300 text-gray-900 px-2 py-2"
                        onChange={(e) => setPassword(e.target.value)}
                    />
                    <div className="text-red-500">{errors.password?.message}</div>
                </div>
                <div className="flex items-center justify-center mt-4 mb-4">
                    <button type="submit" className="shadow-md bg-blue-500 text-white font-bold rounded-md px-4 py-4">
                        登録
                    </button>
                </div>
            </form>
        </div>
    );
}

```

# サーバ側の実装
NextRequestとNextReponseを使って画面からのデータを取得・返却するように設計しています。
```app/api/userRegister/route.ts
// app/api/userRegister/route.ts

import { NextRequest, NextResponse } from 'next/server';
import { PrismaClient } from '@prisma/client';
import bcrypt from 'bcryptjs'

const prisma = new PrismaClient();

export async function POST(req: NextRequest) {
  try {
    let { userName, email, password } = await req.json();
    console.log("パスワード（ハッシュ化前）:", password);
    const hashedPassword:any = await bcrypt.hash(password,10);
    console.log("ハッシュ化後のパスワード:", hashedPassword);
    // パラメータが不足している場合はエラーレスポンス
    if (!userName || !email || !password) {
      return NextResponse.json({ error: "全てのフィールドを入力してください。" }, { status: 400 });
    }

    //登録済みのユーザか確認
    const existUser = await prisma.user.findUnique({
      where:{
        email:email
      }
    });

    if(existUser){
      console.log("登録済みユーザー：" + existUser);
      return NextResponse.json({message:"すでにユーザー登録されています",user:existUser,status:500},{status:500});
    }

    // ユーザーをデータベースに挿入
    const newUser = await prisma.user.create({
      data: {
        userName,
        email,
        password:hashedPassword, // 実際のアプリケーションではパスワードをハッシュ化することを忘れないでください
      },
    });
    
    // 成功レスポンスを返す
    return NextResponse.json({ message: "ユーザー登録が成功しました！", user: newUser }, { status: 200 });
  } catch (error) {
    console.error("エラー:", error);
    if (error instanceof Error) {
      console.error("エラー詳細:", error.message);
    }
    return NextResponse.json({ error: "ユーザー登録中にエラーが発生しました。" }, { status: 500 });
  }
}

```

以上です。

# 

# 参考にしたサイト

https://zenn.dev/kiriyama/articles/87b8911973444d

https://thilog.com/react-modal-sample-typescript-tailwind/

https://qiita.com/unsoluble_sugar/items/b080a16701946fcfce70

https://zenn.dev/n2lab/articles/c613f1cd870738

https://zenn.dev/kiriyama/articles/788af09432333c#jwt-%E3%81%A8%E3%81%AF%EF%BC%9F

https://qiita.com/RintaroNasu/items/28ccb634bf6f6b971ef1

https://reffect.co.jp/react/next-js-13#GET

https://zenn.dev/m_suzuki/articles/55e284daf2bf71

https://qiita.com/RinTenma/items/020b2e9bad846871c926