レビュー待ち·難易度: 中級·更新: 2026-04-18

Prisma と Route Handler で CRUD API を実装する

Prisma Client の findMany / create / update / delete を Route Handler から呼び出して CRUD API を構築する例。スキーマ定義からマイグレーション・型安全なクエリまでを示す。

nextjscrudapiprisma

対応バージョン

nextjs 15react 19prisma 6

前提環境

Next.js Route Handler の基本と SQL の基礎を理解していること

概要

schema.prisma でモデルを定義し、prisma migrate でテーブルを作成、Route Handler から Prisma Client の findMany / create / update / delete を呼び出して型安全な CRUD API を構築する。nextjs-route-handler-crud のインメモリ実装を実際の DB 連携に置き換えるパターン。

インストール

npm install prisma @prisma/client
npx prisma init

実装

スキーマ定義

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

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

model User {
  id        Int      @id @default(autoincrement())
  name      String
  email     String   @unique
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}
# .env
DATABASE_URL="file:./dev.db"
# マイグレーション実行
npx prisma migrate dev --name init

Prisma Client シングルトン

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

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

export const prisma =
  globalForPrisma.prisma ?? new PrismaClient();

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

GET(一覧取得)・POST(作成)

// app/api/users/route.ts
import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";

export async function GET() {
  const users = await prisma.user.findMany({
    orderBy: { createdAt: "desc" },
  });
  return NextResponse.json(users);
}

export async function POST(request: Request) {
  let body: unknown;
  try {
    body = await request.json();
  } catch {
    return NextResponse.json({ error: "Invalid JSON" }, { status: 400 });
  }

  const { name, email } = body as { name?: string; email?: string };

  if (!name || !email) {
    return NextResponse.json({ error: "name and email are required" }, { status: 400 });
  }

  try {
    const user = await prisma.user.create({ data: { name, email } });
    return NextResponse.json(user, { status: 201 });
  } catch {
    return NextResponse.json({ error: "Email already exists" }, { status: 409 });
  }
}

GET(単件取得)・PATCH(更新)・DELETE(削除)

// app/api/users/[id]/route.ts
import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";

type Params = { params: Promise<{ id: string }> };

export async function GET(_request: Request, { params }: Params) {
  const { id } = await params;
  const user = await prisma.user.findUnique({ where: { id: Number(id) } });

  if (!user) {
    return NextResponse.json({ error: "User not found" }, { status: 404 });
  }
  return NextResponse.json(user);
}

export async function PATCH(request: Request, { params }: Params) {
  const { id } = await params;
  const body = await request.json() as { name?: string; email?: string };

  try {
    const user = await prisma.user.update({
      where: { id: Number(id) },
      data: body,
    });
    return NextResponse.json(user);
  } catch {
    return NextResponse.json({ error: "User not found" }, { status: 404 });
  }
}

export async function DELETE(_request: Request, { params }: Params) {
  const { id } = await params;

  try {
    await prisma.user.delete({ where: { id: Number(id) } });
    return new Response(null, { status: 204 });
  } catch {
    return NextResponse.json({ error: "User not found" }, { status: 404 });
  }
}

ポイント

  • lib/prisma.ts でシングルトンパターンを使う。globalThis に保存して next dev のホットリロード時に接続が重複しないようにする
  • prisma.user.create() の返り値は User 型(schema.prisma から自動生成)で型安全。idcreatedAt の型を手動管理しなくてよい
  • @unique フィールドへの重複は Prisma が PrismaClientKnownRequestError(code P2002)をスローする。catch で補足して 409 を返す
  • nextjs-route-handler-crud との使い分け: インメモリで CRUD の構造を理解する場合は nextjs-route-handler-crud、実際の DB(SQLite / PostgreSQL 等)と接続する場合はこのパターン
  • Next.js 15 では paramsPromise になるため await params が必要

注意点

nextjs-route-handler-crud はインメモリデータを使ったルートハンドラの CRUD パターン。これは Prisma を使った実際の DB 連携(schema.prisma 定義 → migrate → Client クエリ)。

関連サンプル