Prisma + Docker + PostgreSQLを使った効率的なデータベース

## はじめに
Prisma、Docker、PostgreSQLを組み合わせた開発環境の構築方法と、効率的なデータベース開発のワークフローについてまとめました。

こちらの[サンプルコード](https://github.com/ssssswooooo/prisma-docker-postgres-starter.git)を使用しています。

## Prismaとは

Prismaは、TypeScript/JavaScript向けの次世代ORMです。型安全なデータベースアクセス、自動生成されるクエリビルダー、マイグレーション管理などの機能を提供し、データベース操作を簡素化します。

## 開発環境のセットアップ

本記事では、Docker、Docker Compose、Prisma、PostgreSQLを使った開発環境を前提としています。リポジトリのクローンから開発環境の起動までの手順は以下の通りです。

```bash
# リポジトリのクローン
git clone https://github.com/ssssswooooo/prisma-docker-postgres-starter.git
cd prisma-docker-postgres-starter

# Dockerコンテナのビルドと起動
docker compose up -d --build

# Prisma Clientの生成
docker compose exec app npm run prisma:generate

# データベースマイグレーションの実行
docker compose exec app npm run prisma:migrate-dev -- --name init

# シードデータの投入
docker compose exec app npm run prisma:seed
```

## プロジェクト構成

このスターターテンプレートは、以下のような構成になっています：

```
.
├── Dockerfile             # アプリケーションコンテナの定義
├── compose.yaml           # Dockerサービス設定
├── package.json           # Node.js設定・依存関係
├── tsconfig.json          # TypeScript設定
├── .dockerignore          # Dockerビルド除外ファイル
├── .gitignore             # Git除外ファイル
└── src/                   # ソースコード
    ├── index.ts           # アプリケーションエントリーポイント
    └── prisma/            # Prisma関連ファイル
        ├── schema.prisma  # データモデル定義
        └── seed.ts        # シードデータスクリプト
```

## package.jsonのスクリプト説明

プロジェクトのpackage.jsonには、開発に便利な様々なスクリプトが定義されています。これらを理解することで、効率的な開発が可能になります：

```json
"scripts": {
    "build": "tsc",                      // TypeScriptのコンパイル
    "start": "node dist/index.js",       // コンパイル済みアプリの実行
    "dev": "ts-node src/index.ts",       // 開発モードでアプリを実行
    
    // Prisma関連コマンド
    "prisma:migrate-dev": "prisma migrate dev --schema ./src/prisma/schema.prisma",           // 開発用マイグレーション作成と適用
    "prisma:migrate-create": "prisma migrate create --schema ./src/prisma/schema.prisma",     // マイグレーションファイルの作成のみ
    "prisma:migrate-deploy": "prisma migrate deploy --schema ./src/prisma/schema.prisma",     // 本番環境用マイグレーション適用
    "prisma:db-pull": "prisma db pull --schema ./src/prisma/schema.prisma",                   // 既存DBからスキーマを生成
    "prisma:db-push": "prisma db push --schema ./src/prisma/schema.prisma",                   // マイグレーションなしでスキーマを適用
    "prisma:generate": "prisma generate --schema ./src/prisma/schema.prisma",                 // Prisma Clientの生成
    "prisma:seed": "prisma db seed --schema ./src/prisma/schema.prisma",                      // シードデータの投入
    "prisma:studio": "prisma studio --schema ./src/prisma/schema.prisma --hostname 0.0.0.0",  // Prisma Studioの起動
    
    "seed": "ts-node src/prisma/seed.ts",                                    // シードスクリプトの直接実行
    "psql": "docker exec -it prisma_docker_starter_db psql -U user -d mydatabase"  // PostgreSQLへの直接アクセス
}
```

主要なスクリプトの用途：
- **prisma:generate**: スキーマから型付きのPrisma Clientを生成
- **prisma:migrate-dev**: 開発環境でのマイグレーション作成・適用
- **prisma:migrate-deploy**: 本番環境へのマイグレーション適用
- **prisma:seed**: テストデータの投入
- **prisma:studio**: データベース管理用GUIの起動
- **psql**: PostgreSQLに直接アクセスしてSQLを実行

## データモデルの設計と実装

Prismaの大きな特徴は、`schema.prisma`ファイルでデータモデルを宣言的に定義できることです。このプロジェクトでは、以下のようなシンプルなブログ系のデータモデルを定義しています：

```ts
model User {
  id        Int      @id @default(autoincrement())
  name      String
  email     String   @unique
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  posts     Post[]

  @@map("users")
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  published Boolean  @default(false)
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  author    User     @relation(fields: [authorId], references: [id])
  authorId  Int

  @@map("posts")
}
```

### 重要なポイント

- `@id` や `@default` などのアノテーションで、フィールドの制約や初期値を設定
- `@@map()` でテーブル名をカスタマイズ（慣例的に複数形・小文字を使用）
- `@relation` で外部キー制約を定義
- `createdAt`、`updatedAt` フィールドで自動的にタイムスタンプを管理

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

データモデルを変更する際は、マイグレーションを作成・実行する必要があります。

```bash
# 新しいマイグレーションを作成・適用
docker compose exec app npm run prisma:migrate-dev -- --name add_new_field

# 本番環境へのマイグレーション適用（CIパイプライン等で使用）
docker compose exec app npm run prisma:migrate-deploy
```

`prisma:migrate-dev`コマンドは以下の処理を行います：
1. 前回のマイグレーションからの変更を検出
2. マイグレーションファイルを生成（`prisma/migrations/[timestamp]_[name]`）
3. マイグレーションをデータベースに適用
4. Prisma Clientを自動的に再生成

マイグレーションは `src/prisma/migrations` ディレクトリに保存され、バージョン管理されます。これにより、チーム開発でのスキーマ変更の追跡や、本番環境への安全なデプロイが可能になります。

## シードデータの作成

テスト・開発用のサンプルデータを簡単に投入できるのもPrismaの利点です。`src/prisma/seed.ts` ファイルに、以下のようなシードスクリプトを記述しています：

```typescript
async function main() {
  try {
    // ユーザーデータのクリーンアップ（既存の場合）
    await prisma.post.deleteMany({});
    await prisma.user.deleteMany({});

    console.log('既存データをクリーンアップしました');

    // ユーザーの作成
    const user1 = await prisma.user.create({
      data: {
        name: '山田太郎',
        email: 'taro@example.com',
      },
    });

    // 投稿の作成
    const post1 = await prisma.post.create({
      data: {
        title: 'Prismaの使い方',
        content: 'Prismaは次世代のORMで、TypeScriptとの相性が抜群です。',
        published: true,
        authorId: user1.id,
      },
    });

    // ... その他のシードデータ
  } catch (error) {
    console.error('シードの実行中にエラーが発生しました:', error);
    process.exit(1);
  } finally {
    // Prismaクライアントの切断
    await prisma.$disconnect();
  }
}
```

シードデータを実行するには、`prisma:seed`スクリプトを使用します：

```bash
docker compose exec app npm run prisma:seed
```

このコマンドは`package.json`の`prisma.seed`設定に基づいて、TypeScriptのシードスクリプトを実行します。

## アプリケーションでのPrisma Client利用

実際のアプリケーションコードでは、Prisma Clientを使って型安全なデータベース操作を行います：

```typescript
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

// 基本的なCRUD操作
async function main() {
  // 作成（Create）
  const newUser = await prisma.user.create({
    data: {
      name: '新規ユーザー',
      email: 'new@example.com',
    },
  });

  // リレーションを含む作成
  const userWithPost = await prisma.user.create({
    data: {
      name: 'ブロガー',
      email: 'blogger@example.com',
      posts: {
        create: {
          title: '最初の投稿',
          content: '内容...',
          published: true,
        },
      },
    },
  });

  // 読み取り（Read）
  const allUsers = await prisma.user.findMany();
  
  // リレーションを含む読み取り
  const usersWithPosts = await prisma.user.findMany({
    include: {
      posts: true,
    },
  });
  
  // 条件付き検索
  const publishedPosts = await prisma.post.findMany({
    where: {
      published: true,
    },
  });
  
  // 更新（Update）
  const updatedUser = await prisma.user.update({
    where: { id: 1 },
    data: { name: '更新後の名前' },
  });
  
  // 削除（Delete）
  const deletedPost = await prisma.post.delete({
    where: { id: 1 },
  });
}
```

Prisma Clientでは、オートコンプリートと型チェックによって、実行前にエラーを防止できます。また、リレーションの扱いも直感的です。

## 開発ワークフロー

効率的な開発のために、以下のワークフローを推奨します：

1. **モデル定義**: `schema.prisma` にデータモデルを定義/更新
2. **マイグレーション**: `prisma:migrate-dev` で変更をデータベースに反映
3. **Client生成**: マイグレーション時に自動で行われるが、必要に応じて `prisma:generate` を実行
4. **シードデータ**: 開発/テスト用のデータを `seed.ts` で定義し、`prisma:seed` で投入
5. **アプリ開発**: 生成されたPrisma Clientを使ってデータベース操作を実装
6. **モデル調整**: 必要に応じてモデルを調整し、新しいマイグレーションを作成

## データの可視化: Prisma Studio

Prisma Studioは、データベースの内容を視覚的に確認・編集できるGUIツールです。

```bash
docker compose exec app npm run prisma:studio
```

このコマンドは、Prisma Studioをコンテナ内で起動し、外部からアクセスできるようにします。

ブラウザで `http://localhost:5555` にアクセスすると、以下のことができます：
- テーブル内のデータ閲覧・編集
- リレーションの確認
- レコードの追加・削除

これは特にデータ構造の確認やテストデータの調整に便利です。

## トラブルシューティング

### データベースリセット

開発中にデータベースをリセットする必要がある場合は：

```bash
docker compose exec app npx prisma migrate reset --force --schema ./src/prisma/schema.prisma
```

このコマンドは以下の処理を行います：
1. データベースの全テーブルを削除
2. すべてのマイグレーションを初めから順に再適用
3. シードデータを投入

### PostgreSQLへの直接アクセス

低レベルなSQLクエリを実行したい場合は、`psql`スクリプトを使用します：

```bash
npm run psql

# PostgreSQLコンソール内で
SELECT * FROM users;
SELECT * FROM posts;
```

このコマンドは、Dockerコンテナ内のPostgreSQLに接続し、SQLコマンドを直接実行できるインタラクティブシェルを提供します。

## まとめ

Prisma + Docker + PostgreSQLの組み合わせは、TypeScriptプロジェクトにおけるデータベース開発を大幅に効率化します。型安全なデータベースアクセス、マイグレーション管理、リレーションの簡潔な表現など、多くのメリットがあります。

今回紹介したスターターテンプレートと各スクリプトの使い方を理解することで、効率的なデータベース開発を始めることができます。特に、package.jsonに定義されたスクリプトは、よく使われる操作を簡単に実行できるようにしており、開発効率を大きく向上させます。

コードの全体像は、[GitHub リポジトリ](https://github.com/ssssswooooo/prisma-docker-postgres-starter)で確認できます。

## 参考資料

- [Prisma 公式ドキュメント](https://www.prisma.io/docs/)
- [Docker Compose ドキュメント](https://docs.docker.com/compose/)
- [PostgreSQL 公式ドキュメント](https://www.postgresql.org/docs/)