express-validatorのSchema-Validationがよくわからないのでソースを読んでみる
2016新卒入社の龍島です。最近業務でexpress-validatorのSchema-Validationを利用しているのですが、あまり丁寧な情報がなく、利用方法がわからない部分がありました。そこで前日の光山の記事に触発され、ソースを読んで調べてみました。少しでも同じ悩みを持っている方の役に立てれば幸いです。今困っている!使い方を早く知りたい!という方は「Schema-Validationの設定方法」の章を読んでいただければと思います。
express-validatorとは
公式ドキュメントの説明を借りると、
Node.jsのwebアプリケーションフレームワークであるExpress上でミドルウェアとしてvalidator.jsの機能を利用して、フィールドをバリデート、サニタイズできるようにしたものです。
利用方法は下記のような感じです(公式ドキュメントより)。
const { check, validationResult } = require('express-validator');
app.post('/user', [
// username must be an email
check('username').isEmail(),
// password must be at least 5 chars long
check('password').isLength({ min: 5 })
], (req, res) => {
// Finds the validation errors in this request and wraps them in an object with handy functions
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
}
User.create({
username: req.body.username,
password: req.body.password
}).then(user => res.json(user));
});
ミドルウェアとしてcheck('${field名}').isEmail()などとするとフィールドがメールアドレスの形式かチェックできるといった次第ですね。
チェックはchainingが可能で、
check("password")
.isLength({min: 5})
.isInt()
とすれば長さが5以上で数字のみのバリデーションが可能です。
Schema-Validation
公式ドキュメントにもある通り、フィールド名をキーとしてオブジェクトでバリデーションの定義を記述できるものです。複数ページで共通のバリデーションを行う場合や、定義を動的に生成する際に便利そうですね。公式ドキュメントのサンプルを簡略化したものを載せておきます。
const { checkSchema } = require('express-validator');
app.put('/user/:id/password', checkSchema({
id: {
// The location of the field, can be one or more of body, cookies, headers, params or query.
// If omitted, all request locations will be checked
in: ['params', 'query'],
errorMessage: 'ID is wrong',
isInt: true,
// Sanitizers can go here as well
toInt: true
},
password: {
isLength: {
errorMessage: 'Password should be at least 7 chars long',
// Multiple options would be expressed as an array
options: { min: 7 }
}
},
firstName: {
isUppercase: {
// To negate a validator
negated: true,
},
rtrim: {
// Options as an array
options: [[" ", "-"]],
},
},
// Wildcards/dots for nested fields work as well
'addresses.*.postalCode': {
// Make this field optional when undefined or null
optional: { options: { nullable: true } },
isPostalCode: true
}
}), (req, res, next) => {
// handle the request as usual
})
フィールド名をキーとすることや、inで対象のlocationを絞れること、errorMessageでエラーメッセージを定義することは読み取れますが、isIntはbooleanを設定しているのに対して、isLengthはオブジェクトでoptionsがあったり、rtrimはoptionsが二重配列だったりと、どのように設定していけばよいのか読み取ることは難しいです。ドキュメントから読み取れなければソースを読みましょう。簡単にソースを読めるのはOSSのいいところです。
以下、isIntなどmethodに対する設定値(true, Object, Arrayなど)をソースに倣ってmethodCfgと呼ぶことにします。
ソースを読む
v6.2.0のソースを読んでいきます。checkSchemaはこのあたりです。40行程度しか無いのでサクッと読めそうですね。
全体をざっと見ると、各fieldに対してValidationChainを作って配列で返しているようです。
const chain = check(field, ensureLocations(config, defaultLocations), config.errorMessage);
で作成したValidateChainオブジェクトに対して
(chain[method] as any)(...options);
でoptionsを引数としてmethod(isIntなど)を実行して、chainingしています。つまり最初に例として出した
check("password")
.isLength({min: 5})
.isInt()
の形を作っているわけですね。
気になっているmethodCfgに何を入れればよいかという点は、下記のあたりを見るとわかります。
let options: any[] = methodCfg === true ? [] : methodCfg.options || [];
if (options != null && !Array.isArray(options)) {
options = [options];
}
optionsという変数に対してmethodCfgがtrueもしくはmethodCfg.optionsが無ければ空配列、methodCfg.optionsを代入します。またoptionsが配列でなかった場合は配列化していますね。このoptionsが先程のmethod実行時の引数にスプレッド演算子で展開されて渡され、n個目の要素が第n引数となります。
Schema-Validationの設定方法
ソースを読んだことでSchema-Validationの設定方法が見えてきました。
設定可能なmethod
check()にchainで繋げられるものがmethodとして利用可能なので、validatorjsのvalidatorsやsanitizersはもちろん、express-validator独自であるvalidation、sanitizationのadditional-methodsもが利用可能です。
methodCfgの設定方法
各methodで、引数が必要なくinやerrorMessageも指定しない場合はtrueを設定しておけば良いです。
id: {
isInt: true
}
引数が必要な場合はoptionsとして設定します。
password: {
isLength: {
options: { min: 7 }
}
}
// => check('password').isLength({min: 7})
引数が複数ある場合は配列にして渡します。※matchesはmodifierも引数に取れます。
text: {
matches: {
options: ['abc', 'i']
}
}
// => check('text').matches('abc', 'i')
引数に配列を渡す場合は注意が必要です(これにハマりました...)。
sort: {
isIn: {
options: [['recommend', 'lowPrice', 'highPrice']]
}
}
// => check('sort').isIn(['recommend', 'lowPrice', 'highPrice'])
配列はスプレッド演算子で展開されてmethodに渡されるため、配列を渡したい場合は二重配列にする必要があります。
その他は公式ドキュメントのサンプルの通りinでfieldの場所を指定できたり、errorMessageでバリデートエラー時のエラーメッセージを設定できたりします。
さいごに
公式ドキュメントのサンプルに大体のことは書いてありますが、やはり不明な部分のソースを読んでみるとかなり理解が深まりますね。今回の該当部分以外にもValidationChainの仕組みなどを読んでみましたが、参考になる部分も多かったです。
時間を見つけて今後はExpress本体やvalidator.jsの実装などをソースコードリーディングしようと思います。
龍島 広人
旅行プラットフォーム事業部 エンジニア 2016年新卒入社。
DevOpsや仮想化技術をメインにしていたが、最近はTypeScriptの型システムに
非常に興味があり、型に守られた安全な開発を目指して日々精進している。



