九保すこひ@フリーランスエンジニア|累計300万PVのブログ運営中
さてさて、ここ数回は高速なサイトを構築する流れでNode.js
のExpress
を使った開発方法を紹介しています。
Express
はLaravel
と比べると、より必要なものだけに特化したフレームワークで「いたれりつくせり」のLaravel
とはまた違った雰囲気があります。
そして、今回はその「標準搭載されてはいないもの」の中から、
バリデーション機能(入力が正しい/間違ってるのチェック)
の使い方を実例で紹介したいと思います。
ぜひ皆さんのお役に立てると嬉しいです😊✨
開発環境: Node 8.10、Express 4.1、express-validator 6.3
目次
- 1 インストールする
- 2 準備する
- 3 基本的な使い方
- 4 バリデーション・ルールの一覧
- 4.1 空白ではないかどうか(必須項目)
- 4.2 特定の文字列を含んでいるかどうか:contains()
- 4.3 入力した2つの値が一致するかどうか
- 4.4 特定の日付より後かどうか: isAfter()
- 4.5 特定の日付より前かどうか: isBefore()
- 4.6 アルファベットかどうか: isAlpha()
- 4.7 アルファベットと数字かどうか: isAlphanumeric()
- 4.8 アスキー文字かどうか: isAscii()
- 4.9 Base32でエンコードした文字列かどうか: isBase32()
- 4.10 Base64でエンコードした文字列かどうか: isBase64()
- 4.11 boolean値かかどうか: isBoolean()
- 4.12 文字列の長さが指定した範囲内かどうか: isLength()
- 4.13 文字列のバイト長が指定した範囲内かどうか: isByteLength()
- 4.14 クレジットカード番号かどうか: isCreditCard()
- 4.15 金額として正しいか: isCurrency()
- 4.16 dataURIとして正しいか: isDataURI()
- 4.17 数字かどうか: isNumeric()
- 4.18 Int型の数字かどうか: isInt()
- 4.19 Float型の数字かどうか: isFloat()
- 4.20 Decimal型の数字かどうか: isDecimal()
- 4.21 特定の数字で割り切れるかどうか: isDivisibleBy()
- 4.22 メールアドレスかどうか: isEmail()
- 4.23 空白かどうか: isEmpty()
- 4.24 ドメインとして正しいか: isFQDN()
- 4.25 マルチバイト文字が含まれているかどうか: isMultibyte()
- 4.26 全角文字が含まれているか: isFullWidth()
- 4.27 半角文字が含まれているか: isHalfWidth()
- 4.28 全角文字と半角文字が両方含まれているか: isVariableWidth()
- 4.29 ハッシュ値として正しいか: isHash()
- 4.30 MD5のハッシュ値として正しいか: isMD5()
- 4.31 16進数として正しいか: isHexadecimal()
- 4.32 色(16進数)として正しいか
- 4.33 特定の値に含まれているか: isIn()
- 4.34 特定の値だけで構成された文字列かどうか: isWhitelisted()
- 4.35 IPアドレスかどうか: isIP()
- 4.36 IPアドレスが範囲内に入っているか: isIPRange()
- 4.37 ISBNとして正しいか:isISBN
- 4.38 証券コード(ISIN)として正しいか: isISIN()
- 4.39 国コード(2桁)として正しいか: isISO31661Alpha2()
- 4.40 国コード(3桁)として正しいか: isISO31661Alpha3()
- 4.41 日付として正しいか
- 4.42 日時として正しいか: isISO8601()
- 4.43 日時として正しいか: isRFC3339()
- 4.44 国際標準図書番号(ISSN)として正しいか: isISSN()
- 4.45 国際標準レコーディングコード(ISRC)として正しいか: isISRC()
- 4.46 JSONとして正しいか: isJSON()
- 4.47 JWTトークンとして正しいか: isJWT()
- 4.48 緯度経度として正しいか: isLatLong()
- 4.49 アルファベットの小文字かどうか: isLowercase()
- 4.50 アルファベットの大文字かどうか: isUppercase()
- 4.51 MACアドレスかどうか: isMACAddress()
- 4.52 マグネットリンクかどうか: isMagnetURI()
- 4.53 MimeTypeとして正しいか: isMimeType()
- 4.54 携帯電話番号として正しいか: isMobilePhone()
- 4.55 MongoDBのオブジェクトIDかどうか: isMongoId()
- 4.56 ポート番号として正しいか: isPort()
- 4.57 郵便番号かどうか: isPostalCode()
- 4.58 サロゲートペアかどうか: isSurrogatePair()
- 4.59 URLかどうか: isURL()
- 4.60 UUIDかどうか: isUUID()
- 4.61 正規表現にマッチするか: matches()
- 5 独自のエラーメッセージを使う
- 6 独自のバリデーションを使う
- 7 おわりに
インストールする
まずは、Express
でバリデーションが使えるようになるパッケージ「express-validator」をインストールしておきましょう。
npm install --save express-validator
準備する
「express-validator」を使うにはパッケージの読み込みとパラメータの設定が必要になります。以下のコードを適当な位置に追加してください。
const { check, validationResult } = require('express-validator'); app.use(express.json()); app.use(express.urlencoded({ extended: true }));
基本的な使い方
「express-validator」は以下のように、ルートに設定します。
app.post('/user/', [ check('value1').not().isEmpty().isEmail(), check('value2').isNumeric() ], (req, res) => { const errors = validationResult(req); if(!errors.isEmpty()) { // バリデーション失敗 return res.status(422).json({ errors: errors.array() }); } // バリデーション成功 res.json({ result: true }); });
※check()
の部分は.
で繋いでいくつも追加できます。
※また、check()
は配列で好きなだけ追加することもできます。
バリデーション・ルールの一覧
「express-validator」(内部的は validator.js を使ってます)にはたくさんのバリデーション・ルールが存在していますので、1つずつ見ていきましょう!
※ただし、コードが長くなってしまうので、check()...
の部分のみを紹介しています。実際の使い方は、基本的な使い方をご覧ください。
空白ではないかどうか(必須項目)
例えば、名前の入力を必須項目にする場合です。
check('attribute_name').not().isEmpty();
特定の文字列を含んでいるかどうか:contains()
例えば、「テスト」という文字が入力内容に含まれているかどうかをチェックする場合です。
check('attribute_name').contains('テスト')
入力した2つの値が一致するかどうか
例えば、「パスワード」と「パスワード(確認)」の内容が一致するかをチェックする場合です。
check('password') .custom((value, { req }) => { if(req.body.password !== req.body.passwordConfirmation) { throw new Error('パスワード(確認)と一致しません。'); } return true; })
※ 実は、内部的に使っているvalidator.js
にはequals()
というメソッドがあるのですが、check()
を使う位置では比較する値を取得できないのでcustom()
を使って実装しています。
特定の日付より後かどうか: isAfter()
例えば、「2000年1月15日」よりも後の日付かどうかをチェックする場合です。
check('attribute_name').isAfter('2000-01-15')
※ つまり、「2000-01-16」以降の日付だとバリデーションを通過します。
特定の日付より前かどうか: isBefore()
例えば、「2000年1月15日」よりも前の日付かどうかをチェックする場合です。
check('attribute_name').isBefore('2000-01-15')
※ つまり、「2000-01-14」以前の日付だとバリデーションを通過します。
アルファベットかどうか: isAlpha()
check('attribute_name').isAlpha()
※ バリデーションを通過できるのは、半角のa〜z
とA〜Z
です。
アルファベットと数字かどうか: isAlphanumeric()
check('attribute_name').isAlphanumeric()
※ バリデーションを通過できるのは、半角のa〜z
とA〜Z
、1〜9
です。
アスキー文字かどうか: isAscii()
check('attribute_name').isAscii()
※ バリデーションを通過できるのは、半角のa〜z
とA〜Z
、そして!
や*
などのアスキー文字です。(全角文字はエラーになります)
Base32でエンコードした文字列かどうか: isBase32()
check('attribute_name').isBase32()
Base64でエンコードした文字列かどうか: isBase64()
check('attribute_name').isBase64()
boolean値かかどうか: isBoolean()
例えば、入力が「true」もしくは「false」かをチェックする場合です。
check('attribute_name').isBoolean()
※ バリデーションを通過するのは半角小文字の「true」と「false」だけです。
文字列の長さが指定した範囲内かどうか: isLength()
例えば、入力された文字列の長さが0〜5
の範囲かどうかをチェックする場合です。
check('attribute_name').isLength({ min:3, max:5 })
※ 全角・半角関係なく単純に文字の数をチェックする形式です。もしバイト長さをチェックしたい場合は、isByteLength()を使ってください。
文字列のバイト長が指定した範囲内かどうか: isByteLength()
例えば、入力された文字列のバイト長が(UTF-8
として)0〜5
の範囲かどうかをチェックする場合です。
check('attribute_name').isByteLength({ min:0, max:5 })
※ つまりこの例の場合「12345」はバリデーションを通過しますが、「ねこ」は通過しません。なぜならUTF-8
は3
バイト文字なので、長さが6
になるからです。
※もし単純に文字列の長さをチェックしたい場合はisLength()を使ってください。
クレジットカード番号かどうか: isCreditCard()
例えば、入力されたクレジットカード番号が正しいかどうかをチェックする場合です。(※ ただし、あくまで文字列が正しいかのチェックです。実際にカード番号が有効かどうかをチェックするわけではないので注意してください)
check('attribute_name').isCreditCard()
金額として正しいか: isCurrency()
例えば、「¥1,000」という文字列が金額として正しいかをチェックする場合です。
check('attribute_name') .isCurrency({ // 任意 symbol: '¥', // 通貨シンボル require_symbol: false, // 通貨シンボル($, ¥)などが必要? allow_negatives: true, // マイナスの数字はOK? allow_decimal: false, // 小数点OK? })
※ オプションは、日本環境に合うものをピックアップしました。その他のものはこちらで確認してください。
dataURIとして正しいか: isDataURI()
例えば、dataURI
に変換された画像データが正しい形式になっているかどうかをチェックする場合です。
check('attribute_name').isDataURI()
数字かどうか: isNumeric()
例えば、「-10」「1.5」「3」などの数値をチェックする場合です。
check('attribute_name').isNumeric()
Int型の数字かどうか: isInt()
例えば、整数かどうかをチェックする場合です。
check('attribute_name') .isInt({ // 任意 min: 3, max: 10 })
※この例では、入力値が3
、もしくは10
はバリデーションを通過します。
Float型の数字かどうか: isFloat()
check('attribute_name') .isFloat({ // 任意 min: 1.2, max: 5.8 })
※この例の場合、1.2
と5.8
はバリデーションを通過できます。
Decimal型の数字かどうか: isDecimal()
例えば、野球で言う防御率(例:2.59
)など小数点つきの数字として正しいかをチェックする場合です。
check('attribute_name') .isDecimal({ // 任意 force_decimal: false, // 小数点がない数字もOKな場合 decimal_digits: '2' // 小数点以下が2桁で固定 })
※もし小数点以下が1桁以上、2桁以下にする場合は、1,2
をセットしてください。
特定の数字で割り切れるかどうか: isDivisibleBy()
check('attribute_name').isDivisibleBy(3)
※この例では、9
なバリデーションを通過し、8
ならエラーになります。
メールアドレスかどうか: isEmail()
check('attribute_name').isEmail()
空白かどうか: isEmpty()
check('attribute_name') .isEmpty({ // 任意 ignore_whitespace: false // 空白も1文字とする })
※ 全角文字のスペースも対象になっています
ドメインとして正しいか: isFQDN()
check('attribute_name').isFQDN()
マルチバイト文字が含まれているかどうか: isMultibyte()
check('attribute_name').isMultibyte()
※例えば「モンゴル800」はバリデーションを通過します。
全角文字が含まれているか: isFullWidth()
check('attribute_name').isFullWidth()
※例えば「モンゴル800」はバリデーションを通過します。
半角文字が含まれているか: isHalfWidth()
check('attribute_name').isHalfWidth()
※例えば「モンゴル800」はバリデーションを通過します。
全角文字と半角文字が両方含まれているか: isVariableWidth()
check('attribute_name').isVariableWidth()
※例えば、「モンゴル」「800」はバリデーションを通過できませんが、「モンゴル800」なら通過できます。
ハッシュ値として正しいか: isHash()
例えば、md5
のハッシュ値として正しいかをチェックする場合です。
check('attribute_name').isHash('md5')
使えるハッシュ形式は以下のとおりです。
- md4
- md5 ・・・ isMD5()という専用メソッドがあります
- sha1
- sha256
- sha384
- sha512
- ripemd128
- ripemd160
- tiger128
- tiger160
- tiger192
- crc32
- crc32b
MD5のハッシュ値として正しいか: isMD5()
check('attribute_name').isMD5()
※その他のハッシュをチェックする場合は、isHashを参照してください。
16進数として正しいか: isHexadecimal()
check('attribute_name').isHexadecimal()
色(16進数)として正しいか
例えば、「fff」や「ff0000」など16進数を使った色の表現が正しいかをチェックします。
check('attribute_name').isHexColor()
特定の値に含まれているか: isIn()
例えば、入力値が都道府県かどうかをチェックする場合です。
check('attribute_name') .isIn([ '北海道', /* (..省略) */ '沖縄県' ])
特定の値だけで構成された文字列かどうか: isWhitelisted()
例えば、1〜5
の数字のみで構成された文字化どうかをチェックする場合です。
check('attribute_name').isWhitelisted(['1', '2', '3', '4', '5'])
※この例では、「12345」はバリデーションを通過しますが、「123456」は通過できません。
IPアドレスかどうか: isIP()
check('attribute_name').isIP()
※ IPアドレスv4
とv6
に対応しています。
IPアドレスが範囲内に入っているか: isIPRange()
例えば「10.0.0.0/24」のようなIPアドレス情報をチェックする場合です。(v4のみ)
check('attribute_name').isIPRange()
ISBNとして正しいか:isISBN
check('attribute_name').isISBN()
※第1引数に桁数を指定することができます。10
と13
が有効。指定しない場合は両方が有効になります。
証券コード(ISIN)として正しいか: isISIN()
check('attribute_name').isISIN()
国コード(2桁)として正しいか: isISO31661Alpha2()
例えば、「JP」などの2桁の国コードをチェックする場合です。
check('attribute_name').isISO31661Alpha2()
国コード(3桁)として正しいか: isISO31661Alpha3()
例えば、「JPN」などの3桁の国コードをチェックする場合です。
check('attribute_name').isISO31661Alpha3()
日付として正しいか
check('attribute_name') .custom(value => { const matches = value.match(/^(\d{4})\/(\d{2})\/(\d{2})$/); const dt = new Date(value); if(!matches || isNaN(dt)) { throw new Error('「YYYY/MM/DD」の正しい日付ではありません。'); } return true; })
※過去にvalidator.js
にはisDate()
というメソッドがあったようですが、現在は削除されているので、独自にコードを書きました。
日時として正しいか: isISO8601()
ISO 8601
で定められた日付として正しいかどうかをチェックする場合です。
check('attribute_name').isISO8601()
なお、バリデーションを通過する例は以下になります。
- 2000-12-15
- 2000-12-15T13:50:40
- 2000-12-15T13:50:40+09:00
日時として正しいか: isRFC3339()
RFC 3339
で定められた日付として正しいかどうかをチェックする場合です。
check('attribute_name').isRFC3339()
※この例では、「2000-12-15T13:50:40+09:00」のような値がバリデーションを通過します。
国際標準図書番号(ISSN)として正しいか: isISSN()
check('attribute_name').isISSN()
国際標準レコーディングコード(ISRC)として正しいか: isISRC()
check('attribute_name').isISRC()
JSONとして正しいか: isJSON()
check('attribute_name').isJSON()
JWTトークンとして正しいか: isJWT()
check('attribute_name').isJWT()
緯度経度として正しいか: isLatLong()
check('attribute_name').isLatLong()
※例えば、東京スカイツリーでは「35.7101, 139.8107」となります。
アルファベットの小文字かどうか: isLowercase()
check('attribute_name').isLowercase()
※例えば、g
やj
がバリデーションを通過します。全角文字も対応しています。
アルファベットの大文字かどうか: isUppercase()
check('attribute_name').isUppercase()
※例えば、G
やJ
がバリデーションを通過します。全角文字も対応しています。
MACアドレスかどうか: isMACAddress()
check('attribute_name').isMACAddress()
※例えば、00:1B:44:11:3A:B7
などがバリデーションを通過します。
マグネットリンクかどうか: isMagnetURI()
check('attribute_name').isMagnetURI()
MimeTypeとして正しいか: isMimeType()
check('attribute_name').isMimeType()
携帯電話番号として正しいか: isMobilePhone()
check('attribute_name') .isMobilePhone('ja-JP', { strictMode: false // 国際電話形式にするかどうか })
※ strictMode
をtrue
にした場合、例えば「+819012345678」というような形式のみバリデーションを通過するようになります。
MongoDBのオブジェクトIDかどうか: isMongoId()
check('attribute_name').isMongoId()
※例えば「507f191e810c19729de860ea」がバリデーション通過します。
ポート番号として正しいか: isPort()
0〜65535
の数字かどうかをチェックする場合です。
check('attribute_name').isPort()
郵便番号かどうか: isPostalCode()
check('attribute_name').isPostalCode('JP')
※バリデーションを通過する例としては、「131-0045」となり、ハイフンが必要です。
サロゲートペアかどうか: isSurrogatePair()
check('attribute_name').isSurrogatePair()
URLかどうか: isURL()
check('attribute_name') .isURL({ // 任意 protocols: ['http','https','ftp'], // どのプロトコルが有効? require_protocol: false, // プロトコルは必要? })
UUIDかどうか: isUUID()
check('attribute_name').isUUID()
※ 引数にUUID
のバージョンを指定できます。3
, 4
, 5
が有効です。
正規表現にマッチするか: matches()
check('attribute_name').matches('^[a-z]+$', 'i')
※この例では、「abcD」などはバリデーションを通過しますが、「a3b」などは通過できません。
※第2引数はmodifier
で、任意です。
独自のエラーメッセージを使う
独自のエラーメッセージを使うには、withMessage()
を使います。
check('value') .not().isEmpty().withMessage('この項目は必須入力です。') .isEmail().withMessage('有効なメールアドレスではありません。')
ただし、バリデーションがいくつも必要になる場合、同じエラーメッセージを何度もセットするのはめんどうですし、保守管理にも向いているとはいえません。
そのため、そんな場合はi18n
などの翻訳パッケージを使う便利です。
やり方は次のとおりです。
まずパッケージをインストールしてください。
npm install --save i18n
すると、自動で「locales」というフォルダ、そしてその中に(環境によって違うと思いますが)「en.json」と「ja.json」というファイルが作成されます。
では、「app.js」を開いてi18n
が使えるようにしましょう。
const i18n = require('i18n'); // 翻訳 app.use(i18n.init); i18n.configure({ locales:['ja'], directory: __dirname + '/locales', objectNotation: true });
これで、i18n
の準備は完了です。
今回は日本語のみに対応させますので、「locales/ja.json」を開いて次のようにバリデーション・メッセージを追加します。
{ "validation": { "message": { "required": "この項目は必須入力です。" } } }
では、実際にバリデーション・メッセージを使ってみましょう。
check('value') .not().isEmpty().withMessage((value, { req }) => { return req.__('validation.message.required'); })
また、custom()
を使った場合も同様です。
check('value') .custom((value, { req }) => { return User.count({ where: { email: value } }).then(userCount => { if(userCount > 0) { throw new Error(req.__('validation.message.email_exists')); } }); })
この方法でemail
やunique
などのメッセージを追加して使うと、今後の保守管理もしやすくなると思います。
※ ちなみにバリデーションのメッセージ内容は、Laravel-langのものを参考にするといいでしょう。
独自のバリデーションを使う
独自のバリデーションを使う場合は、custom()
を使います。
例えば、送信されたメールアドレスがすでにUsers
テーブルに登録済みかどうかをチェックするバリデーションを作ってみましょう。
check('email').custom(value => { return User.count({ where: { email: value } }).then(userCount => { if(userCount > 0) { throw new Error('このメールアドレスはすでに登録されています。'); } }); })
ここで重要なのが、バリデーションに失敗した場合はnew Error('******')
を使う部分です。ここで設定したメッセージがエラーメッセージとして返されることになります。なお、return Promise.reject('******');
でも同じ結果になります。
おわりに
ということで、今回は「express-validator」でExpress
にバリデーション機能をつけてみました。
普段からよくLaravel
を使う身からすると、「へぇ、こんなバリデーションまで用意してるんだ」というものもあれば、逆に「あー、あれがないのか」などいい部分もそうでない部分もあるようでした。
やはり一口にプログラムと言ってもいろいろと違う部分があって、とても勉強になりますね😊✨
「別の言語を勉強するのは、メインで使う言語のスキルアップにもなる」と聞いたことを思い出しました。
ぜひ皆さんも色々と挑戦してみてくださいね。
ではでは〜!