はじめに
メールアドレスの形式チェックを、自分で正規表現を書いて実装したことはありませんか。
一見簡単そうに見えて、+付きのアドレスや国際化ドメインを考慮し始めると、あっという間に
読めない正規表現が出来上がります。URLの検証、パスワードの強度チェック、フォームに
入力された文字列のサニタイズも同様に、ゼロから書くと抜け漏れが生まれやすい領域です。
Validatorは、こうした文字列の検証・サニタイズをまとめて引き受けてくれるライブラリです。
isEmail()やisURL()のような検証関数と、escape()やtrim()のようなサニタイズ関数を
100種類近く備えていて、依存ライブラリなしで使えます。
とはいえ、読むより触った方が早いと思います。メールアドレスとパスワードをその場で 入力して、Validatorがどう判定するかを見られるサンプルを用意したので、先に挙動を 見たい方はこちらからどうぞ。
Validatorとは
Validatorはvalidatorjs組織が開発している、文字列専用の検証・サニタイズライブラリです。
GitHubで23,000以上のスターを集めており、Express.jsのミドルウェアexpress-validatorの
内部でも使われるなど、Node.jsのバックエンドからブラウザのフォームまで幅広く採用されています。
主な特徴
- 検証関数が豊富 -
isEmail()、isURL()、isInt()、isMobilePhone()、isDate()など、 実務でよく使う検証ルールが一通り揃っています - サニタイズ関数も持つ -
escape()でHTMLの特殊文字をエスケープしたり、trim()で 余分な空白を取り除いたり、normalizeEmail()でメールアドレスを正規化したりできます - 依存ゼロ - 外部パッケージに依存しないため、フロントエンドのバンドルサイズを 圧迫しません
- 入力は必ず文字列 - すべての関数は文字列を前提にしています。数値や真偽値を渡す
場合は
String(input)で明示的に変換してから渡す必要があります
インストール
npm install validator
TypeScriptで型定義を使う場合は、型定義パッケージも合わせて入れます。
npm install --save-dev @types/validator
Validatorのサンプルを動かす
以下はvalidator.isEmail()でメールアドレスの形式を、validator.isStrongPassword()で
パスワードの強度をそれぞれリアルタイムに判定するフォームです。入力するたびに
判定結果が即座に切り替わります。
まずは要点だけを抜き出したコードです。
import validator from 'validator'
validator.isEmail('foo@example.com') // true
validator.isEmail('not-an-email') // false
validator.isStrongPassword('Sw0rd-fish!', {
minLength: 8,
minLowercase: 1,
minUppercase: 1,
minNumbers: 1,
minSymbols: 1,
}) // true
実際に動かせるのが下のサンプルです。メールアドレスの@を消したり、パスワードを
「password」のような単純な文字列に変えたりして、判定がどう変わるか試してみてください。
isEmail()はドメインの形式まで見てくれるので、taro@exampleのようにトップレベル
ドメインが欠けた文字列もきちんと弾きます。isStrongPassword()はminLengthや
minSymbolsといったオプションの数値を変えるだけで、求める強度を柔軟に調整できます。
基本的な使い方
Validatorはデフォルトエクスポートされたオブジェクトのメソッドとして、それぞれの 検証・サニタイズ関数を呼び出します。すべての引数は文字列である必要があります。
import validator from 'validator'
// 検証系: true / false を返す
validator.isEmail('foo@example.com') // true
validator.isURL('https://example.com') // true
validator.isInt('42') // true
validator.isLength('hello', { min: 2, max: 10 }) // true
validator.isAlphanumeric('abc123') // true
// サニタイズ系: 変換後の文字列を返す
validator.trim(' hello ') // 'hello'
validator.escape('<b>bold</b>') // '<b>bold</b>'
validator.toInt('42px', 10) // 42
数値や真偽値をそのまま渡すとエラーになる点に注意してください。フォームの
<input>から取得した値はもともと文字列なので、そのまま渡せば問題ありません。
実践的なユースケース
会員登録フォームをまとめて検証する
1つの値だけでなく、複数のフィールドを持つオブジェクトを一括で検証したい場面は
よくあります。フィールドごとにisEmail()やisLength()を呼び分け、エラーを
オブジェクトに集約するパターンです。
import validator from 'validator'
function validateSignup(input: { email: string; username: string; password: string }) {
const errors: Record<string, string> = {}
if (!validator.isEmail(input.email)) {
errors.email = 'メールアドレスの形式が不正です'
}
if (!validator.isLength(input.username, { min: 3, max: 20 })) {
errors.username = 'ユーザー名は3〜20文字で入力してください'
}
if (!validator.isStrongPassword(input.password, { minLength: 8 })) {
errors.password = 'パスワードの強度が不足しています'
}
return errors
}
下のサンプルでは、メールアドレス・ユーザー名・パスワードの3項目を同時に検証し、 エラーがあるフィールドだけを一覧表示します。ユーザー名を2文字以下にしてみると、 どのフィールドで引っかかったのかがすぐに分かります。
このように、フィールドごとの検証結果をオブジェクトに集めておくと、フォーム
コンポーネント側では「どの項目にエラーメッセージを出すか」をerrors.emailの
ような形でそのまま参照できます。
入力値をエスケープ・正規化してから保存する
ユーザーが入力した文字列をそのまま画面に表示したり、DBに保存したりするのは
危険です。Validatorのescape()はHTMLの特殊文字を実体参照に変換し、trim()は
前後の空白を取り除き、normalizeEmail()はメールアドレスを比較しやすい正規形
(Gmailのドット無視など)に変換します。
import validator from 'validator'
validator.escape('<script>alert(1)</script>')
// => '<script>alert(1)</script>'
validator.trim(' Taro Yamada ')
// => 'Taro Yamada'
validator.normalizeEmail('Taro.Yamada+news@GMAIL.com')
// => 'taroyamada@gmail.com'
下のサンプルはコメント入力欄です。入力欄に<b>や<script>のようなHTMLタグを
含む文字列を打ち込むと、escape()によってタグがそのまま文字として表示される
様子を確認できます。
escape()を通した文字列をtextContentではなくinnerHTMLに差し込んでしまうと
意味がなくなってしまう点には注意してください。escape()はあくまで「タグを
文字として見せたいとき」に使うもので、DOMにHTMLとして描画する際のサニタイズには
DOMPurifyのような別のライブラリを使う必要があります。
まとめ
Validatorを使うと、メールアドレスやURLの検証、パスワードの強度チェック、 HTMLエスケープやメールアドレスの正規化といった文字列まわりの定番処理を、 自前の正規表現に頼らずまとめて任せられます。フィールドごとの検証結果を オブジェクトに集約するパターンを覚えておけば、単一の値だけでなく、フォーム 全体のバリデーションにもそのまま応用できます。
次にフォームを実装するときは、正規表現を書き始める前に、Validatorに欲しい 関数がないか探してみてください。
