ExpressでSequelizeを使う

# はじめに
ExpressでDBのSeederを使いたいと思って調べたところ、`Sequelize`というライブラリがヒットしました。

https://sequelize.org/

今回はその使い方を調べたので、記事にしたいと思います。

<!--
# Sequelizeとは
Node.js用のライブラリで、データベースのテーブルをJavaScriptのオブジェクトとして表現し、SQLクエリを直接書く代わりにJavaScriptを通じてデータベース操作を行うことができます。
-->

# 環境構築

### インストール
Sequelizeのライブラリをインストール
```
npm install sequelize
```
SequelizeをCLIで使うためのライブラリをインストール
```
npm install sequelize-cli --save-dev
```

:::note warn
MySQLを使用する場合下記も実行してください。
（ないと、migration実行時にエラーになります。）
```
npm install mysql2
```
:::

### 初期化
以下のコマンドで初期化します。
```
npx sequelize init
```

実行すると、以下のファイルとフォルダが作成されます。
- config/config.json
接続設定を記載するファイルです。
- models
データベースのモデル用のファイルを配置するフォルダです。
- migrations
マイグレーション用のファイルを配置するフォルダです。
- seeders
シーダー用のファイルを配置するフォルダです。

### 接続設定
config/config.jsonにDB接続用の設定を記載します。

:::note info
設定はdevelopmentなど環境ごとに設定できます。デフォルトで使用されるのはdevelopmentの設定となります。
:::

```json
{
  "development": {
    "username": "root",
    "password": null,
    "database": "database_development",
    "host": "127.0.0.1",
    "dialect": "mysql"
  },
  "test": {
    "username": "root",
    "password": null,
    "database": "database_test",
    "host": "127.0.0.1",
    "dialect": "mysql"
  },
  "production": {
    "username": "root",
    "password": null,
    "database": "database_production",
    "host": "127.0.0.1",
    "dialect": "mysql"
  }
}
```

# DBの初期化
DBにテーブルを定義し、データの挿入まで行う方法を説明します。
順番は以下になります。
- モデル作成
- マイグレーション実行
- シーダー作成
- シーダー実行

## モデル作成

`sequelize model:generate --name 名前 --attributes "属性"`でモデルを作成します。
モデルを作成後、マイグレーションのコマンドを実行すると、DBにモデルと同等のテーブルが作成されます。
なので、上記のコマンドは以下のように読み換えることができます。
`sequelize model:generate --name テーブル名 --attributes "列定義"`

:::note info
モデル名とテーブル名について
テーブル名の定義は複数形にする方もいると思いますが、複数形にするとモデルの名称も複数形になります。
回避方法があれば追記したいと思います。
どなたか情報ある方コメントしていただきたいです。
:::

モデルを作成するサンプルコマンドを下記に記載しておきます。

```
npx sequelize model:generate --underscored --name users --attributes "name:string, age:integer"
```

:::note info
自動生成される列について
以下の列は自動で生成されるので、`model:generate`で作成する列定義には含めない方が良いです。
- id
- created_at
- updated_at

含めてもエラーにはなりませんが、定義内容が上書きされるみたいです。
具体的に記載すると下記のようなモデルファイルが作成されます。
```js
'use strict';
/** @type {import('sequelize-cli').Migration} */
module.exports = {
  async up(queryInterface, Sequelize) {
    await queryInterface.createTable('users', {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      id: {
        type: Sequelize.INTEGER
      },
// 以下省略
```
idが二重で定義されて、下の方の設定で上書きされるみたいです。
:::

実行すると`models`にモデルのファイル、`migrations`にマイグレーション用のファイルが作成されます。

## マイグレーションを実行
[モデル作成](#モデル作成)で、作成したマイグレーションファイルを実行します。

```
npx sequelize db:migrate
```

ターミナルで、MySQLを確認すると下記のようにテーブルが作成されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475427/21adab58-9bb0-b904-47cd-da51b41f8eae.png)

## Seederを作成

以下のコマンドでSeederを作成します。

```
npx sequelize seed:generate --name sample-users
```

上記のコマンドで作成されたSeederファイルを編集し、どういうデータを作成するかを記載します。

```js
'use strict';

/** @type {import('sequelize-cli').Migration} */
module.exports = {
  async up (queryInterface, Sequelize) {
    await queryInterface.bulkInsert('users', [
      {
        name: "Alice",
        age: 16,
        created_at: new Date(),
        updated_at: new Date(),
      },
      {
        name: "Bob",
        age: 30,
        created_at: new Date(),
        updated_at: new Date(),
      },
      { 
        name: 'Charlie', 
        age: 35, 
        created_at: new Date(), 
        updated_at: new Date() 
      },
      { 
        name: 'Diana', 
        age: 28, 
        created_at: new Date(), 
        updated_at: new Date() 
      },
      { 
        name: 'Evan', 
        age: 22, 
        created_at: new Date(), 
        updated_at: new Date() 
      }
    ]);
  },

  async down (queryInterface, Sequelize) {
    await queryInterface.bulkDelete('users', null, {});
  }
};
```

## Seederを実行
以下のどちらかのコマンドで実行してください。
- Seederファイルを全部実行する
`npx sequelize db:seed:all`
- Seederファイルを個別に実行する
`npx sequelize db:seed --seed ファイル名`

以下のような形で挿入されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475427/67a6785f-2086-601d-5c30-6e202624debd.png)

# オプション
こちらの章では、外部キーの設定など詳細な設定方法を記載していきます。

## 外部キーの設定
外部キーの設定は、モデルのファイルとマイグレーションファイルに外部キーの設定を定義し、マイグレーションを行うことで設定できます。
DBの変更のみならマイグレーションファイルに設定だけで問題なさそうですが、モデルのファイルにも定義しておくとモデルを操作するときに便利になるそうです。

### モデルを作成
サンプルとして、[DBの初期化](#dbの初期化)の続きからusersのidを外部キーに持つ`posts`というテーブルを作っていきたいと思います
```
npx sequelize model:generate --underscored --name posts --attributes "user_id:integer body:text"
```

### modelsに外部キーを定義する
modelsのファイルに、`associate`というメソッドがあるのでそこで定義します。

```js:users.js
'use strict';
const {
  Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
  class users extends Model {
    /**
     * Helper method for defining associations.
     * This method is not a part of Sequelize lifecycle.
     * The `models/index` file will call this method automatically.
     */
    static associate(models) {
      // define association here
      // ここで定義する
      users.hasMany(models.posts, {foreignKey: 'user_id'});
    }
  }
  users.init({
    id: DataTypes.INTEGER,
    name: DataTypes.STRING,
    age: DataTypes.INTEGER
  }, {
    sequelize,
    modelName: 'users',
    underscored: true,
  });
  return users;
};
```

```js:posts.js
'use strict';
const {
  Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
  class posts extends Model {
    /**
     * Helper method for defining associations.
     * This method is not a part of Sequelize lifecycle.
     * The `models/index` file will call this method automatically.
     */
    static associate(models) {
      // define association here
      posts.belongsTo(models.users, { foreignKey: 'user_id'});
    }
  }
  posts.init({
    user_id: DataTypes.INTEGER,
    body: DataTypes.TEXT
  }, {
    sequelize,
    modelName: 'posts',
    underscored: true,
  });
  return posts;
};
```

### マイグレーションファイルに外部キーを定義
`migrations`の`xxxx-create-posts.js`の方に外部キーの定義を追加します。
```diff_javascript:xxxx-create-posts.js
'use strict';
/** @type {import('sequelize-cli').Migration} */
module.exports = {
  async up(queryInterface, Sequelize) {
    await queryInterface.createTable('posts', {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      user_id: {
        type: Sequelize.INTEGER,
+       references: {
+         model: 'users', // 'users'は関連するモデルのテーブル名
+         key: 'id', // 'id'は参照先のフィールド名
+       },
      },
      // 省略
  },
  async down(queryInterface, Sequelize) {
    await queryInterface.dropTable('posts');
  }
};
```

### 参照先のキーにINDEXを追加
参照先となるキー（サンプルで言うと`users`の`id`)にINDEXを追加します。
追加しないとエラーになります。
usersのマイグレーションファイルに下記を追加してください。

```diff_javascript:xxxx-create-users.js
'use strict';
/** @type {import('sequelize-cli').Migration} */
module.exports = {
  async up(queryInterface, Sequelize) {
    await queryInterface.createTable('users', {
      // 省略
      updated_at: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });
+   await queryInterface.addIndex('users', ['id'], {
+     unique: true,
+     name: 'id_index'
+   });
  },
  async down(queryInterface, Sequelize) {
    await queryInterface.dropTable('users');
  }
};
```
### マイグレーションを実行する

[DBの初期化](#dbの初期化)の続きから実行している場合、一旦マイグレーション実行前に戻します。
```
npx sequelize db:migrate:undo:all
```

その後、再度マイグレーションを実行します。

```
npx sequelize db:migrate
```

`posts`のテーブル定義をMySQLで確認すると、user_idにFOREIGN KEYの定義が追加されているはずです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475427/7047b17b-e019-3395-e00e-6e1e800c17b3.png)