Next.jsとPrismaで作る型安全なデータベース設計入門

〜App Router時代のスキーマ駆動開発〜

はじめに

Next.jsでデータベースを扱う場合、
SQLを直接書くよりもORM(Object Relational Mapper)を使うのが一般的です。
中でも Prisma は、型安全・補完・マイグレーション管理のすべてを高次元で両立したモダンORMです。

この記事では、Next.js × Prismaを使った型安全なデータベース設計と開発フローを紹介します。


環境構成

src/
  app/
    users/
      page.tsx
  lib/
    prisma.ts
  prisma/
    schema.prisma

Prisma CLIを使って初期化します。

pnpm add prisma @prisma/client
npx prisma init

これにより prisma/schema.prisma が作成され、DB接続設定も自動生成されます。


Prismaのスキーマ定義

Prismaでは、RDB(MySQL / PostgreSQL / SQLiteなど)のスキーマを
宣言的に定義し、prisma migrate コマンドでDBに反映します。

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

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

model User {
  id        String   @id @default(uuid())
  name      String
  email     String   @unique
  createdAt DateTime @default(now())
}

これで npx prisma migrate dev --name init を実行すると、
自動でテーブルが作成されます。


Prisma Clientの初期化

Next.jsでは、App Routerでサーバー側コードが頻繁に実行されるため、
Prisma Clientを使い回す設計が重要です。

// src/lib/prisma.ts
import { PrismaClient } from "@prisma/client";

const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };

export const prisma =
  globalForPrisma.prisma ||
  new PrismaClient({
    log: ["query", "error", "warn"],
  });

if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;

これにより、ホットリロード時の再生成によるエラーを防げます。


データの取得・登録を実装する

Next.js App Routerでは、Server Components内で直接DBアクセス可能です。

// src/app/users/page.tsx
import { prisma } from "@/lib/prisma";

export default async function UsersPage() {
  const users = await prisma.user.findMany({
    orderBy: { createdAt: "desc" },
  });

  return (
    <div className="p-6">
      <h1 className="text-2xl font-bold mb-4">ユーザー一覧</h1>
      <ul>
        {users.map((u) => (
          <li key={u.id}>
            {u.name}({u.email})
          </li>
        ))}
      </ul>
    </div>
  );
}

これだけで、DBからデータを型安全に取得できます。
users は自動的に User[] 型として補完されます。


データ作成用のServer Action

App Routerでは、Server Actionを使ってDB操作を行うのが自然です。

// src/server/actions/createUser.ts
"use server";

import { prisma } from "@/lib/prisma";
import { revalidatePath } from "next/cache";

export async function createUser(formData: FormData) {
  const name = formData.get("name")?.toString();
  const email = formData.get("email")?.toString();

  if (!name || !email) return { success: false };

  await prisma.user.create({
    data: { name, email },
  });

  revalidatePath("/users");
  return { success: true };
}

フォームからServer Actionを呼び出す

// src/app/users/new/page.tsx
import { createUser } from "@/server/actions/createUser";

export default function NewUserPage() {
  return (
    <form action={createUser} className="max-w-md mx-auto flex flex-col gap-4 mt-8">
      <label>
        名前
        <input name="name" className="border p-2 w-full" />
      </label>
      <label>
        メール
        <input name="email" className="border p-2 w-full" />
      </label>
      <button type="submit" className="bg-blue-600 text-white py-2 rounded">
        追加
      </button>
    </form>
  );
}

このフォームを送信すると、自動でcreateUser()がサーバーで実行され、
新しいユーザーがDBに登録されます。


Prismaの型安全性を活かす

Prismaは、スキーマから型定義を自動生成します。
つまり、DBの変更が即座にTypeScriptの型に反映されるため、
リファクタリング時の事故を防げます。

const users = await prisma.user.findMany();
// → users: User[]

また、selectinclude の型補完もサポートされています。


Prisma Studioでデータ確認

開発中は、Prisma公式のGUIツール「Prisma Studio」を使うと便利です。

npx prisma studio

ブラウザ上でDBを操作でき、開発中のデータ確認や手動編集が簡単になります。


まとめ

Next.js × Prismaを組み合わせることで、次のようなメリットがあります。

App Router時代のNext.jsでは、Prismaはもはや必須レベルのORMです。
堅牢かつ安全なDBアクセスを、型の力で支える設計をぜひ導入してみてください。