mcpField
P
prisma-orm

prisma-orm

github.com/bobmatnycデータ・データベース78341 回インストール15 週間前に更新
リポジトリ

prisma-orm について

prisma-ormは、TypeScript向けデータベースツールキットのリファレンススキルです。スキーマ設計、型安全なデータアクセス、マイグレーションに加え、クエリ、リレーション、トランザクション、Prisma Studioやアプリ連携を扱います。

主な機能

  1. 01スキーマファーストのデータベース設計
  2. 02型安全なPrisma Clientの生成
  3. 03CRUD、リレーション、高度なクエリ
  4. 04開発・本番環境のマイグレーション
  5. 05トランザクションとクエリ最適化
  6. 06Prisma Studioでのビジュアルデータ閲覧

活用シーン

  1. 01TypeScriptアプリのテーブルとリレーション設計
  2. 02アプリコード向け型安全なデータアクセスの生成
  3. 03開発・本番環境でのスキーマ変更管理
  4. 04Prisma Studioでのレコード閲覧、絞り込み、編集

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/bobmatnyc/claude-mpm-skills --skill prisma-orm

インストール後、Claude Code で/prisma-ormを入力して読み込まれたか確認します。

同梱ファイルmetadata.json

詳細

Prisma ORM - 型安全なデータベースツールキット

スキーマファースト開発、自動生成される型安全なクライアント、強力なマイグレーションシステムを備えた、TypeScript 向けの最新データベースツールキットです。

クイックリファレンス

インストール
npm install prisma @prisma/client
npx prisma init
基本的なワークフロー
# 1. Define schema
# Edit prisma/schema.prisma

# 2. Create migration
npx prisma migrate dev --name init

# 3. Generate client
npx prisma generate

# 4. Open Studio
npx prisma studio
コアスキーマパターン
// 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?
  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

  @@index([authorId])
}
型安全な CRUD
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

// Create
const user = await prisma.user.create({
  data: {
    email: '[email protected]',
    name: 'Alice',
    posts: {
      create: { title: 'First Post', content: 'Hello World' }
    }
  },
  include: { posts: true }
});

// Read with filters
const users = await prisma.user.findMany({
  where: { email: { contains: '@example.com' } },
  include: { posts: { where: { published: true } } },
  orderBy: { createdAt: 'desc' },
  take: 10
});

// Update
await prisma.user.update({
  where: { id: userId },
  data: { name: 'Bob' }
});

// Delete
await prisma.user.delete({ where: { id: userId } });

スキーマ設計パターン

フィールドの型と属性
model Product {
  id          Int      @id @default(autoincrement())
  sku         String   @unique
  name        String
  description String?  // Optional field
  price       Decimal  @db.Decimal(10, 2)
  inStock     Boolean  @default(true)
  quantity    Int      @default(0)
  tags        String[] // Array field (PostgreSQL)
  metadata    Json?    // JSON field
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt

  @@index([sku])
  @@index([name, inStock])
}
リレーション

一対多:

model User {
  id    String @id @default(cuid())
  posts Post[]
}

model Post {
  id       String @id @default(cuid())
  author   User   @relation(fields: [authorId], references: [id])
  authorId String

  @@index([authorId])
}

多対多:

model Post {
  id         String     @id @default(cuid())
  categories Category[] @relation("PostCategories")
}

model Category {
  id    String @id @default(cuid())
  name  String @unique
  posts Post[] @relation("PostCategories")
}

一対一:

model User {
  id      String   @id @default(cuid())
  profile Profile?
}

model Profile {
  id     String @id @default(cuid())
  bio    String
  user   User   @relation(fields: [userId], references: [id])
  userId String @unique
}

自己リレーション:

model User {
  id        String @id @default(cuid())
  following User[] @relation("UserFollows")
  followers User[] @relation("UserFollows")
}

クライアント操作

ネストされた書き込み
// Create with nested relations
const user = await prisma.user.create({
  data: {
    email: '[email protected]',
    profile: {
      create: { bio: 'Software Engineer' }
    },
    posts: {
      create: [
        { title: 'Post 1', content: 'Content 1' },
        { title: 'Post 2', content: 'Content 2' }
      ]
    }
  }
});

// Update with nested operations
await prisma.user.update({
  where: { id: userId },
  data: {
    posts: {
      create: { title: 'New Post' },
      update: {
        where: { id: postId },
        data: { published: true }
      },
      delete: { id: oldPostId }
    }
  }
});
トランザクション

逐次(インタラクティブ):

await prisma.$transaction(async (tx) => {
  const user = await tx.user.create({
    data: { email: '[email protected]' }
  });

  await tx.post.create({
    data: { title: 'Post', authorId: user.id }
  });

  // Rollback if error thrown
  if (someCondition) {
    throw new Error('Rollback transaction');
  }
});

バッチ(並列):

const [deletedPosts, updatedUser] = await prisma.$transaction([
  prisma.post.deleteMany({ where: { published: false } }),
  prisma.user.update({
    where: { id: userId },
    data: { name: 'Updated' }
  })
]);
高度なクエリ

集計:

const result = await prisma.post.aggregate({
  _count: { id: true },
  _avg: { views: true },
  _sum: { likes: true },
  _max: { createdAt: true },
  where: { published: true }
});

const grouped = await prisma.post.groupBy({
  by: ['authorId'],
  _count: { id: true },
  _avg: { views: true },
  having: { views: { _avg: { gt: 100 } } }
});

生の SQL:

// Raw query
const users = await prisma.$queryRaw<User[]>`
  SELECT * FROM "User" WHERE email LIKE ${`%${search}%`}
`;

// Execute
await prisma.$executeRaw`
  UPDATE "Post" SET views = views + 1 WHERE id = ${postId}
`;

マイグレーション

開発ワークフロー
# Create and apply migration
npx prisma migrate dev --name add_user_role

# Reset database (WARNING: deletes all data)
npx prisma migrate reset

# View migration status
npx prisma migrate status
本番環境へのデプロイ
# Apply pending migrations
npx prisma migrate deploy

# Generate client (in CI/CD)
npx prisma generate
スキーマのプロトタイピング
# Push schema without migrations (dev only)
npx prisma db push

# Pull schema from existing database
npx prisma db pull

統合パターン

Next.js App Router
// lib/prisma.ts
import { PrismaClient } from '@prisma/client';

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

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

if (process.env.NODE_ENV !== 'production') {
  globalForPrisma.prisma = prisma;
}

サーバーコンポーネント:

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

export default async function UsersPage() {
  const users = await prisma.user.findMany({
    include: { posts: { take: 5 } }
  });

  return (
    <ul>
      {users.map(u => (
        <li key={u.id}>{u.name} - {u.posts.length} posts</li>
      ))}
    </ul>
  );
}

サーバーアクション:

// app/actions.ts
'use server';

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

export async function createPost(formData: FormData) {
  const title = formData.get('title') as string;
  const authorId = formData.get('authorId') as string;

  await prisma.post.create({
    data: { title, authorId }
  });

  revalidatePath('/posts');
}
Node.js ミドルウェア
import { PrismaClient } from '@prisma/client';
import express from 'express';

const app = express();
const prisma = new PrismaClient();

app.get('/users/:id', async (req, res) => {
  const user = await prisma.user.findUnique({
    where: { id: req.params.id },
    include: { posts: true }
  });

  if (!user) return res.status(404).json({ error: 'Not found' });
  res.json(user);
});

app.listen(3000);

パフォーマンスの最適化

クエリの最適化
// ❌ N+1 queries
const users = await prisma.user.findMany();
for (const user of users) {
  const posts = await prisma.post.findMany({
    where: { authorId: user.id }
  });
}

// ✅ Single query with include
const users = await prisma.user.findMany({
  include: { posts: true }
});

// ✅ Select specific fields
const users = await prisma.user.findMany({
  select: { id: true, email: true, posts: { select: { title: true } } }
});
ページネーション
// Cursor-based (recommended for large datasets)
const posts = await prisma.post.findMany({
  take: 10,
  cursor: lastPostId ? { id: lastPostId } : undefined,
  skip: lastPostId ? 1 : 0,
  orderBy: { createdAt: 'desc' }
});

// Offset-based (simple but slower)
const posts = await prisma.post.findMany({
  skip: (page - 1) * pageSize,
  take: pageSize
});
コネクションプーリング
// schema.prisma
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")

  // Connection pool settings
  directUrl = env("DIRECT_URL")

  // Serverless connection limit
  relationMode = "prisma" // For PlanetScale, Neon
}
# .env
DATABASE_URL="postgresql://user:pass@host:5432/db?connection_limit=10&pool_timeout=20"

Prisma Studio

# Launch visual database browser
npx prisma studio

機能:

  • データを視覚的に閲覧・編集
  • レコードの作成、読み取り、更新、削除
  • データのフィルタリングと検索
  • リレーションを視覚的に表示
  • localhost:5555 で実行

Prisma と Drizzle の比較

機能PrismaDrizzle
スキーマ定義独自 DSLTypeScript コード
型安全性生成された型推論された型
マイグレーション組み込み(migrate)drizzle-kit
クエリビルダー流暢な APISQL 風のビルダー
リレーション自動手動の結合
Studio組み込み GUIGUI なし
バンドルサイズ~300kB~50kB
生の SQLサポート第一級の機能
Edge Runtime制限あり完全サポート
学習曲線中程度急
最適な用途フルスタックアプリ、迅速な開発、チームエッジ関数、SQL の専門家、バンドルサイズを重視する場合

Prisma を選ぶ場合:

  • チームがスキーマファースト開発を好む
  • データベースの視覚的なツール(Studio)が必要
  • リレーションの自動処理が必要
  • フルスタックのモノリスを構築する
  • 迅速なプロトタイピングとマイグレーションを行う

Drizzle を選ぶ場合:

  • 最小限のバンドルサイズが必要(エッジ関数)
  • SQL 風の構文を好む
  • Edge Runtime にデプロイする(Cloudflare Workers)
  • SQL の生成を完全に制御したい
  • チームに豊富な SQL の専門知識がある

ベストプラクティス

  1. シングルトンパターン - PrismaClient インスタンスを再利用します(特に開発環境)
  2. コネクション管理 - サーバーレス向けにプールサイズを設定します
  3. 特定のフィールドを選択 - select を使ってペイロードサイズを減らします
  4. トランザクションを使用 - 原子性が必要な複数ステップの操作に使用します
  5. 戦略的にインデックスを作成 - 頻繁にクエリされるフィールドに @@index を追加します
  6. マイグレーションを厳格に管理 - デプロイ後にマイグレーションを編集してはいけません
  7. スキーマのバージョン管理 - 内容が分かるマイグレーション名を使用します
  8. ソフトデリート - ハード削除の代わりに deletedAt フィールドを追加します
  9. 保存前に検証 - Prisma の操作前に Zod スキーマを使用します
  10. クエリを監視 - ログ記録には prisma.$on('query') を使用します

よくある落とし穴

❌ 複数の PrismaClient インスタンスを作成する:

// WRONG - creates connection leak
function getUser() {
  const prisma = new PrismaClient(); // New instance every call
  return prisma.user.findMany();
}

// CORRECT - singleton pattern
const prisma = new PrismaClient();
function getUser() {
  return prisma.user.findMany();
}

❌ N+1 クエリ:

// WRONG - multiple queries
const users = await prisma.user.findMany();
for (const user of users) {
  user.posts = await prisma.post.findMany({ where: { authorId: user.id } });
}

// CORRECT - single query with include
const users = await prisma.user.findMany({ include: { posts: true } });

❌ 複数ステップの操作でトランザクションがない:

// WRONG - not atomic, can leave inconsistent state
await prisma.user.delete({ where: { id: userId } });
await prisma.post.deleteMany({ where: { authorId: userId } }); // May fail

// CORRECT - atomic transaction
await prisma.$transaction([
  prisma.post.deleteMany({ where: { authorId: userId } }),
  prisma.user.delete({ where: { id: userId } })
]);

注意すべき兆候

次の場合は立ち止まって再検討してください:

  • リクエストハンドラー内で新しい PrismaClient を作成している
  • 複数ステップの操作でトランザクションを使用していない
  • 外部キーや頻繁にクエリされるフィールドにインデックスがない
  • 大きなテーブルでページネーションなしに findMany を使用している
  • 特定のフィールドだけが必要なのにオブジェクト全体を取得している
  • 本番環境での接続エラーを処理していない
  • 本番環境で migrate dev を使用している(migrate deploy を使用してください)

他のスキルとの連携

  • typescript-core:Zod による検証、型安全性のパターン
  • nextjs-core:Server Actions、Server Components との統合
  • nextjs-v16:App Router のデータ取得、キャッシュ
  • database-migration:安全なスキーマ進化のパターン

リソース

関連スキル

Prisma を使用する際、次のスキルがワークフローを強化します。

  • drizzle: Prisma の軽量な代替となる Drizzle ORM
  • typescript: Prisma の生成型に関する TypeScript のベストプラクティス
  • nextjs: Next.js での Prisma:コネクションプーリング、エッジランタイムに関する考慮事項
  • test-driven-development: Prisma のモデル、マイグレーション、クエリのテスト

[これらのスキルをバンドルにデプロイすると、完全なドキュメントを利用できます]

コメント