SequelizeモデルからER図を自動生成、自動更新する方法

## はじめに

データベース設計の変更を継続的に追跡し、ドキュメントを最新に保つことは重要です。この記事では、Sequelizeモデルから自動的にER図を生成し、コミット時に自動更新する方法を解説します。

この記事ではモノレポ構成のプロジェクトを例に説明しますが、単一リポジトリでも同様の方法で実装可能です。

## どのようなドキュメントが出力されるか？

![スクリーンショット 2024-12-30 12.40.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/518208/6d3c80f4-9c79-42ec-6ea9-9a1f983024a6.png)


## プロジェクト構成例

````
.
├── frontend/          # フロントエンドプロジェクト
└── backend/          # バックエンドプロジェクト
    ├── src/
    │   ├── models/   # Sequelizeモデル
    │   └── scripts/  # ER図生成スクリプト
    └── erd.svg       # 生成されるER図
````


## モデル定義の例

### データベース接続設定（models/index.ts）

````typescript:src/models/index.ts
import dotenv from 'dotenv';
import { Sequelize } from 'sequelize';

dotenv.config();

const databaseUrl = process.env.DATABASE_URL; // postgres://postgres:password@localhost:5432/board_game_prototype

if (!databaseUrl) {
  throw new Error('DATABASE_URL is not defined');
}

const sequelize = new Sequelize(databaseUrl, {
  dialect: 'postgres',
  protocol: 'postgres',
  logging: false,
});

export default sequelize;
````


### モデル定義例（models/User.ts）

````typescript:src/models/User.ts
import { DataTypes, Model } from 'sequelize';
import sequelize from './index';

class UserModel extends Model {
  public id!: string;
  public googleId!: string;
  public username!: string;
}

UserModel.init(
  {
    id: {
      type: DataTypes.UUID,
      defaultValue: DataTypes.UUIDV4,
      primaryKey: true,
    },
    googleId: {
      type: DataTypes.STRING,
      allowNull: false,
      unique: true,
    },
    username: {
      type: DataTypes.STRING,
      allowNull: false,
    },
  },
  {
    sequelize,
    modelName: 'User',
  }
);

export default UserModel;
````


## 実装手順

### 1. 必要なパッケージのインストール

````bash
npm install --save-dev sequelize-erd @types/node @types/sequelize @types/dotenv
````


### 2. ER図生成スクリプト（src/scripts/generateErd.ts）

````typescript:src/scripts/generateErd.ts
import fs from 'fs';
import path from 'path';
import sequelizeErd from 'sequelize-erd';
import sequelize from '../models';
import { writeFileSync } from 'fs';

const modelsDir = path.join(__dirname, '../models');

(async function () {
  // モデルファイルの自動読み込み
  fs.readdirSync(modelsDir)
    .filter((file) => {
      return file.endsWith('.ts') && file !== 'index.ts';
    })
    .forEach((file) => {
      const modelModule = require(path.join(modelsDir, file));
      const model = modelModule.default;
      if (model && typeof model.initModel === 'function') {
        model.initModel(sequelize);
      }
    });

  // ER図の生成設定
  const svg = await sequelizeErd({
    source: sequelize,
    arrowShapes: {
      BelongsToMany: ['crow', 'crow'],
      BelongsTo: ['inv', 'crow'],
      HasMany: ['crow', 'inv'],
      HasOne: ['dot', 'dot'],
    },
    engine: 'dot',
    arrowSize: 1.2,
    lineWidth: 1,
    color: 'blue',
  });
  writeFileSync('./erd.svg', svg);
  console.log('ERDが正常に生成されました！');
})();
````


### 3. package.jsonにスクリプトを追加

````json:package.json
{
  "scripts": {
    "generate-erd": "ts-node src/scripts/generateErd.ts"
  }
}
````


### 4. Git pre-commitフックの設定

````bash:.husky/pre-commit
#!/usr/bin/env bash

cd backend  # モノレポの場合。単一リポジトリの場合は不要

# ERDを生成
npm run generate-erd

cd ..  # モノレポの場合。単一リポジトリの場合は不要

# 生成されたER図をステージングに追加
if [ -f "backend/erd.svg" ]; then  # パスは適宜調整
  git add backend/erd.svg
  echo "✨ Added backend/erd.svg to staging"
fi

# 正常終了
exit 0
````


### 5. READMEへのER図の組み込み

````markdown:README.md
## ER図

![ER図](backend/erd.svg)
````


## 自動生成のメリット

1. **モデル定義との同期**
   - Sequelizeモデルの変更が即座にER図に反映
   - 型定義とER図の一貫性を保証

2. **開発効率の向上**
   - モデル定義の変更時に自動的にER図が更新
   - チーム全員が最新のDB構造を把握可能

3. **ドキュメントの保守性**
   - コミット時に自動更新されるため、常に最新状態を維持
   - READMEに埋め込まれたER図も自動的に更新

## まとめ

Sequelizeモデルを使用したER図の自動生成により：
- データベース設計の可視化が容易に
- ドキュメントの自動更新による保守性向上
- チーム開発での認識共有がスムーズに

が実現できます。この方法は、モノレポ構成でも単一リポジトリでも適用可能です。

## 参考資料
- [Sequelize Documentation](https://sequelize.org/)
- [sequelize-erd npm](https://www.npmjs.com/package/sequelize-erd)
- [Husky - Git hooks](https://typicode.github.io/husky/)