T-CREATOR

Prisma でシードデータを効率よく管理する方法

Prisma でシードデータを効率よく管理する方法

開発を始めたばかりの頃、データベースにテストデータを手動で入力していた経験はありませんか?「このユーザーは管理者で、この投稿は公開状態で...」と一つずつデータを作成する作業は、時間がかかるだけでなく、ミスも起きやすいものです。

Prisma を使ったシードデータ管理をマスターすれば、このような手作業から解放され、開発効率が劇的に向上します。本記事では、Prisma でシードデータを効率よく管理する方法を、実践的な例を交えながら詳しく解説していきます。

シードデータとは

シードデータとは、データベースに初期データとして投入するサンプルデータのことです。開発やテストの際に、実際のデータに近い環境を素早く構築できるため、現代の Web 開発では欠かせない要素となっています。

開発・テスト環境での必要性

開発環境では、アプリケーションの動作確認や新機能のテストを行うために、現実的なデータが必要です。例えば、EC サイトを開発している場合、商品データ、ユーザーデータ、注文データなどがなければ、実際のユーザー体験を確認することができません。

また、テスト環境では、様々なシナリオを想定したデータセットが必要になります。正常系のテスト、異常系のテスト、境界値のテストなど、それぞれに適したデータを用意する必要があります。

本番環境での注意点

本番環境でのシードデータ投入には、慎重なアプローチが必要です。誤って本番データを上書きしてしまうと、取り返しのつかない事態になりかねません。

typescript// 危険な例:本番環境で実行してしまうと...
const seedProductionData = async () => {
  await prisma.user.deleteMany(); // 既存データを削除
  await prisma.user.createMany({
    data: seedUsers, // 新しいデータで置き換え
  });
};

このようなコードは、開発環境でのみ実行するように制限する必要があります。

Prisma でのシードデータ作成の基本

Prisma でのシードデータ作成は、実はとてもシンプルです。基本的な手順を順番に確認していきましょう。

prisma​/​seed.tsファイルの作成

まず、プロジェクトのルートディレクトリにprisma​/​seed.tsファイルを作成します。このファイルが、シードデータの実行エントリーポイントとなります。

typescript// prisma/seed.ts
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

async function main() {
  console.log('シードデータの作成を開始します...');

  // ここにシードデータの作成処理を記述

  console.log('シードデータの作成が完了しました!');
}

main()
  .catch((e) => {
    console.error(
      'シードデータの作成中にエラーが発生しました:',
      e
    );
    process.exit(1);
  })
  .finally(async () => {
    await prisma.$disconnect();
  });

基本的なシードスクリプトの書き方

シードスクリプトでは、Prisma Client を使ってデータベースにデータを挿入します。基本的な書き方を確認してみましょう。

typescript// ユーザーデータの作成例
async function createUsers() {
  const users = [
    {
      email: 'admin@example.com',
      name: '管理者',
      role: 'ADMIN',
    },
    {
      email: 'user@example.com',
      name: '一般ユーザー',
      role: 'USER',
    },
  ];

  for (const userData of users) {
    const user = await prisma.user.create({
      data: userData,
    });
    console.log(`ユーザーを作成しました: ${user.name}`);
  }
}

シードデータの実行方法

シードデータを実行するには、package.jsonにスクリプトを追加します。

json{
  "scripts": {
    "seed": "ts-node prisma/seed.ts"
  }
}

実行時は以下のコマンドを使用します:

bashyarn seed

もしts-nodeがインストールされていない場合は、以下のエラーが発生します:

arduinoError: Cannot find module 'ts-node'
Require stack:
- /path/to/project/prisma/seed.ts

この場合は、以下のコマンドでts-nodeをインストールしてください:

bashyarn add -D ts-node

効率的なシードデータ管理のテクニック

基本的なシードデータ作成ができるようになったら、次は効率化のテクニックを学びましょう。これらのテクニックを活用することで、保守性が高く、再利用可能なシードデータを作成できます。

ファクトリーパターンの活用

ファクトリーパターンを使うことで、データの生成ロジックを分離し、再利用可能なコードを作成できます。

typescript// factories/userFactory.ts
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

export class UserFactory {
  static async create(overrides: Partial<any> = {}) {
    const defaultData = {
      email: `user${Date.now()}@example.com`,
      name: 'テストユーザー',
      role: 'USER',
    };

    return await prisma.user.create({
      data: { ...defaultData, ...overrides },
    });
  }

  static async createMany(
    count: number,
    overrides: Partial<any> = {}
  ) {
    const users = [];
    for (let i = 0; i < count; i++) {
      users.push(await this.create(overrides));
    }
    return users;
  }
}

このファクトリーを使うことで、以下のように簡単にユーザーデータを作成できます:

typescript// 管理者ユーザーの作成
const admin = await UserFactory.create({
  email: 'admin@example.com',
  name: '管理者',
  role: 'ADMIN',
});

// 複数の一般ユーザーの作成
const users = await UserFactory.createMany(10);

データの依存関係の管理

複数のテーブルにリレーションシップがある場合、データの作成順序が重要になります。

typescript// 依存関係を考慮したシードデータの作成
async function createDataWithRelations() {
  // 1. まずカテゴリを作成
  const category = await prisma.category.create({
    data: {
      name: '技術書',
      description: 'プログラミング関連の書籍',
    },
  });

  // 2. 次に商品を作成(カテゴリIDが必要)
  const product = await prisma.product.create({
    data: {
      name: 'Prisma完全ガイド',
      price: 3000,
      categoryId: category.id,
    },
  });

  // 3. 最後に在庫情報を作成(商品IDが必要)
  await prisma.inventory.create({
    data: {
      productId: product.id,
      quantity: 100,
    },
  });
}

依存関係を無視してデータを作成しようとすると、以下のようなエラーが発生します:

vbnetError: Foreign key constraint failed on the field: `categoryId`

環境別のシードデータ分離

開発環境、テスト環境、本番環境で異なるデータが必要な場合があります。環境変数を使って分離しましょう。

typescript// config/seedConfig.ts
export const seedConfig = {
  development: {
    userCount: 50,
    productCount: 100,
    includeTestData: true,
  },
  test: {
    userCount: 10,
    productCount: 20,
    includeTestData: true,
  },
  production: {
    userCount: 0,
    productCount: 0,
    includeTestData: false,
  },
};

export function getSeedConfig() {
  const env = process.env.NODE_ENV || 'development';
  return seedConfig[env] || seedConfig.development;
}

この設定を使って、環境に応じたデータを作成します:

typescript// 環境別のシードデータ作成
async function createEnvironmentSpecificData() {
  const config = getSeedConfig();

  if (config.includeTestData) {
    // テスト用データの作成
    await createTestUsers(config.userCount);
    await createTestProducts(config.productCount);
  }

  // 本番環境では管理者アカウントのみ作成
  if (process.env.NODE_ENV === 'production') {
    await createAdminUser();
  }
}

実践的なシードデータの例

実際のプロジェクトでよく使われるシードデータの例を見ていきましょう。これらの例を参考に、自分のプロジェクトに適したデータを作成できます。

ユーザーデータの作成

ユーザー管理システムでは、様々な役割や権限を持つユーザーデータが必要です。

typescript// ユーザーデータの作成例
async function createUserData() {
  // 管理者ユーザー
  const admin = await prisma.user.create({
    data: {
      email: 'admin@company.com',
      name: 'システム管理者',
      role: 'ADMIN',
      isActive: true,
      profile: {
        create: {
          bio: 'システム全体の管理を担当',
          avatar: 'https://example.com/admin-avatar.jpg',
        },
      },
    },
  });

  // 一般ユーザー(複数作成)
  const userData = [
    { email: 'user1@example.com', name: '田中太郎' },
    { email: 'user2@example.com', name: '佐藤花子' },
    { email: 'user3@example.com', name: '鈴木一郎' },
  ];

  for (const data of userData) {
    await prisma.user.create({
      data: {
        ...data,
        role: 'USER',
        isActive: true,
        profile: {
          create: {
            bio: '一般ユーザーです',
            avatar: null,
          },
        },
      },
    });
  }
}

関連データの一括作成

ブログシステムや EC サイトでは、複数のテーブルにまたがる関連データが必要になります。

typescript// ブログシステムのシードデータ例
async function createBlogData() {
  // カテゴリの作成
  const categories = await Promise.all([
    prisma.category.create({
      data: { name: '技術', slug: 'tech' },
    }),
    prisma.category.create({
      data: { name: 'ライフスタイル', slug: 'lifestyle' },
    }),
    prisma.category.create({
      data: { name: 'ビジネス', slug: 'business' },
    }),
  ]);

  // 各カテゴリに記事を作成
  for (const category of categories) {
    const posts = await Promise.all([
      prisma.post.create({
        data: {
          title: `${category.name}の記事1`,
          content: 'これはサンプル記事です。',
          published: true,
          categoryId: category.id,
          authorId: (await prisma.user.findFirst()).id,
        },
      }),
      prisma.post.create({
        data: {
          title: `${category.name}の記事2`,
          content: 'これもサンプル記事です。',
          published: false,
          categoryId: category.id,
          authorId: (await prisma.user.findFirst()).id,
        },
      }),
    ]);

    // 各記事にコメントを作成
    for (const post of posts) {
      await prisma.comment.create({
        data: {
          content: '素晴らしい記事ですね!',
          postId: post.id,
          authorId: (await prisma.user.findFirst()).id,
        },
      });
    }
  }
}

大量データの効率的な生成

パフォーマンステストや負荷テストのために、大量のデータが必要な場合があります。

typescript// 大量データの効率的な生成
async function createBulkData() {
  const batchSize = 1000; // 一度に処理する件数
  const totalUsers = 10000;

  console.log(`${totalUsers}件のユーザーデータを作成中...`);

  for (let i = 0; i < totalUsers; i += batchSize) {
    const userData = [];

    for (
      let j = 0;
      j < batchSize && i + j < totalUsers;
      j++
    ) {
      userData.push({
        email: `user${i + j}@example.com`,
        name: `ユーザー${i + j}`,
        role: 'USER',
        isActive: true,
      });
    }

    await prisma.user.createMany({
      data: userData,
      skipDuplicates: true, // 重複をスキップ
    });

    console.log(`${i + userData.length}件完了`);
  }
}

大量データを作成する際は、以下のエラーに注意が必要です:

javascriptError: Query engine exited with code: 1
Error: Database connection limit exceeded

このようなエラーが発生した場合は、バッチサイズを小さくするか、データベースの接続制限を調整する必要があります。

シードデータの運用ベストプラクティス

シードデータを効果的に活用するためには、適切な運用方法を知ることが重要です。チーム開発や本番環境での運用を考慮したベストプラクティスを紹介します。

バージョン管理での扱い方

シードデータは、アプリケーションのコードと同様にバージョン管理に含める必要があります。

bash# .gitignoreに含めないファイル
# prisma/seed.ts  ← これは含める
# prisma/seed-data/  ← これも含める
# prisma/seed-config.ts  ← これも含める

# .gitignoreに含めるファイル
prisma/seed-local.ts  # ローカル専用のシードファイル
prisma/seed-production.ts  # 本番環境専用(機密データ含む)

シードデータの変更履歴を追跡することで、データベーススキーマの変更と合わせて、適切なデータの更新を行うことができます。

チーム開発での共有方法

チーム開発では、全員が同じシードデータを使えるようにすることが重要です。

typescript// チーム共有用のシードスクリプト
async function createTeamSharedData() {
  // 開発チームのメンバーデータ
  const teamMembers = [
    {
      email: 'dev1@team.com',
      name: '開発者A',
      role: 'DEVELOPER',
    },
    {
      email: 'dev2@team.com',
      name: '開発者B',
      role: 'DEVELOPER',
    },
    {
      email: 'designer@team.com',
      name: 'デザイナー',
      role: 'DESIGNER',
    },
    {
      email: 'pm@team.com',
      name: 'プロジェクトマネージャー',
      role: 'PM',
    },
  ];

  for (const member of teamMembers) {
    await prisma.user.upsert({
      where: { email: member.email },
      update: member,
      create: member,
    });
  }
}

upsertを使うことで、既存のデータがある場合は更新し、ない場合は新規作成します。これにより、チームメンバーが同じシードスクリプトを実行しても、重複エラーが発生しません。

データの整合性を保つコツ

シードデータの整合性を保つためには、いくつかの重要なポイントがあります。

typescript// データの整合性を保つためのヘルパー関数
class SeedDataManager {
  private createdData: Map<string, any[]> = new Map();

  // 作成したデータを記録
  recordCreatedData(type: string, data: any) {
    if (!this.createdData.has(type)) {
      this.createdData.set(type, []);
    }
    this.createdData.get(type)!.push(data);
  }

  // 特定のタイプのデータを取得
  getCreatedData(type: string): any[] {
    return this.createdData.get(type) || [];
  }

  // データの検証
  async validateData() {
    const users = this.getCreatedData('user');
    const posts = this.getCreatedData('post');

    // ユーザーが存在しない投稿がないかチェック
    for (const post of posts) {
      const userExists = users.some(
        (user) => user.id === post.authorId
      );
      if (!userExists) {
        throw new Error(
          `投稿 ${post.id} の作成者 ${post.authorId} が存在しません`
        );
      }
    }
  }
}

このような検証機能を組み込むことで、データの整合性を自動的にチェックできます。

まとめ

Prisma でのシードデータ管理は、開発効率を劇的に向上させる強力なツールです。本記事で紹介したテクニックを活用することで、保守性が高く、再利用可能なシードデータを作成できるようになります。

特に重要なポイントは以下の通りです:

  • ファクトリーパターンを活用して、再利用可能なデータ生成ロジックを作成する
  • 依存関係を考慮して、適切な順序でデータを作成する
  • 環境別の設定を使って、開発・テスト・本番環境に適したデータを管理する
  • バージョン管理に含めて、チーム全体で共有する
  • データの整合性を保つための検証機能を組み込む

これらのベストプラクティスを実践することで、開発の初期段階から本格的な運用まで、一貫したデータ管理が可能になります。

シードデータの管理は、一見地味な作業に思えるかもしれませんが、プロジェクトの成功を左右する重要な要素です。適切に管理されたシードデータは、開発チーム全体の生産性を向上させ、品質の高いアプリケーション開発を支えてくれます。

今すぐにでも、自分のプロジェクトにこれらのテクニックを適用してみてください。きっと、開発体験が大きく変わることでしょう。

関連リンク

  • Prisma 公式ドキュメント - Seeding
  • Prisma Client API Reference
  • [Prisma 入門:インストールから初期セットアップまで完全ガイド](./__Prisma 入門:インストールから初期セットアップまで完全ガイド.md)
  • [Prisma スキーマ設計のコツと実践テクニック](./Prisma スキーマ設計のコツと実践テクニック.md)
  • [Prisma Client の使い方:基本クエリから応用まで](./Prisma Client の使い方:基本クエリから応用まで.md)