mcpField
P
prisma

prisma

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

prisma について

Prisma ORMを使う開発者向けのスキルです。クエリ、トランザクション、リポジトリ、エラー処理のパターンを扱い、クエリ最適化とN+1問題の防止に重点を置きます。

主な機能

  1. 01Prisma Clientのクエリパターン
  2. 02リポジトリによるデータアクセス整理
  3. 03トランザクションとネスト書き込み
  4. 04クエリ最適化とN+1防止
  5. 05エラー処理と集計

活用シーン

  1. 01複雑なクエリをリポジトリに整理
  2. 02複数のDB操作をトランザクションで実行
  3. 03取得フィールドを絞ってクエリを最適化
  4. 04Prismaのエラーやリレーションクエリを処理

インストール

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

$npx skills add https://github.com/blencorp/claude-code-kit --skill prisma

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

同梱ファイルskill-rules-fragment.json

詳細

Prisma ORM のパターン

目的

クエリの最適化、トランザクション処理、クリーンなデータアクセスのためのリポジトリパターンなど、Prisma ORM を効果的に使用するための包括的なパターンを紹介します。

このスキルを使用する場面

  • データベースクエリに Prisma Client を使用する場合
  • データアクセス用のリポジトリを作成する場合
  • トランザクションを使用する場合
  • クエリを最適化し、N+1 を防止する場合
  • Prisma のエラーを処理する場合

Prisma の基本的な使い方

基本的なクエリパターン
import { PrismaService } from '@project-lifecycle-portal/database';

// Always use PrismaService.main
if (!PrismaService.isAvailable) {
    throw new Error('Prisma client not initialized');
}

// Find one
const user = await PrismaService.main.user.findUnique({
    where: { id: userId },
});

// Find many with filters
const users = await PrismaService.main.user.findMany({
    where: { isActive: true },
    orderBy: { createdAt: 'desc' },
    take: 10,
});

// Create
const newUser = await PrismaService.main.user.create({
    data: {
        email: '[email protected]',
        name: 'John Doe',
    },
});

// Update
const updated = await PrismaService.main.user.update({
    where: { id: userId },
    data: { name: 'Jane Doe' },
});

// Delete
await PrismaService.main.user.delete({
    where: { id: userId },
});
複雑なフィルタリング
// Multiple conditions
const users = await PrismaService.main.user.findMany({
    where: {
        email: { contains: '@example.com' },
        isActive: true,
        createdAt: { gte: new Date('2024-01-01') },
    },
});

// AND/OR conditions
const posts = await PrismaService.main.post.findMany({
    where: {
        AND: [
            { published: true },
            { author: { isActive: true } },
        ],
        OR: [
            { title: { contains: 'prisma' } },
            { content: { contains: 'prisma' } },
        ],
    },
});

リポジトリパターン

リポジトリを使用する場面

✅ 次のような場合はリポジトリを使用します:

  • 結合や include を伴う複雑なクエリ
  • 複数の場所で使用するクエリ
  • テスト用にモックが必要な場合

❌ 次のような場合はリポジトリを使用しません:

  • 単純な一度限りのクエリ
  • プロトタイピング
リポジトリのテンプレート
import { PrismaService } from '@project-lifecycle-portal/database';
import type { User, Prisma } from '@prisma/client';

export class UserRepository {
    async findById(id: string): Promise<User | null> {
        return PrismaService.main.user.findUnique({
            where: { id },
            include: { profile: true },
        });
    }

    async findByEmail(email: string): Promise<User | null> {
        return PrismaService.main.user.findUnique({
            where: { email },
        });
    }

    async findActive(): Promise<User[]> {
        return PrismaService.main.user.findMany({
            where: { isActive: true },
            orderBy: { createdAt: 'desc' },
        });
    }

    async create(data: Prisma.UserCreateInput): Promise<User> {
        return PrismaService.main.user.create({ data });
    }

    async update(id: string, data: Prisma.UserUpdateInput): Promise<User> {
        return PrismaService.main.user.update({ where: { id }, data });
    }

    async delete(id: string): Promise<void> {
        await PrismaService.main.user.delete({ where: { id } });
    }
}
サービスでの使用
export class UserService {
    private userRepository: UserRepository;

    constructor() {
        this.userRepository = new UserRepository();
    }

    async getById(id: string): Promise<User> {
        const user = await this.userRepository.findById(id);
        if (!user) {
            throw new Error('User not found');
        }
        return user;
    }
}

トランザクションのパターン

シンプルなトランザクション
const result = await PrismaService.main.$transaction(async (tx) => {
    const user = await tx.user.create({
        data: { email: '[email protected]', name: 'John' },
    });

    const profile = await tx.userProfile.create({
        data: { userId: user.id, bio: 'Developer' },
    });

    return { user, profile };
});
インタラクティブトランザクション
const result = await PrismaService.main.$transaction(
    async (tx) => {
        const user = await tx.user.findUnique({ where: { id: userId } });
        if (!user) throw new Error('User not found');

        const updated = await tx.user.update({
            where: { id: userId },
            data: { lastLogin: new Date() },
        });

        await tx.auditLog.create({
            data: { userId, action: 'LOGIN', timestamp: new Date() },
        });

        return updated;
    },
    {
        maxWait: 5000,   // Wait max 5s to start
        timeout: 10000,  // Timeout after 10s
    }
);

クエリの最適化

select を使用してフィールドを制限する
// ❌ Fetches all fields
const users = await PrismaService.main.user.findMany();

// ✅ Only fetch needed fields
const users = await PrismaService.main.user.findMany({
    select: {
        id: true,
        email: true,
        name: true,
    },
});

// ✅ Select with relations
const users = await PrismaService.main.user.findMany({
    select: {
        id: true,
        email: true,
        profile: {
            select: { firstName: true, lastName: true },
        },
    },
});
include は慎重に使用する
// ❌ Excessive includes
const user = await PrismaService.main.user.findUnique({
    where: { id },
    include: {
        posts: { include: { comments: true } },
        workflows: { include: { steps: { include: { actions: true } } } },
    },
});

// ✅ Only include what you need
const user = await PrismaService.main.user.findUnique({
    where: { id },
    include: { profile: true },
});

N+1 クエリの防止

問題
// ❌ N+1 Query Problem
const users = await PrismaService.main.user.findMany(); // 1 query

for (const user of users) {
    // N additional queries
    const profile = await PrismaService.main.userProfile.findUnique({
        where: { userId: user.id },
    });
}
解決策 1:include を使用する
// ✅ Single query with include
const users = await PrismaService.main.user.findMany({
    include: { profile: true },
});

for (const user of users) {
    console.log(user.profile.bio);
}
解決策 2:バッチクエリ
// ✅ Batch query
const users = await PrismaService.main.user.findMany();
const userIds = users.map(u => u.id);

const profiles = await PrismaService.main.userProfile.findMany({
    where: { userId: { in: userIds } },
});

const profileMap = new Map(profiles.map(p => [p.userId, p]));

リレーション

1 対多
// Get user with posts
const user = await PrismaService.main.user.findUnique({
    where: { id: userId },
    include: {
        posts: {
            where: { published: true },
            orderBy: { createdAt: 'desc' },
            take: 10,
        },
    },
});
ネストされた書き込み
// Create user with profile
const user = await PrismaService.main.user.create({
    data: {
        email: '[email protected]',
        name: 'John Doe',
        profile: {
            create: {
                bio: 'Developer',
                avatar: 'avatar.jpg',
            },
        },
    },
    include: { profile: true },
});

// Update with nested updates
const user = await PrismaService.main.user.update({
    where: { id: userId },
    data: {
        name: 'Jane Doe',
        profile: {
            update: { bio: 'Senior developer' },
        },
    },
});

エラー処理

Prisma のエラーコード
import { Prisma } from '@prisma/client';

try {
    await PrismaService.main.user.create({
        data: { email: '[email protected]' },
    });
} catch (error) {
    if (error instanceof Prisma.PrismaClientKnownRequestError) {
        // P2002: Unique constraint violation
        if (error.code === 'P2002') {
            throw new ConflictError('Email already exists');
        }

        // P2003: Foreign key constraint failed
        if (error.code === 'P2003') {
            throw new ValidationError('Invalid reference');
        }

        // P2025: Record not found
        if (error.code === 'P2025') {
            throw new NotFoundError('Record not found');
        }
    }

    Sentry.captureException(error);
    throw error;
}
一般的なエラーコード
コード意味
P2002一意制約違反
P2003外部キー制約違反
P2025レコードが見つかりません
P2014リレーション違反

高度なパターン

集計
// Count
const count = await PrismaService.main.user.count({
    where: { isActive: true },
});

// Aggregate
const stats = await PrismaService.main.post.aggregate({
    _count: true,
    _avg: { views: true },
    _sum: { likes: true },
    where: { published: true },
});

// Group by
const postsByAuthor = await PrismaService.main.post.groupBy({
    by: ['authorId'],
    _count: { id: true },
});
Upsert
// Update if exists, create if not
const user = await PrismaService.main.user.upsert({
    where: { email: '[email protected]' },
    update: { lastLogin: new Date() },
    create: {
        email: '[email protected]',
        name: 'John Doe',
    },
});

TypeScript のパターン

import type { User, Prisma } from '@prisma/client';

// Create input type
const createUser = async (data: Prisma.UserCreateInput): Promise<User> => {
    return PrismaService.main.user.create({ data });
};

// Include type
type UserWithProfile = Prisma.UserGetPayload<{
    include: { profile: true };
}>;

const user: UserWithProfile = await PrismaService.main.user.findUnique({
    where: { id },
    include: { profile: true },
});

ベストプラクティス

  1. 常に PrismaService.main を使用する - PrismaClient のインスタンスを新たに作成しないでください
  2. 複雑なクエリにはリポジトリを使用する - データアクセスを整理された状態に保ちます
  3. 必要なフィールドのみを選択する - select を使ってパフォーマンスを向上させます
  4. N+1 クエリを防止する - include またはバッチクエリを使用します
  5. トランザクションを使用する - 複数ステップの操作で原子性を確保します
  6. エラーを処理する - Prisma の特定のエラーコードを確認します

関連スキル:

  • backend-dev-guidelines - バックエンドアーキテクチャの包括的なガイド
  • nodejs - Node.js の基本的なパターンと非同期処理
  • express - Express.js のルーティングとミドルウェア

コメント