express-validator を使ってみたポイント

## はじめに
以前、 [Node.js](https://nodejs.org/en/) + [Express.js](http://expressjs.com/) で Web API を開発した際、 入力チェックに [express-validator](https://express-validator.github.io/docs/) を使いました。
express-validator は [validator.js](https://github.com/validatorjs/validator.js) がベースになっており、 validation や sanitize ができる便利なモジュールですが、提供されている API は文字列に対する検証になるため、 JSON パラメータの型を明確にチェックしたい場合には少し注意が必要でした。
今回は使用した際のポイントなんかを自分の整理がてらメモとして残します。

## 環境
OS: macOS Mojave 10.14.6
Node.js: 10.16.3
Express.js: 4.16.1
express-validator: 6.2.0

## Case1（GET & 桁チェック）
|引数 |必須 |桁 |
|:---: |:---: |:---: |
|arg1 |○ | |
|arg2 | |min:2 max:4 |
|arg3 | |max:4 |
|arg4 | |min:2 |

```js:case1.js
const express = require('express');
const router = express.Router();
const {check, validationResult} = require('express-validator');

router.get('/', validateParam(), async (req, res) => {
    const errors = validationResult(req);
    if(!errors.isEmpty()) {
        res.status(400).end();
        return;
    }

    res.status(200).end();
});

function validateParam() {
	return [
		check('arg1')
			.exists({checkFalsy: true})
            .isString(),
        check('arg2')
            .optional({nullable: true})
            .isInt()
            .isLength({min: 2, max: 4}),
        check('arg3')
            .optional({nullable: true})
            .isInt()
            .isLength({min: undefined, max: 4}),
        check('arg4')
            .optional({nullable: true})
            .isInt()
            .isLength({min: 2, max: undefined})
    ];
}

module.exports = router;
```

### OKな例
```
?arg1=hoge
?arg1=hoge&arg2=12
?arg1=hoge&arg2=1234
?arg1=hoge&arg3=12
?arg1=hoge&arg3=1
?arg1=hoge&arg3=1234
?arg1=hoge&arg4=12
?arg1=hoge&arg4=1234567890
```

### NGな例
```
?arg1=hoge&arg2=1
?arg1=hoge&arg2=12345
?arg1=hoge&arg3=12345
?arg1=hoge&arg4=1
```

### 解説
GET メソッドを例に桁チェックを行います。
桁は `isLength()` を使用してチェックできます。
さらに、オプションで min (最小値)と max (最大値)が指定できます。
OKな例とNGな例で、オプションで指定した min と max の境界値が効いているのがわかります。

## Case2（POST & 型チェック）
|引数 |必須 |型 |
|:---: |:---: |:---: |
|arg1 |○ |整数 |
|arg2 | |文字列の整数 |
|arg3 | |真偽値 |

```js:case2.js
const express = require('express');
const router = express.Router();
const {check, validationResult} = require('express-validator');

router.post('/', validateParam(), async (req, res) => {
    const errors = validationResult(req);
    if(!errors.isEmpty()) {
        res.status(400).end();
        return;
    }

    res.status(200).end();
});

function validateParam() {
	return [
        check('arg1')
            .exists({nullable: true})
            .not().isString()
            .isInt(),
        check('arg2')
            .optional({nullable: true})
            .isString()
            .isInt(),
        check('arg3')
            .optional({nullable: true})
            .not().isString()
            .not().isInt()
            .isBoolean()
    ];
}

module.exports = router;
```

### OKな例
```
{
    arg1: 1,
    arg2: '1'
}

{
    arg1: 1,
    arg3: true    
}
```

### NGな例
```
{
    arg1: 1,
    arg2: 'a'
}

{
    arg1: 1,
    arg2: 1
}

{
    arg1: 1,
    arg3: 'true'
}

{
    arg1: 1,
    arg3: 1
}
```

### 解説
POST メソッドを例に型チェックを行います。
冒頭に触れている通り、 express-validator の API は文字列に対する検証なので、少し工夫をします。
arg1 は整数なので、 `.not().isString()` と `.isInt()` を組み合わせて**文字列ではない**かつ**整数**という指定になります。
arg2 のように**文字列の整数**に限定したい場合は、`.isString()` と `.isInt()` を組み合わせて**文字列**かつ**整数**という指定になります。
arg3 は真偽値ですが、文字列の `'false', 'true'` や整数の `0, 1` が真偽値として判定されないようそれぞれ `.not().isString()` と `.not().isInt()` という指定をしています。ケースによっては上記を真偽値として判定したいという場合もあるので、その場合は適宜指定を外せばOKです。

## Case3（POST & 条件付き必須）
|引数 |必須 |型 |
|:---: |:---: |:---: |
|arg1 |○ |真偽値 |
|arg2 |arg1=true の場合のみ必須 |文字列 |

```js:case3.js
const express = require('express');
const router = express.Router();
const {check, oneOf, validationResult} = require('express-validator');

router.post('/', validateParam(), async (req, res) => {
    const errors = validationResult(req);
    if(!errors.isEmpty()) {
        res.status(400).end();
        return;
    }

    res.status(200).end();
});

function validateParam() {
	return [
        check('arg1')
            .exists({checkNull: true})
            .not().isString()
            .not().isInt()
            .isBoolean(),
        check('arg2')
            .optional({nullable: true})
            .isString(),
        oneOf(
            [
                check('arg1')
                    .custom((value) => value === false),
                check('arg2')
                    .exists({checkNull: true})
            ]
        )
    ];
}

module.exports = router;
```

### OKな例
```
{
    arg1: false
}

{
    arg1: true,
    arg2: 'hoge'
}
```

### NGな例
```
{
    arg1: true
}
```

### 解説
POST メソッドを例に条件付き必須チェックを行います。
条件付き必須チェックを行う場合には `oneOf()` が使用できます。
`oneOf()` は Validation Chain の配列で、いずれか１つでも真であれば検証OK(=すべて偽だった場合に検証NG)となります。これを利用すると、 arg2 の**arg1=true の場合のみ必須**という条件は、**arg1=false** と **arg2が必須** の**どちらかが真**という条件に置き換えることができます(もっとスマートなやり方があれば教えて下さい)。

## まとめ
今回は簡単なサンプルを例にとって紹介しましたが、 express-validator のようなモジュールがあると、組み合わせでたいていの要件を満たすことができてとても便利です。
Web API で入力チェックはとても大切ですが、あまり時間をかけたくないところでもあります。また、実装者が複数いれば実装の粒度も変わりやすいので、実装者に依存しにくくなるというのも大きなメリットでしょうか。

今回、使用したコードはGitHubで公開しています。
https://github.com/ponko2bunbun/express-validator-sample