【ハンズオン】Next.jsでPrismaを使ってみた

## はじめに
趣味でNext.jsの公式チュートリアルをやっていた時の話。
「やたらとSQLを記述することが多いから、インターンで利用経験があったPrismaを使って書き直してみよう！」と思ったのですが、最新の記事があまり見当たらなかったので導入方法をまとめてみます。

Next.js公式チュートリアル(App Router編)

https://nextjs.org/learn/dashboard-app

## Prismaってなに？
そもそもPrismaってなんぞや？となる人もいるかと思います。
PrismaはNode.jsで開発する際に使われるオープンソースのORM(Object-Relational Mapping)ツールです。
ここではORMについて詳しく解説しませんが、ざっくり言うとJavaScriptやTypeScriptのオブジェクトを扱うのと同じようにデータベースを操作することができます！(ざっくりしすぎたかもしれません...)

https://www.prisma.io/

## 導入方法
### Next.jsのプロジェクトをデプロイする
今回は公式チュートリアルに則ってVelcelを利用しました。もし、Velcelアカウントがなければ作成してgithubアカウントと連携しましょう。
- 連携が終わったら以下の画面から作成したリポジトリをimport
![01_deploy_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3957228/195a7188-fa44-41b7-93ce-d0e39bd697e1.png)
- プロジェクトに名前をつけてdeployをクリック
![02_deploy_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3957228/cef96d5f-650d-4a5e-86a4-83c4751780ef.png)

### データベースを作成する
今回はデータベースにPostgresを利用しました。2025年4月現在、多くのプロバイダーがありますがその中でもNeonを選択しました。
NeonはPostgresをサーバーレスで提供しているDBaaS(DataBase as a Service)になります。1アカウントあたり1データベースまで無料で利用できます!

https://neon.tech/

- プロジェクトダッシュボードからstorageを選択(私は作成済みのため以下のような表示になっています)
![03_db_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3957228/3341a7cb-c113-4a4b-8afe-6f9cd55b03ca.png)
- 一覧からNeonを選択
![04_db_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3957228/7d8f426f-c38e-464d-a987-fa8f79f7805e.png)
- RegionはWashington, D.C.、PlanはFreeを選択してcontinue
![スクリーンショット 2025-04-24 21.09.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3957228/bfc4dcb2-5401-4176-bc18-6c9d229f31c7.png)
- Next.jsプロジェクト内の.envにDATABASE_URLから始まる記述を貼り付け
![スクリーンショット 2025-04-24 21.14.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3957228/c5b269b1-80b7-46ab-b268-121aee531e0f.png)
### Prismaをインストールする
データベースの作成が完了したので本題であるPrismaをインストールしていきます。
- prisma, prisma clientのインストール
    ```shell
    $ npm install prisma @prisma/client
    ```
- スキーマ定義の生成
    ```shell
    $ npx prisma init
    ```
- prismaディレクトリ配下に生成されたschema.prismaにモデルを定義
    ```sql
    // 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 {
      id        String      @id @default(dbgenerated("uuid_generate_v4()")) @db.Uuid
      name      String
      email     String      @unique
      password  String
    }
    
    model Invoices {
      id          String      @id @default(dbgenerated("uuid_generate_v4()")) @db.Uuid
      customerId  String      @db.Uuid
      amount      Int
      status      String
      date        DateTime 
    
      customers   Customers   @relation(fields: [customerId], references: [id])
    }
    
    model Customers {
      id          String      @id @default(dbgenerated("uuid_generate_v4()")) @db.Uuid
      name        String
      email       String      @unique
      image_url   String
    
      invoices    Invoices[]
    }
    
    model Revenue {
      month      String       @unique
      revenue    Int
    }
    ```
- prismaクライアントを生成してデータベースにスキーマ定義を反映
    ```shell
    $ npx prisma generate
    $ npx prisma db push
    ```
- シードスクリプトの書き換え
    - app/lib/prisma.ts
        ```typescript
        import { PrismaClient } from "@prisma/client";
        
        export const prisma = new PrismaClient();
        
        export default prisma;
        ```
    - app/seed/route.ts
        ```typescript
        import bcrypt from 'bcryptjs';
        import { PrismaClient } from '@prisma/client';
        import { invoices, customers, revenue, users } from '../lib/placeholder-data';
        
        const prisma = new PrismaClient();
        
        async function seedUsers() {
          const insertedUsers = await Promise.all(
            users.map(async (user) => {
              const hashedPassword = await bcrypt.hash(user.password, 10);
              const seedUser = await prisma.user.create({
                data: {
                  id: user.id,
                  name: user.name,
                  email: user.email,
                  password: hashedPassword,
                },
              });
              return seedUser;
            }),
          );
        
          return insertedUsers;
        }
    
        async function seedInvoices() {
          const insertedInvoices = await Promise.all(
            invoices.map(async (invoice) => {
              const seedInvoices = await prisma.invoices.create({
                data: {
                  customerId: invoice.customer_id,
                  amount: invoice.amount,
                  status: invoice.status,
                  date: new Date(invoice.date),
                },
              });
              return seedInvoices;
            }),
          );
        
          return insertedInvoices;
        }
    
        (途中略)
    
        export async function GET() {
          try {
            await prisma.$transaction(async () => {
              await seedUsers();
              await seedCustomers();
              await seedInvoices();
              await seedRevenue();
          });
        
            return new Response(JSON.stringify({ message: 'Database seeded successfully' }), {
              status: 200,
            });
          } catch (error) {
            const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
            return new Response(JSON.stringify({ error: errorMessage }), { status: 500 });
          }
        }
        ```
- データベースにシード
開発サーバを立ち上げて http://localhost:3000/seed にアクセス
"Database seeded successfully"と表示されたら成功です!

## おわりに
今回はNext.jsにORMであるPrismaを導入する方法についてご紹介しました。ORMに頼り切りになるのは良くないとの記事を目にしたこともありますが、私自身も上手な付き合い方を探していければと思います!