【Next.js × Prisma】Server ActionsからSQLiteデータベースを操作する

## はじめに

本記事では、TypeScript 向けの ORM（Object-Relational Mapping）である [Prisma](https://www.prisma.io/) の使い方を解説します。
お問い合わせフォームを題材にし、Next.js の Server Actions 機能を用いて、サーバーサイドで SQLite データベースにアクセスする構成のデモ形式で進めます。

#### 開発環境

- Node.js v20.11.1 以上
- npm v9.x 以上

---

## Prisma のインストール

以下のコマンドを実行して、Prisma および Prisma Client をインストールします。
今回はバージョン6を指定します。

```bash
npm install prisma@^6
npm install @prisma/client@^6
```

## Prisma の初期設定

以下のコマンドを実行して、初期設定を行います。

```bash
npx prisma init
```

実行後、プロジェクトのフォルダ直下に `.env` ファイルと `prisma/schema.prisma` ファイルが作成されます。

---

## デモ構成について

このデモでは、`src/prisma` 配下に SQLite ベースのお問い合わせフォーム用データベースを作成し、Next.js の Server Actions から操作できるようにします。

---

## 1. SQLite の設定

このデモでは、開発用のファイルベースデータベースである「SQLite」を使用します。

まず、`.env` にデータベースの URL を設定します。
データベース名は `contact-form.db` とします。

```env: .env
CONTACT_DATABASE_URL="file:./contact-form.db"
```

続いて、`schema.prisma` を編集します。
役割を明確にするために、`src/prisma/contact-form` フォルダを作成し、初期の `schema.prisma` をコピーして使用します。

以下のように設定します。

```prisma: schema.prisma
datasource db {
  provider = "sqlite"
  url      = env("CONTACT_DATABASE_URL")
}
```

---

## 2. テーブルの設定

`ContactForm` テーブルを作成します。テーブルの構成は以下のとおりです。

| カラム名  | データ型 | 制約                     |
| :-------- | :------- | :----------------------- |
| id        | String   | 主キー、デフォルト値あり |
| userName  | String   |                          |
| mail      | String   | 重複不可                 |
| createdAt | DateTime | デフォルト値あり         |

`schema.prisma` ファイルでの定義は以下のようになります。

```prisma: schema.prisma
model ContactForm {
  id        String   @id @default(cuid())  // 主キー、デフォルト値
  userName  String
  mail      String   @unique               // 重複不可
  createdAt DateTime @default(now())       // 作成日時に現在時刻を設定
}
```

---

## 3. テーブルの作成

以下のコマンドを実行し、Prisma スキーマに書いたモデル定義を、実際のデータベース（SQLite など）に反映させます。

```bash
npx prisma migrate dev --schema=prisma/contact-form/schema.prisma
```

この操作により、`./migrations/(実行日時)_init` と `contact-form.db` ファイルが作成されます。

:::note info
開発環境では migrate dev を使い、本番環境では migrate deploy を使います。
また、今回のデモでは別途フォルダを作成して `schema.prisma` がプロジェクトに2つある状態なので、 `--schema` オプションをつけています。
デフォルトの `src/prisma/schema.prisma` の1つだけであれば、オプションは不要です。
:::

作成されたデータベースを GUI 上で確認するには、以下のコマンドを使用します。

```bash
npx prisma studio --schema=prisma/contact-form/schema.prisma
```

「http://localhost:5555/」で Prisma Studio が起動し、テーブルの内容を視覚的に確認できます。

---

## 4. PrismaClient の設定

TypeScript から Prisma を操作するために、`PrismaClient` を生成します。
ジェネレーターの `output` は `src/generated/prisma/client/contact-form` に設定します。

```prisma: schema.prisma
generator client {
  provider = "prisma-client-js"
  output   = "/src/generated/prisma/client/contact-form"
}
```

以下のコマンドで PrismaClient を生成します。

```bash
npx prisma generate --schema=prisma/contact-form/schema.prisma
```

---

## 5. Next.js の Server Actions で DB にアクセスする

まず、`src/lib/contact-form/prisma.ts` を作成します。
以下のコードは、Prisma の公式ドキュメント「[Prevent hot reloading from creating new instances of PrismaClient](https://www.prisma.io/docs/orm/prisma-client/setup-and-configuration/databases-connections#prevent-hot-reloading-from-creating-new-instances-of-prismaclient)」に掲載されている例を基にしています。

```ts: prisma.ts
// PrismaClient の型定義をインポート
import { PrismaClient } from "@/generated/prisma/client/contact-form";

// グローバルオブジェクトに prisma プロパティを追加
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };

// 既存インスタンスがあれば再利用し、なければ新規作成
export const prisma = globalForPrisma.prisma || new PrismaClient();

// 開発環境ではインスタンスをグローバルに保持
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
```

:::note warn
**注意:**  
PrismaClient は `generated/prisma/client/contact-form` に生成した型定義を参照させます。
Prisma v6.6.0 以降では、ESM 対応のジェネレーターが導入されており、`@prisma/client` から直接インポートすると `PrismaClient: any` となってしまう場合があります。必ず `output` に設定したパスからインポートしてください。
:::

次に、Server Actions で PrismaClient を使用してデータベースにアクセスするコードを実装します。

```ts: submitForm.ts
"use server";

import { redirect } from "next/navigation";
import { prisma } from "../contact-form/prisma";

export const submitForm = async (formData: FormData) => {
  const userName = formData.get("userName") as string;
  const mail = formData.get("mail") as string;

  // メールアドレスの存在確認
  const existRecord = await prisma.contactForm.findUnique({
    where: { mail },
  });

  if (existRecord) {
    return {
      success: false,
      error: {
        mail: ["このメールアドレスは既に使用されています"],
      },
    };
  }

  // データベースに登録
  await prisma.contactForm.create({
    data: { userName, mail },
  });

  // 完了ページにリダイレクト
  redirect("/complete");
};
```

メールアドレスの重複を確認し、既に存在していればエラーを返します。
存在しない場合は、フォームデータをデータベースに登録し、完了ページに遷移します。

---

## まとめ

本記事では、Prisma を用いて Next.js の Server Actions からデータベース操作を行う方法を紹介しました。

Prisma を導入することで、

- DB が設計通りになる（schema.prisma に書いた model の定義をもとに自動作成されるため）
- TypeScript で型付き DB アクセスができるようになる

といったメリットが得られ、型安全かつ効率的にデータベース管理が可能になることを確認できました。