Prismaスキーマに新しいモデルを追加するたびに、CRUD APIを手書きするのが苦痛だった。Claude Codeにスキーマを渡したら、Route HandlersからZodバリデーターまで一式生成してくれた——これが僕の開発フローを変えたきっかけだ。
この記事では、Prismaスキーマを起点にZodバリデーター・Next.js App Router Route Handlers・tRPCルーターを自動生成するワークフローを解説する。型安全を保ちながら実装速度を大幅に上げる方法が具体的なプロンプトとともにわかる。
PrismaスキーマからAPIを生成するワークフローの全体像は?
まず全体の流れを把握しておく。
prisma/schema.prisma
↓
Claude Code が読み込む
↓
┌──────────────────────────┐
│ Zodバリデーター生成 │ lib/validations/
│ Route Handlers生成 │ app/api/**
│ tRPCルーター生成 │ server/routers/
│ 認証ミドルウェア追加 │ middleware/
│ テスト生成 │ __tests__/
└──────────────────────────┘
ポイントは、Prismaスキーマを唯一の信頼できる情報源(Single Source of Truth)として扱うことだ。スキーマが変われば、Claude Codeに「このスキーマの変更に合わせてAPIを更新して」と指示するだけで連鎖的に更新できる。
前提となる技術スタックはこれだ。
- Next.js 15 (App Router)
- Prisma 6.x + PostgreSQL
- TypeScript 5.x
- Zod 3.x
- tRPC 11.x(tRPCを使わない場合は該当セクションをスキップ)
Prisma MCP Serverはどうセットアップするか?
Prisma公式が提供するMCPサーバーを使うと、Claude Codeがスキーマだけでなくマイグレーション履歴やデータベースの状態も把握した上でコードを生成できる。
インストールと設定は以下の手順で行う。
# Prisma MCP serverのインストール
npm install -D @prisma/mcp-server
~/.claude.json に追加する。
{
"mcpServers": {
"prisma": {
"command": "npx",
"args": [
"-y",
"@prisma/mcp-server",
"--schema", "./prisma/schema.prisma"
]
}
}
}
MCP Serverが使えない環境では、スキーマファイルを直接指定する方法でも十分動く。
# スキーマをClaude Codeに読み込ませる
claude "prisma/schema.prisma を読んで、スキーマの構造を把握してください"
サンプルとして使うスキーマを用意しておく。
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
email String @unique
name String?
role Role @default(USER)
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Post {
id String @id @default(cuid())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
enum Role {
USER
ADMIN
}
このスキーマを使って以降の手順を進める。
スキーマからZodバリデーターを自動生成するにはどうするか?
まずZodバリデーターを生成する。プロンプトはこれだけでいい。
prisma/schema.prisma のスキーマを読んで、以下のZodバリデーターを生成してください。
- User モデルの作成・更新・検索用バリデーター
- Post モデルの作成・更新・検索用バリデーター
- 出力先: lib/validations/ ディレクトリ
- ファイル名はモデル名をケバブケースにする(例: user.ts, post.ts)
- Prismaの型と整合性を取ること
- emailはZodのemail()バリデーションを使う
- Partialを使って更新用スキーマを派生させる
Claude Codeが生成するコードの例は以下のようになる。
// lib/validations/user.ts
import { z } from 'zod'
import { Role } from '@prisma/client'
export const createUserSchema = z.object({
email: z.string().email('有効なメールアドレスを入力してください'),
name: z.string().min(1).max(100).optional(),
role: z.nativeEnum(Role).default('USER'),
})
export const updateUserSchema = createUserSchema.partial()
export const userQuerySchema = z.object({
page: z.coerce.number().int().min(1).default(1),
limit: z.coerce.number().int().min(1).max(100).default(20),
role: z.nativeEnum(Role).optional(),
search: z.string().optional(),
})
export type CreateUserInput = z.infer<typeof createUserSchema>
export type UpdateUserInput = z.infer<typeof updateUserSchema>
export type UserQuery = z.infer<typeof userQuerySchema>
生成後は型の整合性を確認する。
npx tsc --noEmit
エラーが出たら、そのエラーメッセージをそのままClaude Codeに投げると修正してくれる。
Next.js App RouterのRoute HandlersをClaude Codeで自動生成するには?
Zodバリデーターが揃ったところで、Route Handlersを生成する。
lib/validations/user.ts と lib/validations/post.ts のバリデーターを使って、
以下のRoute Handlersを生成してください。
User API:
- GET /api/users — 一覧取得(ページネーション・検索対応)
- POST /api/users — 新規作成
- GET /api/users/[id] — 単一取得
- PATCH /api/users/[id] — 更新
- DELETE /api/users/[id] — 削除
Post API:
- 同様のCRUD
条件:
- Prisma Clientはlib/prisma.tsからインポート
- エラーはNextResponseで適切なHTTPステータスを返す
- バリデーションエラーは400、Not Foundは404を返す
- 各ファイルはApp Routerのディレクトリ構造に従う
生成されるコードの例を示す。
// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
import { createUserSchema, userQuerySchema } from '@/lib/validations/user'
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url)
const query = userQuerySchema.safeParse(Object.fromEntries(searchParams))
if (!query.success) {
return NextResponse.json(
{ error: 'Invalid query parameters', details: query.error.flatten() },
{ status: 400 }
)
}
const { page, limit, role, search } = query.data
const skip = (page - 1) * limit
const where = {
...(role && { role }),
...(search && {
OR: [
{ email: { contains: search, mode: 'insensitive' as const } },
{ name: { contains: search, mode: 'insensitive' as const } },
],
}),
}
const [users, total] = await Promise.all([
prisma.user.findMany({ where, skip, take: limit, orderBy: { createdAt: 'desc' } }),
prisma.user.count({ where }),
])
return NextResponse.json({
data: users,
pagination: { page, limit, total, totalPages: Math.ceil(total / limit) },
})
}
export async function POST(request: NextRequest) {
const body = await request.json()
const parsed = createUserSchema.safeParse(body)
if (!parsed.success) {
return NextResponse.json(
{ error: 'Validation failed', details: parsed.error.flatten() },
{ status: 400 }
)
}
const user = await prisma.user.create({ data: parsed.data })
return NextResponse.json({ data: user }, { status: 201 })
}
ページネーション、バリデーション、エラーハンドリングまで一式生成される。手書きとの違いを実感できるポイントだ。
tRPCルーターをPrismaスキーマから生成するには?
tRPCを使っているプロジェクトでは、Route Handlers代わりにtRPCルーターを生成できる。
lib/validations/ のZodスキーマを使って、
以下のtRPCルーターを生成してください。
- server/routers/user.ts — User の CRUD procedures
- server/routers/post.ts — Post の CRUD procedures
- server/routers/index.ts — appRouter としてまとめる
条件:
- tRPC v11 の書式に従う
- protectedProcedure と publicProcedure を使い分ける
(一覧取得はpublic、作成・更新・削除はprotected)
- input は既存のZodスキーマを再利用する
- エラーはTRPCErrorを使って適切なコードで返す
生成されるルーターの例は以下だ。
// server/routers/user.ts
import { z } from 'zod'
import { TRPCError } from '@trpc/server'
import { createTRPCRouter, publicProcedure, protectedProcedure } from '@/server/trpc'
import { prisma } from '@/lib/prisma'
import { createUserSchema, updateUserSchema, userQuerySchema } from '@/lib/validations/user'
export const userRouter = createTRPCRouter({
list: publicProcedure
.input(userQuerySchema)
.query(async ({ input }) => {
const { page, limit, role, search } = input
const skip = (page - 1) * limit
const where = {
...(role && { role }),
...(search && {
OR: [
{ email: { contains: search, mode: 'insensitive' as const } },
{ name: { contains: search, mode: 'insensitive' as const } },
],
}),
}
const [users, total] = await Promise.all([
prisma.user.findMany({ where, skip, take: limit }),
prisma.user.count({ where }),
])
return { users, total, totalPages: Math.ceil(total / limit) }
}),
create: protectedProcedure
.input(createUserSchema)
.mutation(async ({ input }) => {
const existing = await prisma.user.findUnique({ where: { email: input.email } })
if (existing) {
throw new TRPCError({ code: 'CONFLICT', message: 'このメールアドレスは既に使用されています' })
}
return prisma.user.create({ data: input })
}),
update: protectedProcedure
.input(z.object({ id: z.string(), data: updateUserSchema }))
.mutation(async ({ input, ctx }) => {
const user = await prisma.user.findUnique({ where: { id: input.id } })
if (!user) throw new TRPCError({ code: 'NOT_FOUND' })
return prisma.user.update({ where: { id: input.id }, data: input.data })
}),
delete: protectedProcedure
.input(z.object({ id: z.string() }))
.mutation(async ({ input }) => {
await prisma.user.delete({ where: { id: input.id } })
return { success: true }
}),
})
認証・認可のミドルウェアはClaude Codeにどう追加させるか?
生成したAPIに認証ガードを追加する。
生成されたRoute Handlersに認証・認可ミドルウェアを追加してください。
要件:
- NextAuth.js v5 のセッション確認
- ADMIN ロールが必要なエンドポイント: DELETE /api/users/[id]
- 認証必須のエンドポイント: POST, PATCH, DELETE 全般
- 認証エラーは401、認可エラーは403を返す
- 共通化できる部分はwithAuth / withAdminのラッパー関数にまとめる
// lib/auth-middleware.ts
import { auth } from '@/auth'
import { NextRequest, NextResponse } from 'next/server'
type Handler = (req: NextRequest, context: { params: Promise<Record<string, string>> }) => Promise<NextResponse>
export function withAuth(handler: Handler): Handler {
return async (req, context) => {
const session = await auth()
if (!session?.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
return handler(req, context)
}
}
export function withAdmin(handler: Handler): Handler {
return async (req, context) => {
const session = await auth()
if (!session?.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
if (session.user.role !== 'ADMIN') {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
}
return handler(req, context)
}
}
生成したコードのテストもClaude Codeで自動生成できるか?
テストもClaude Codeに生成させる。
app/api/users/route.ts のRoute Handlersに対するVitestのテストを書いてください。
条件:
- Prisma Clientはvitest-mock-extended でモックする
- 正常系: 一覧取得・作成・更新・削除
- 異常系: バリデーションエラー・Not Found・認証エラー
- テストファイルは __tests__/api/users.test.ts に配置
生成されるテストのスケルトン例は以下だ。
// __tests__/api/users.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { NextRequest } from 'next/server'
import { GET, POST } from '@/app/api/users/route'
import { prisma } from '@/lib/prisma'
vi.mock('@/lib/prisma', () => ({
prisma: {
user: {
findMany: vi.fn(),
count: vi.fn(),
create: vi.fn(),
},
},
}))
describe('GET /api/users', () => {
it('ユーザー一覧を返す', async () => {
vi.mocked(prisma.user.findMany).mockResolvedValue([
{ id: '1', email: 'test@example.com', name: 'Test', role: 'USER', createdAt: new Date(), updatedAt: new Date() },
])
vi.mocked(prisma.user.count).mockResolvedValue(1)
const request = new NextRequest('http://localhost/api/users')
const response = await GET(request)
const data = await response.json()
expect(response.status).toBe(200)
expect(data.data).toHaveLength(1)
expect(data.pagination.total).toBe(1)
})
})
Prismaスキーマを変更したときのAPI更新はどう進めるか?
Prismaスキーマを変更した時の更新手順を決めておくと、チームで使う際に一貫性が保てる。
以下のスキーマ変更をAPIに反映させてください:
変更内容:
- Post モデルに tags: String[] フィールドを追加
- User モデルに avatar: String? フィールドを追加
更新が必要なファイル:
- lib/validations/user.ts, post.ts
- app/api/users/route.ts, app/api/posts/route.ts
- 対応するテストファイル
既存のAPIの挙動は変えないこと(tagsはオプション)。
このプロンプト形式をチームで標準化しておくと、「スキーマを変えたらAPIも更新するのを忘れた」という事故がなくなる。
スキーマ変更後のチェックリストとしてこれを使おう。
prisma migrate devでDBを更新- Claude Codeに変更差分を伝えてAPI・バリデーター・テストを更新
npx tsc --noEmitで型エラーがないか確認npm testでテストがパスするか確認prisma generateで Prisma Clientを再生成
まとめ
PrismaスキーマをClaude Codeと組み合わせることで、API実装の大部分を自動化できる。
重要なポイントを整理する。
- Prisma MCPサーバーを使うとClaude Codeがスキーマを直接参照できる
- Zodバリデーターを先に生成して、Route HandlerとtRPCで共通利用する
- 認証ミドルウェアはラッパー関数でまとめ、Route Handlerに適用する
- スキーマ変更時のプロンプト形式を決めておくと更新漏れが防げる
手書きCRUDが1モデルあたり1〜2時間かかっていたとすると、このワークフローでは10〜15分で同等のコードが揃う。型安全性はむしろ上がる。スキーマ駆動の開発にClaude Codeを組み込んでみてほしい。