概要
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から自動生成)で型安全。idやcreatedAtの型を手動管理しなくてよい@uniqueフィールドへの重複は Prisma がPrismaClientKnownRequestError(codeP2002)をスローする。catchで補足して 409 を返すnextjs-route-handler-crudとの使い分け: インメモリで CRUD の構造を理解する場合はnextjs-route-handler-crud、実際の DB(SQLite / PostgreSQL 等)と接続する場合はこのパターン- Next.js 15 では
paramsがPromiseになるためawait paramsが必要