Nodejs用のモデルライブラリを作ってみた【Express】

#リポジトリ
https://github.com/kbc18a11/oreoreExpress
ライブラリ本体は`oreoreExpress/database/AbstractModel.js`です。

#解説用の構成や注意点
- FW：Express
- ディレクトリ構成：WebStormの新規Expressプロジェクトの状態に`プロジェクト名/database`と`プロジェクト名/model`というディレクトリを生成
- 本記事はチーム開発のメンバーのマニュアルとしても書いていますので、ライブラリには直接関係ないことも書いています。

今回利用しているtestsテーブルは以下の構成になっています

| id | text | created_at | updated_at |
|:---|------|:----------:|:----------:|


#使い方
リポジトリのものを利用するのであれば、以下のコマンドを実行してください。
```
npm install mysql2
```

ライブラリだけを使用するのであれば、`mysql2`と`date-utlis`をインストールします
```
npm install mysql2
npm install date-utlis
```

そして以下のとおり、mysql2の設定ファイルを作ります。

~~~JavaScript:プロジェクト名/database/mysqlConnection
const mysql = require('mysql2');
//MySQLの接続設定
const connection = mysql.createConnection({
    host: 'localhost',
    user: '',
    password: '',
    database: ''
}).promise();

module.exports = connection;
~~~

下記の通り、作りたいモデルクラスに設定します。
また以下のメソッドをオーバーライド、オーバーロードします。
`abstractTABLE_NAME`
`abstractVALIDATIONRULES`
`update`

メソッド`abstractTABLE_NAME`は、連携しているテーブル名のです。
メソッド`abstractVALIDATIONRULES`は、バリエーションルールを記述する所です。
バリエーションのルールはvalidatorjsのものになります。
https://www.npmjs.com/package/validatorjs


~~~JavaScript:プロジェクト名/model/Tests
const AbstractModel = require('./AbstractModel');
const connection = require('../database/mysqlConnection');
require('date-utils');

class Tests extends AbstractModel {
    constructor() {
        super();
    }

    /**
     * テーブル名
     * @override
     * @returns {string}
     */
    static get abstractTABLE_NAME() {
        return 'tests';
    }

    /**
     * バリデーションルール
     * @override
     * @return {Object}
     */
    static get abstractVALIDATIONRULES() {
        return {
            get:{
                rule: {
                    id: 'required|integer'
                },
                errorMessage: {
                    required: '必須項目です。',
                    integer: '数値で入力してください'
                }
            },
            //POSTリクエスト用
            post: {
                rule: {
                    text: 'required'
                },
                errorMessage: {
                    required: '必須項目です。',
                }
            },
            //PUTリクエスト用
            put: {
                rule: {
                    id: 'required|integer',
                    text: 'required'
                },
                errorMessage: {
                    required: '必須項目です。',
                    integer: '数値で入力してください'
                }
            },
            //DELETEリクエスト用
            delete: {
                rule: {
                    id: 'required|integer'
                },
                errorMessage: {
                    required: '必須項目です。',
                    integer: '数値で入力してください'
                }
            }
        };
    }

    /**
     * UPDATE文の準備を行って、親クラスのupdate()に実行をさせる
     * @param {Object} insertParam
     */
    static async update(insertParam) {
        //UPDATE文
        const sql = `UPDATE ${this.abstractTABLE_NAME} SET text = ?,updated_at = ? WHERE id = ?`;

        //create_at用の日付時間取得
        insertParam.updated_at = new Date().toFormat('YYYY-MM-DD HH:MI:SS');

        //SQLの実行
        await super.update(insertParam, sql);
    }
}

module.exports = Tests;
~~~

#機能
基本的にライブラリの機能を使う場合は、Expressのルーティングファイルの無名関数に`async`を付与します。

~~~JavaScript:プロジェクト名/routes/ルーティングファイル
router.get('/tests', async (req, res, next) => {
~~~

そして、ルーティングファイルで以下のライブラリを取り組みます。

~~~JavaScript:プロジェクト名/routes/ルーティングファイル
const express = require('express');
const router = express.Router();
const validator = require('validatorjs');
const Tests = require('../model/Tests');
~~~

ライブラリでデータベースを操作するメソッドは、Promiseオブジェクトがかかわっているため、呼び出しの際には、`await`を付与します。

~~~JavaScript:プロジェクト名/routes/ルーティングファイル
//レコードをすべて取得
await Test.all()
//引数idのカラム取得
await Test.find(id)
//引数idの存在確認を行う
await Test.existId(id)
//引数paramの値で新規登録を行う
await Test.insert(param)
//引数paramの値で更新を行う
await Test.update(param)
//引数idのレコードを削除する
await Test.delete(id)
~~~

##all() レコードをすべて取得


~~~JavaScript:プロジェクト名/routes/test.js
/**
 * @GET
 * testsのレコードをすべて取得
 */
router.get('/tests', async (req, res, next) => {

    try {
        //レコードをすべて取得
        const allRows = await Tests.all();

        //レコードを返す
        return res.send(allRows);
    } catch (error) {
        //レコードの取得失敗時
        console.log(error);
        res.status(500);
        return res.send({'error': 'サーバー側でエラーが発生しました'});
    }

});
~~~

http://localhost:3000/tests に`GET`でアクセスします。

~~~Json:http://localhost:3000/tests
[
    {
        "id": 1,
        "text": "これはテストです",
        "created_at": "2020-06-18T16:50:45.000Z",
        "updated_at": null
    },
    {
        "id": 2,
        "text": "これはテストです",
        "created_at": "2020-06-18T16:50:50.000Z",
        "updated_at": null
    },
    {
        "id": 3,
        "text": "これはテストです",
        "created_at": "2020-06-18T16:50:51.000Z",
        "updated_at": null
    }
]
~~~

##find(id) 引数idのカラム取得
~~~JavaScript:プロジェクト名/routes/test.js
/**
 * @GET
 * 指定されたidのカラムを取得
 */
router.get('/tests/:id', async (req, res, next) => {
    //バリデーションの検証を受ける値
    const verificationValue = {
        id: req.params.id
    }
    //バリデーションの結果にエラーがあるかのチェック
    const validation = new validator(
        verificationValue,
        Tests.abstractVALIDATIONRULES.get.rule,
        Tests.abstractVALIDATIONRULES.get.errorMessage
    );
    if (validation.fails()) {
        //エラーを422で返す
        return res.status(422).send({errors: validation.errors.all()});
    }

    try {
        //レコードを取得
        const row = await Tests.find(verificationValue.id);
        //レコードを返す
        return res.send(row);
    } catch (error) {
        //レコードの取得失敗時
        console.log(error);
        res.status(500);
        return res.send({'error': 'サーバー側でエラーが発生しました'});
    }
})
~~~

http://localhost:3000/(testのidを指定) に`GET`でアクセスします。

~~~Json:http://localhost:3000/1
[
    {
        "id": 1,
        "text": "これはテストです",
        "created_at": "2020-06-18T16:50:45.000Z",
        "updated_at": null
    }
]
~~~

また、URIのidを指定する所が以下のとおり数値以外の場合は、エラーメッセージを返します。
http://localhost:3000/aaaa77

~~~Json:http://localhost:3000/1
{
    "errors": {
        "id": [
            "数値で入力してください"
        ]
    }
}
~~~

## insert() 引数paramの値で新規登録を行う

~~~JavaScript:プロジェクト名/routes/test.js
/**
 * @POST
 * testsに新しいレコードを挿入
 */
router.post('/test', async (req, res, next) => {
    //バリデーションの検証を受ける値
    const verificationValue = {
        text: req.query.text
    }
    //バリデーションの結果にエラーがあるかのチェック
    const validation = new validator(
        verificationValue,
        Tests.abstractVALIDATIONRULES.post.rule,
        Tests.abstractVALIDATIONRULES.post.errorMessage
    );
    if (validation.fails()) {
        //エラーを422で返す
        return res.status(422).send({errors: validation.errors.all()});
    }

    try {
        //レコードの挿入開始
        await Tests.insert({text: req.query.text});
        return res.send({'insertResult': true});
    } catch (error) {
        //レコードの挿入失敗時
        console.log(error);
        return res.status(500).send({'insertResult': false});
    }
});
~~~

http://localhost:3000/test?text=テストやりたい に`POST`でアクセスします。

~~~Json:http
[
    {
        "id": 1,
        "text": "これはテストです",
        "created_at": "2020-06-18T16:50:45.000Z",
        "updated_at": null
    }
]
~~~

リクエストのボディにtextがない場合は、エラーメッセージを返します。

~~~Json:http://localhost:3000/1
{
    "errors": {
        "text": [
            "必須項目です。"
        ]
    }
}
~~~

## update(param) 引数paramの値で更新を行う  existId(id) 引数idの存在確認を行う

~~~JavaScript:プロジェクト名/routes/test.js
/**
 * @PUT
 * レコードの更新
 */
router.put('/test/:id', async (req, res, next) => {
    //バリデーションの検証を受ける値
    const verificationValue = {
        id: req.params.id,
        text: req.query.text
    }
    //バリデーションの結果にエラーがあるかのチェック
    const validation = new validator(
        verificationValue,
        Tests.abstractVALIDATIONRULES.put.rule,
        Tests.abstractVALIDATIONRULES.put.errorMessage
    );
    if (validation.fails()) {
        //エラーを422で返す
        return res.status(422).send({errors: validation.errors.all()});
    }

    //idは存在しないか？
    if (!await Tests.existId(verificationValue.id)) {
        //エラーを422で返す
        return res.status(422).send({
            errors: {
                id: ['idが存在しません']
            }
        });
    }

    try {
        //レコードの更新開始
        await Tests.update(verificationValue);
        return res.send({'updateResult': true});
    } catch (error) {
        //レコードの更新失敗時
        console.log(error);
        return res.status(500).send({'updateResult': false});
    }

});
~~~

http://localhost:3000/test?text=テストやりたい に`PUT`でアクセスします。

~~~Json:http
{
    "updateResult": true
}
~~~

URIのidを指定する所が以下のとおり数値以外やリクエストのボディにtextがない場合は、エラーメッセージを返します。
`http://localhost:3000/test/aaa`

~~~Json:http://localhost:3000/ああ
{
    "errors": {
        "id": [
            "数値で入力してください"
        ],
        "text": [
            "必須項目です。"
        ]
    }
}
~~~

そして、`find(id)`の引数のidがレコードに存在しない場合、このようなエラーを返します

~~~Json:http://localhost:3000/ああ
{
    "errors": {
        "id": [
            "idが存在しません"
        ]
    }
}
~~~

## delete(id) 引数idのレコードを削除する

~~~JavaScript:プロジェクト名/routes/test.js
/**
 * @DELETE
 * レコードの削除
 */
router.delete('/test/:id', async (req, res, next) => {
    //バリデーションの検証を受ける値
    const verificationValue = {
        id: req.params.id,
    }
    //バリデーションの結果にエラーがあるかのチェック
    const validation = new validator(
        verificationValue,
        Tests.abstractVALIDATIONRULES.delete.rule,
        Tests.abstractVALIDATIONRULES.delete.errorMessage
    );
    if (validation.fails()) {
        //エラーを422で返す
        return res.status(422).send({errors: validation.errors.all()});
    }

    //idは存在しないか？
    if (!await Tests.existId(verificationValue.id)) {
        //エラーを422で返す
        return res.status(422).send({
            errors: {
                id: ['idが存在しません']
            }
        });
    }

    try {
        //レコードの削除開始
        await Tests.delete(verificationValue.id);
        return res.send({'deleteResult': true});
    } catch (error) {
        //レコードの削除失敗時
        console.log(error);
        return res.status(500).send({'deleteResult': false});
    }


})
~~~

http://localhost:3000/(testのidを指定) に`DELETE`でアクセスします。

~~~Json:http://localhost:3000/1
[
    {
        "id": 1,
        "text": "これはテストです",
        "created_at": "2020-06-18T16:50:45.000Z",
        "updated_at": null
    }
]
~~~

URIのidを指定する所が以下のとおり数値以外の場合は、エラーメッセージを返します。
http://localhost:3000/aaaa77

~~~Json:http://localhost:3000/1
{
    "errors": {
        "id": [
            "数値で入力してください"
        ]
    }
}
~~~

そして、`find(id)`の引数のidがレコードに存在しない場合、このようなエラーを返します

~~~Json:http://localhost:3000/ああ
{
    "errors": {
        "id": [
            "idが存在しません"
        ]
    }
}
~~~