prisma について
Prisma ORMを使う開発者向けのスキルです。クエリ、トランザクション、リポジトリ、エラー処理のパターンを扱い、クエリ最適化とN+1問題の防止に重点を置きます。
主な機能
- 01Prisma Clientのクエリパターン
- 02リポジトリによるデータアクセス整理
- 03トランザクションとネスト書き込み
- 04クエリ最適化とN+1防止
- 05エラー処理と集計
活用シーン
- 01複雑なクエリをリポジトリに整理
- 02複数のDB操作をトランザクションで実行
- 03取得フィールドを絞ってクエリを最適化
- 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 },
});
ベストプラクティス
- 常に PrismaService.main を使用する - PrismaClient のインスタンスを新たに作成しないでください
- 複雑なクエリにはリポジトリを使用する - データアクセスを整理された状態に保ちます
- 必要なフィールドのみを選択する - select を使ってパフォーマンスを向上させます
- N+1 クエリを防止する - include またはバッチクエリを使用します
- トランザクションを使用する - 複数ステップの操作で原子性を確保します
- エラーを処理する - Prisma の特定のエラーコードを確認します
関連スキル:
- backend-dev-guidelines - バックエンドアーキテクチャの包括的なガイド
- nodejs - Node.js の基本的なパターンと非同期処理
- express - Express.js のルーティングとミドルウェア
コメント
Markdown 対応