sequelizeのモデルをTypeScript化して、ルーティングで使用する方法

### 初めに
今回は、sequelizeで作成したモデルをTypescript化してルーティングから呼び出し、DBから値を取得する方法を記述します。

### 環境
■ Nodeのバージョン
v14.14.0
■ 使用OS
MacOS Big Sur：バージョン11.6
■ 総合開発環境
Visual Studio Code
■ Sequelizeのバージョン
v5.22.4
■ MySQLのバージョン
v5.6.50

### 使用するテーブル設計

```SQL
CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `pass` varchar(255) NOT NULL,
  `status` int(11) NOT NULL,
  `createdAt` datetime NOT NULL,
  `updatedAt` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
```

### tsconfig.jsonの変更
今回のモデルの定義では、クラスを使用するため「tsconfig.json」の「target」キーの値を「es6」に変更します。

```json
"target": "es6"
```

### モデル定義の全文(TypeScript)

```ts
import {
  Sequelize,
  Model,
  DataTypes,
  HasManyGetAssociationsMixin,
  HasManyAddAssociationMixin,
  HasManyHasAssociationMixin,
  Association,
  HasManyCountAssociationsMixin,
  HasManyCreateAssociationMixin,
} from "sequelize";

const sequelize = new Sequelize("mysql://root:asd123@localhost:3306/mydb");

class User extends Model
 {
  public id!: number; // Note that the `null assertion` `!` is required in strict mode.
  public name!: string;
  public pass!: string;
  public status!: number;

  public readonly createdAt!: Date;
  public readonly updatedAt!: Date;
}

User.init(
  {
    id: {
      type: DataTypes.INTEGER.UNSIGNED,
      autoIncrement: true,
      primaryKey: true,
    },
    name: {
      type: new DataTypes.STRING,
      allowNull: false,
    },
    pass: {
      type: new DataTypes.STRING,
      allowNull: false,
    },
    status: {
      type: new DataTypes.INTEGER,
      allowNull: false,
    },
    createdAt: {
      type: new DataTypes.DATE,
      allowNull: false,
    },
    updatedAt: {
      type: new DataTypes.DATE,
      allowNull: false,
    }
  },
  {
    tableName: "users",
    sequelize, 
  }
);
new User
function getUser() {
  const instance = User.findByPk(1);
  return instance
}

export { User, getUser }
 ```
次の章から上記コードに関して解説します。

### sequelizeのインポート

```ts
import {
  Sequelize,
  Model,
  DataTypes,
  HasManyGetAssociationsMixin,
  HasManyAddAssociationMixin,
  HasManyHasAssociationMixin,
  Association,
  HasManyCountAssociationsMixin,
  HasManyCreateAssociationMixin,
} from "sequelize";

const sequelize = new Sequelize("mysql://root:asd123@localhost:3306/mydb");
```

パッケージとしてインストールしている「sequelize」をインポートします。
インポートした波括弧の中にある「Sequelize」は、Visual Studio Codeでマウスをホバーすると

```ts
class Sequelize import Sequelize
```

上記のようなクラスであることが分かります。
次に、「Sequelize」クラスにnew演算子を使用して、オブジェクト型のインスタンスを作成します。
その際の中括弧には、自分が使用したいDBの接続先を記載します。
ここの記述は、どのモデルでも定義する定型文です。

### クラス宣言

次に、下記記述でUserクラスを宣言します。

```ts
class User extends Model
 {
  public id!: number; // Note that the `null assertion` `!` is required in strict mode.
  public name!: string;
  public pass!: string;
  public status!: number;

  public readonly createdAt!: Date;
  public readonly updatedAt!: Date;
}
```

最初の記述でインポートした「Model」を使用しています。
「Model」は、Visual Studio Codeでマウスをホバーすると

```ts
Modelclass Model<T = any, T2 = any>
import Model
```

上記のようなクラスであることが分かります。
extends キーワードを使用し、「Model」を親クラスとする「User」という子クラスを宣言します。
次に、Userクラス本体に、使用したいカラムと型を記載します。
カラムの横に記載しているエクスクラメーションマーク(!)は、Non-null assertion operator(非nullアサーション演算子)という演算子です。
Non-null assertion operatorは、この変数はundefinedやnullにならないという意味です。

### Userモデルの初期化

次に、Userモデルを初期化します。

```ts
User.init(
  {
    id: {
      type: DataTypes.INTEGER.UNSIGNED,
      autoIncrement: true,
      primaryKey: true,
    },
    name: {
      type: new DataTypes.STRING,
      allowNull: false,
    },
    pass: {
      type: new DataTypes.STRING,
      allowNull: false,
    },
    status: {
      type: new DataTypes.INTEGER,
      allowNull: false,
    },
    createdAt: {
      type: new DataTypes.DATE,
      allowNull: false,
    },
    updatedAt: {
      type: new DataTypes.DATE,
      allowNull: false,
    }
  },
  {
    tableName: "users",
    sequelize, 
  }
);
```

ここの記述は、Javascriptで記載されているSequelzieのモデルと一緒です。
使用するカラムの属性とオプションに関して記載しています。

### DBからデータを取得する

```ts
new User
function getUser() {
  const instance = User.findByPk(1);
  return instance
}

export { User, getUser }
```
モデルの定義の最後として、DBからデータを取得する関数を作成しています。

Userクラスは、一度new演算子を使用してインスタンスにします。
関数の処理で、idが「1」であるレコードを取得します。
また、戻り値を取得したレコードを含むオブジェクトとしています。
最後に、定義した関数をエクスポートします。

このエクスポートした関数をルーティングでインポートして呼び出します。

### ルーティングでインポートする

```ts
import {
  User,
  getUser,
} from "../models/user";
```

次に、先ほどエクスポートした関数をルーティングでインポートします。
これで、「getUser」関数をルーティングで使用するとDBから値を取得できます。
最後に、ルーティングでモデルから値を呼び出し、レンダリングするビューファイルに値を渡します。

```ts
router.get('/', (req:any, res:any, next:any) =>  {
  (async () => {
    var result = await getUser()
    console.log(result)
    res.render('index', { title: 'Express' ,name: result.dataValues.name});
  })();
});
```

awaitを使用しないと、非同期処理されて、DBから値を取得する前にビューファイルが表示されてしまいます。
変数resultは、下記の結果となっています。

```js
User {
  dataValues: {
    id: 1,
    name: 'テスト',
    pass: 'test',
    status: 1,
    createdAt: Invalid Date,
    updatedAt: Invalid Date
  },
  _previousDataValues: {
    id: 1,
    name: 'テスト',
    pass: 'test',
    status: 1,
    createdAt: Invalid Date,
    updatedAt: Invalid Date
  },
  _changed: {},
  _modelOptions: {
    timestamps: true,
    validate: {},
    freezeTableName: false,
    underscored: false,
    paranoid: false,
    rejectOnEmpty: false,
    whereCollection: { id: 1 },
    schema: null,
    schemaDelimiter: '',
    defaultScope: {},
    scopes: {},
    indexes: [],
    name: { plural: 'Users', singular: 'User' },
    omitNull: false,
    tableName: 'users',
    sequelize: Sequelize {
      options: [Object],
      config: [Object],
      dialect: [MysqlDialect],
      queryInterface: [QueryInterface],
      models: [Object],
      modelManager: [ModelManager],
      connectionManager: [ConnectionManager],
      importCache: {}
    },
    hooks: {}
  },
  _options: {
    isNewRecord: false,
    _schema: null,
    _schemaDelimiter: '',
    raw: true,
    attributes: [ 'id', 'name', 'pass', 'status', 'createdAt', 'updatedAt' ]
  },
  isNewRecord: false
}
```

「dataValues」に中にDBから取得した値が入っています。
最後に、取得した「name」カラムを下記記述でビューファイルに渡しています。


```ts
res.render('index', { title: 'Express' ,name: result.dataValues.name});
```

### まとめ
sequelizeのモデルをTypeScript化するには、Nodeのパッケージである「sequelize-typescript」を使用する方法もありますが、今回は、sequelizeのv5で提供されている既存の機能でTypeScript化させました。
sequelize-cliで作成されるモデルは、デフォルトでTypeScriptで作成されることはないので、変更するのに時間はかかりますが、その後の保守性は静的型付けの機能により上がる可能性があります。
以上です。
ありがとうございました。

### 参考文献
seeuqlize公式
https://sequelize.org/master/manual/typescript
javascriptのクラスに関して
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Classes
Non-null assertion operator(非nullアサーション演算子)に関して
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator