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の型システムに
非常に興味があり、型に守られた安全な開発を目指して日々精進している。