Prismaチートシート（自分用）

## 設定と初期化

### インストール
```bash
# Prismaと関連パッケージのインストール
npm install prisma @prisma/client
# または
yarn add prisma @prisma/client
```

### 初期化
```bash
# Prismaの初期化
npx prisma init
```

### 環境設定 (.env ファイル)
```
DATABASE_URL="postgresql://user:password@localhost:5432/mydatabase"
```

## スキーマ定義 (schema.prisma)

### 基本設定
```js
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql" // または "mysql", "sqlite", "sqlserver"
  url      = env("DATABASE_URL")
}
```

### モデル定義
```js
model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  posts     Post[]
  profile   Profile?

  @@map("users") // テーブル名をカスタマイズ
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id])
  authorId  Int
  tags      Tag[]
  
  @@index([authorId]) // インデックスの作成
}

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

model Tag {
  id    Int    @id @default(autoincrement())
  name  String @unique
  posts Post[]
}
```

### リレーションタイプ
```js
// 1対1
model User {
  id      Int     @id
  profile Profile?
}

model Profile {
  id     Int  @id
  user   User @relation(fields: [userId], references: [id])
  userId Int  @unique // @uniqueが必要
}

// 1対多
model User {
  id    Int    @id
  posts Post[]
}

model Post {
  id       Int  @id
  author   User @relation(fields: [authorId], references: [id])
  authorId Int
}

// 多対多
model Post {
  id   Int   @id
  tags Tag[]
}

model Tag {
  id    Int    @id
  posts Post[]
}
```

## マイグレーション管理

### マイグレーション作成
```bash
# 開発環境用
npx prisma migrate dev --name init

# 名前付きマイグレーション
npx prisma migrate dev --name add_user_profile
```

### マイグレーション適用（本番環境用）
```bash
npx prisma migrate deploy
```

### スキーマ更新（既存DBからスキーマを生成）
```bash
npx prisma db pull
```

### DBスキーマをPrismaスキーマに合わせる（開発用）
```bash
npx prisma db push
```

### マイグレーションのリセット
```bash
npx prisma migrate reset
```

## Prisma Client

### クライアント生成
```bash
npx prisma generate
```

### 基本的な使用法
```typescript
import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

async function main() {
  // ここでPrisma操作を実行
}

main()
  .catch(e => {
    console.error(e)
    process.exit(1)
  })
  .finally(async () => {
    await prisma.$disconnect()
  })
```

## CRUD操作

### 作成（Create）
```typescript
// 単一レコード作成
const user = await prisma.user.create({
  data: {
    name: '山田太郎',
    email: 'taro@example.com',
  },
})

// リレーションを含む作成
const userWithPosts = await prisma.user.create({
  data: {
    name: '佐藤花子',
    email: 'hanako@example.com',
    posts: {
      create: [
        { title: '初めての投稿', content: '内容...' },
        { title: '2つ目の投稿', content: '内容...' }
      ],
    },
  },
  include: {
    posts: true, // 関連ポストも返す
  },
})

// 複数レコード作成
const users = await prisma.user.createMany({
  data: [
    { name: '鈴木一郎', email: 'ichiro@example.com' },
    { name: '田中二郎', email: 'jiro@example.com' },
  ],
})
```

### 読み取り（Read）
```typescript
// 全件取得
const allUsers = await prisma.user.findMany()

// 条件付き取得
const user = await prisma.user.findUnique({
  where: { id: 1 }
})

const userByEmail = await prisma.user.findUnique({
  where: { email: 'taro@example.com' }
})

// 複合条件
const filteredPosts = await prisma.post.findMany({
  where: {
    AND: [
      { published: true },
      { title: { contains: 'Prisma' } }
    ],
  },
})

// リレーションを含む取得
const usersWithPosts = await prisma.user.findMany({
  include: {
    posts: true,
  },
})

// リレーションの条件付き取得
const usersWithPublishedPosts = await prisma.user.findMany({
  include: {
    posts: {
      where: { published: true }
    }
  }
})

// ネストされたリレーション
const userWithPostsAndProfile = await prisma.user.findUnique({
  where: { id: 1 },
  include: {
    posts: {
      include: {
        tags: true
      }
    },
    profile: true
  }
})

// 選択フィールド
const userNameOnly = await prisma.user.findUnique({
  where: { id: 1 },
  select: { name: true }
})

// ページネーション
const paginatedUsers = await prisma.user.findMany({
  take: 10,  // 10件取得
  skip: 20,  // 20件スキップ
  orderBy: { createdAt: 'desc' }
})

// 集計
const userCount = await prisma.user.count()
const publishedPostCount = await prisma.post.count({
  where: { published: true }
})
```

### 更新（Update）
```typescript
// 単一レコード更新
const updatedUser = await prisma.user.update({
  where: { id: 1 },
  data: { name: '新しい名前' },
})

// 複数レコード更新
const publishAll = await prisma.post.updateMany({
  where: { authorId: 1 },
  data: { published: true },
})

// リレーションを含む更新
const userWithNewPost = await prisma.user.update({
  where: { id: 1 },
  data: {
    posts: {
      create: { title: '新しい投稿' }
    }
  },
  include: { posts: true }
})

// upsert（存在すれば更新、なければ作成）
const upsertUser = await prisma.user.upsert({
  where: { email: 'taro@example.com' },
  update: { name: '山田太郎（更新）' },
  create: { email: 'taro@example.com', name: '山田太郎（新規）' },
})
```

### 削除（Delete）
```typescript
// 単一レコード削除
const deletedUser = await prisma.user.delete({
  where: { id: 1 },
})

// 複数レコード削除
const deleteDrafts = await prisma.post.deleteMany({
  where: { published: false },
})

// カスケード削除（リレーションを含む）
// スキーマで onDelete: Cascade を設定している場合
const deleteUserAndPosts = await prisma.user.delete({
  where: { id: 1 },
})
```

## 高度なクエリ

### トランザクション
```typescript
const [newUser, newPost] = await prisma.$transaction([
  prisma.user.create({ 
    data: { name: '新ユーザー', email: 'new@example.com' } 
  }),
  prisma.post.create({ 
    data: { title: '新投稿', authorId: 1 } 
  })
])

// インタラクティブなトランザクション
const result = await prisma.$transaction(async (tx) => {
  const user = await tx.user.findUnique({ where: { id: 1 } })
  if (!user) throw new Error('ユーザーが見つかりません')
  
  const post = await tx.post.create({
    data: { title: 'タイトル', authorId: user.id }
  })
  
  return { user, post }
})
```

### Raw SQL
```typescript
// 直接SQLを実行
const result = await prisma.$queryRaw`SELECT * FROM users WHERE id = ${userId}`

// 型付きクエリ
const result = await prisma.$queryRaw<User[]>`SELECT * FROM users`
```

## シード

### シードスクリプト (prisma/seed.ts)
```typescript
import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

async function main() {
  // データクリーンアップ
  await prisma.post.deleteMany()
  await prisma.user.deleteMany()
  
  // データ作成
  const user = await prisma.user.create({
    data: {
      name: 'テストユーザー',
      email: 'test@example.com',
      posts: {
        create: [
          { title: 'テスト投稿1', published: true },
          { title: 'テスト投稿2', published: false },
        ]
      }
    }
  })
  
  console.log('シード完了', user)
}

main()
  .catch(e => console.error(e))
  .finally(async () => await prisma.$disconnect())
```

### package.json 設定
```json
{
  "prisma": {
    "seed": "ts-node prisma/seed.ts"
  }
}
```

### シード実行
```bash
npx prisma db seed
```

## Prisma Studio（GUI）
```bash
npx prisma studio
```

## ミドルウェア
```typescript
const prisma = new PrismaClient()

// ロギングミドルウェア
prisma.$use(async (params, next) => {
  console.log(`操作: ${params.model}.${params.action}`)
  const before = Date.now()
  const result = await next(params)
  const after = Date.now()
  console.log(`時間: ${after - before}ms`)
  return result
})
```

## デバッグとログ
```typescript
const prisma = new PrismaClient({
  log: ['query', 'info', 'warn', 'error'],
})

// または特定のレベルのみ
const prisma = new PrismaClient({
  log: [
    { emit: 'stdout', level: 'query' },
    { emit: 'event', level: 'error' },
  ],
})

// イベントリスナー
prisma.$on('query', (e) => {
  console.log('クエリ: ' + e.query)
  console.log('パラメータ: ' + e.params)
  console.log('実行時間: ' + e.duration + 'ms')
})
```

# script
### 環境構築
```bash
# Dockerコンテナのビルドと起動
docker compose up -d --build

# Prisma Clientの生成
docker compose exec app npm run prisma:generate
prisma generate --schema ./src/prisma/schema.prisma

# 初期マイグレーションの実行
docker compose exec app npm run prisma:migrate-dev -- --name init
prisma migrate dev --schema ./src/prisma/schema.prisma

# シードデータの投入
docker compose exec app npm run prisma:seed
prisma db seed --schema ./src/prisma/schema.prisma
```

###  スキーマ変更関連
```bash
# 新しいマイグレーションの作成と実行（スキーマ変更後）
docker compose exec app npm run prisma:migrate-dev -- --name [変更内容を表す名前]
prisma migrate dev --schema ./src/prisma/schema.prisma

# 例: テーブル追加
docker compose exec app npm run prisma:migrate-dev -- --name add_categories_table

# 例: カラム追加
docker compose exec app npm run prisma:migrate-dev -- --name add_image_url_to_posts
```

### シードデータ関連
```bash
# シードデータの投入
docker compose exec app npm run prisma:seed
prisma db seed --schema ./src/prisma/schema.prisma

# データベースをリセット（マイグレーションを再適用し、シードデータを投入）
docker compose exec app npx prisma migrate reset --force --schema ./src/prisma/schema.prisma
```

### 確認・デバッグ
```bash
# データの確認（Prisma Studio起動）
docker compose exec app npm run prisma:studio
prisma studio --schema ./src/prisma/schema.prisma --hostname 0.0.0.0

# PostgreSQLへのアクセス
npm run psql
docker exec -it prisma_docker_starter_db psql -U user -d mydatabase

# データベース構造の確認（PostgreSQLコンソール内）
\dt
\d sample_table
```