６. Koa2を使ってWebページを作成しよう ~validatonで検証~

## 記事一覧
* [第一回 Koa2の環境を構築する](https://qiita.com/MEDICE-NOTE/items/0c0d18cf01bc4ae50c05)
* [第二回 Hello World!!を表示する](https://qiita.com/MEDICE-NOTE/items/6260a7a58bc0c3303de0)
* [第三回 koa-routerでGETパラメーターを取得する](https://qiita.com/MEDICE-NOTE/items/bb363d091bff59345da7)
* [第四回 koa-bodyでPOSTパラメーターを取得する](https://qiita.com/MEDICE-NOTE/items/b630748133c954a50ec1)
* [第五回 アカウントを登録してログインを検証する](https://qiita.com/MEDICE-NOTE/items/1232550deba4d955a928)

## 概要
今回は、入力値のバリデーションの実装を行います。
また、前回の続きとして進めて行くので、上手くいかない場合は以前の記事をご覧ください。

## バリデーション
バリデーションは値のフォーマットが正しい形であるかの確認をする処理で、今回は入力必須や文字数制限、メールアドレス形式であるかなどの観点で値の検証を行っていきます。
一例として、ユーザーから値を受け取って処理を行うプログラムでは、ユーザーが必ずしも正しいフォーマットでデータを入力するとは限らないため、フォーマットの確認が必要になります。

＊バリデーションにはこちらのパッケージを利用します。
https://github.com/skaterdav85/validatorjs

以下のコマンドをコンソールで実行してください。
`npm install validatorjs`

## DBの作成
phpMyAdminで以下のSQLを実行してください。
別のツールでDBを操作できる場合は、そちらでも問題ありません。

```sql
create table koa2_test.validate_user
(
   user_id   int auto_increment
       primary key,
   user_name varchar(20)  not null,
   password  varchar(60) not null,
   mail      varchar(255) not null
);
```

**DBの各列で指定されている条件をまとめると、以下のようになります。**

* user_id: 主キー制約。自動採番。
* user_name: 最大20文字。NOTNULL。
* password: 最大60文字。NOTNULL。
* mail: 最大255文字。NOTNULL。

また、以下の条件も考慮します。

* user_idは自動採番で、バリデーションの対象とする必要はない
* mailはメールアドレスの形式である事も検証する
* パスワードはハッシュ化前文字列の最大を128文字とする。

よって、必要となる検証項目は以下のようになります。

* user_name: 20文字以下であること。値が空でない事。
* password: 128文字以下であること。値が空でない事。
* mail: 255文字以下であること。値が空でない事。メールアドレスの形式である事。

## validatorで入力値の検証
`/router/validator-signup.js`を作成し、以下の記述を追加してください。

```validator-signup.js
const Router = require('koa-router');
const router = new Router();
const connection = require('../app/db');
const bcrypt = require('bcrypt');
const validator = require('validatorjs');

router.get('/validator-signup', async (ctx) => {
    await ctx.render('validator-signup');
});

router.post('/validator-signup', async (ctx) => {
    // POSTパラメータを取得
    let userName = ctx.request.body['name'];
    let password = ctx.request.body['password'];
    let mail = ctx.request.body['mail'];

    // 入力値の検証
    let validationResult = await validation([userName, password, mail]);

    // 入力値拒否時、登録処理を行わずにエラーメッセージを表示する
    if (validationResult.result === false) {
        await ctx.render('validator-signup', validationResult);
    } else {
        // パスワードをハッシュ化
        const salt = bcrypt.genSaltSync();
        const hashPassword = bcrypt.hashSync(password, salt);

        // DBにアカウント情報を登録
        let signupSQL = 'INSERT INTO validate_user (user_name, password, mail) VALUES (?, ?, ?)';
        await connection.query(signupSQL, [userName, hashPassword, mail]);

        await ctx.render('validator-signup');
    }
});

module.exports = router;
```

大部分は前回の記事で作成したアカウント登録処理と同じものですが、入力値の検証を行う処理を追加しています。
次に、入力値の検証を行うためのvalidation関数を作成します。

## validation関数の作成
`/router/validator-signup.js`の最後に以下の記述を追加してください。

```validator-signup.js
async function validation(data) {
    // ユーザーの入力値をvalidatorjsへの入力形式である連想配列に変換。
    // ここで指定しているkey値がrulesやerrorMessageの条件部分でも利用される。
    let requests = {
        userName: data[0],
        password: data[1],
        mail: data[2],
    }

    // それぞれの値に対して掛ける制約を指定している。
    // required: 文字列が0文字でないことを確認
    // max: 最大文字数以下であることを確認
    // email: メールの形式であることを確認
    let rules = {
        userName: 'required|max:20',
        password: 'required|max:128',
        mail: 'required|max:255|email',
    };

    // '条件.検証項目'の形で、検証項目が条件を満たしていない場合の
    // エラーメッセージを指定している。
    // ここで指定していないエラーについては、
    // デフォルトで設定されている英語のメッセージが返される。
    let errorMessage = {
        'required.userName': 'USER NAMEは必須項目です',
        'required.password': 'PASSWORDは必須項目です',
        'required.mail': 'MAILは必須項目です',
        'max.userName': 'USER NAMEは20文字以下で入力して下さい',
        'max.password': 'PASSWORDは128文字以下で入力して下さい',
        'max.mail': 'MAILは255文字以下で入力して下さい',
        'email.mail': 'MAILはメールアドレスの形式で入力して下さい'
    }

    // validator(検証する値, 検証する条件, 拒否時のメッセージ) の形で、
    // validatorのインスタンスを生成。
    let signupValidator = new validator(requests, rules, errorMessage);
    
    let result = {
        error: {},
        result: false
    };

    // 値の検証を実行して、成功時とエラー時で処理を振り分ける。
    // checkAsync() の第一引数は成功時の関数を、第二引数は失敗時の関数を指定する。
    await signupValidator.checkAsync(() => {
        // Success時の処理
        result.result = true;
    }, () => {
        // Error時の処理
        result.result = false;

        // errors.first(‘userName’)では、userNameにエラーが発生している場合、
        // 発生したエラーに応じて、validatorインスタンス生成時に
        // 第三引数で指定したメッセージを取得している。
        // 取得した値はresult変数に格納して、関数の最後でreturnしている。
        if (signupValidator.errors.first('userName')) {
            result.error.userName = signupValidator.errors.first('userName');
        }
        if (signupValidator.errors.first('password')) {
            result.error.password = signupValidator.errors.first('password');
        }
        if (signupValidator.errors.first('mail')) {
            result.error.mail = signupValidator.errors.first('mail');
        }
    })

    return result;
}
```

＊上記以外の制約についてはこちらのページを参照。
https://github.com/skaterdav85/validatorjs

## 入力フォームを作成
`/view/validator-signup.ejs`を作成し、以下の記述を追加してください。

```validator-signup.ejs
<h1>アカウント登録</h1>
<% if(typeof error !== 'undefined'){ %>
    <% for(let key in error) { %>
    <p style="color: red"><%= error[key] %></p>
    <% } %>
<% } %>
<form action="validator-signup" method="post">
    USER NAME <input type="text" name="name"><br>
    PASSWORD <input type="password" name="password"><br>
    MAIL <input type="email" name="mail"><br>
    <input type="submit" value="登録"><br>
</form>
```

## ルーティングを設定
`/index.js`に以下の記述を追加してください。

```index.js
const validatorSignupRouter = require('./router/validator-signup');
app.use(validatorSignupRouter.routes());
app.use(validatorSignupRouter.allowedMethods());
```

## ページの遷移を修正
`/view/login.ejs`を以下のように修正してください。

```login.ejs
<h1>ログイン</h1>
<form action="login" method="post">
   USER NAME <input type="text" name="name" placeholder="userと入力"><br>
   PASSWORD <input type="password" name="password" placeholder="userと入力"><br>
   <input type="submit" value="ログイン"><br>
</form>

<!--validator-signupに遷移するように修正-->
<a href="validator-signup">アカウント登録</a>
<% if(typeof loginResult !== "undefined"){ %>
   <br><br>
   <%= loginResult %>
   <br><br>
   <a href="index">トップへ</a>
<% } %>
```

## ログイン認証で参照するDBを変更
ログイン時、検証を行なっているSQL文を修正します。
`/router/login.js` のSQL文を以下のように修正してください

```login.js
// DBからuserNameが一致するデータを検索
let loginSQL = 'SELECT user_id, password, user_name FROM validate_user WHERE user_name = ?';
```

## validationの表示結果
下記のコマンドを実行して、以下のURLにアクセスしてください。
`npm start`

http://localhost:5000/validator-signup

![validation-signup.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/685849/151c4bb7-ce6f-a241-2862-77fd37946688.png)

## 最後に
以上でvalidatorjsを利用した入力値の検証は完成です。
次回はセッションを用いたログイン状態の管理を実装してきます。