Old Sunset Days

Node.jsでJoiでパラメータのバリデーション

日付 タグ node.js カテゴリ node.js

目次

Joiを利用してパラメータのバリデーションを行う

例えば、ユーザー登録におけるユーザー名、パスワードなどに何かしらの文字数の制約や禁則文字の条件をつけて、サーバーに渡ってきたパラメータがそれらの条件を満たしているのかのチェックはNode.jsでどうやるのだろうか?そこで便利なのがオブジェクトのバリデーション用のJoiである。

https://github.com/hapijs/joi - Joi

今回はこのJoiというパッケージを使ってバリデーションする方法について見ていく。

なお、今回作ったサンプルのソースコードは参照用としてGithubの以下に置いてある。 https://github.com/hugodeblog/node-joi

今回実装するJoiを利用したサンプル

いつもの通り、サンプルディレクトリを作って、そこでJoiパッケージをインストール。

$ mkdir node-joi
$ cd node-joi
$ npm install --save joi

この記事を書いている時点ではJoiのバージョンは17.3.0であった。


joi_test.mjs

import Joi from 'joi';

const schema = Joi.object().keys({
  username: Joi.string().alphanum().min(6).max(16).required(),
  email: Joi.string().email(),
  password: Joi.string().regex(/^[a-zA-Z_0-9]{8,30}$/).required()
})

// ユーザー名が短すぎる
// emailが適当な文字列すぎる
const payload1 = {
  username: 'me',
  email: 'wrong_string', 
  password: '12345678'
}

// emailはなくてもOK
const payload2 = {
  username: 'testisok',
  password: '12345678'
}

// passwordに使用不可な文字
const payload3 = {
  username: 'testisok',
  password: '12345a_5AB*678'
}

console.log('-------------------------');
console.log('checking payload1 ......');
console.log('-------------------------');
var {error, value} = schema.validate(payload1, { abortEarly: false })

if(error){
  console.log(error.details);
} else {
  console.log(value);
}

console.log('-------------------------');
console.log('checking payload2 ......');
console.log('-------------------------');
var {error, value} = schema.validate(payload2, { abortEarly: false })

if(error){
  console.log(error.details);
} else {
  console.log(value);
}

console.log('-------------------------');
console.log('checking payload3 ......');
console.log('-------------------------');
var {error, value} = schema.validate(payload3, { abortEarly: false })

if(error){
  console.log(error.details);
} else {
  console.log(value);
}

では、ちょっとコードの作りを見ておく。

const schema = Joi.object().keys({
  username: Joi.string().alphanum().min(6).max(16).required(),
  email: Joi.string().email(),
  password: Joi.string().regex(/^[a-zA-Z_0-9]{8,30}$/).required()
})

Joi.object() でインスタンスを生成してキーのセットを定義する。
keys() 中で制約スキーマを定義していくのだが、 ここでは、

  • username
    • 文字列(アルファベット、数字)で最低6文字以上、最大16文字でrequired()で必ず必要
  • email
    • 組み込みのemail()を利用してEmailアドレス形式である制約。required()がないので省略可
  • password
    • regexで正規表現を用いて[a-zA-Z_0-9]で8文字以上、30文字まで、required()で必ず必要

サンプルのデータを与えて
(WebサービスならGETに付加されたパラメータやPOSTされたパラメータ)

const payload1 = {
  username: 'me',
  email: 'wrong_string', 
  password: '12345678'
}

これに対してスキーマを用いてバリデーションをする。

var {error, value} = schema.validate(payload1, { abortEarly: false })

if(error){
  console.log(error.details);
} else {
  console.log(value);
}

実際の動作挙動チェック

$ node joi_test.mjs
-------------------------
checking payload1 ......
-------------------------
[
  {
    message: '"username" length must be at least 6 characters long',
    path: [ 'username' ],
    type: 'string.min',
    context: {
      limit: 6,
      value: 'me',
      encoding: undefined,
      label: 'username',
      key: 'username'
    }
  },
  {
    message: '"email" must be a valid email',
    path: [ 'email' ],
    type: 'string.email',
    context: {
      value: 'invalid_string',
      invalids: [Array],
      label: 'email',
      key: 'email'
    }
  }
]
-------------------------
checking payload2 ......
-------------------------
{ username: 'testisok', password: '12345678' }
-------------------------
checking payload3 ......
-------------------------
[
  {
    message: '"password" with value "12345a_5AB*678" fails to match the required pattern: /^[a-zA-Z_0-9]{8,30}$/',
    path: [ 'password' ],
    type: 'string.pattern.base',
    context: {
      name: undefined,
      regex: /^[a-zA-Z_0-9]{8,30}$/,
      value: '12345a_5AB*678',
      label: 'password',
      key: 'password'
    }
  }
]
  • payload1
    • usernameとpasswordがスキーマの制約を満たしていないのでエラー
  • payload2
    • emailが与えられていないがemailは必須ではなくusernameとpasswordはスキーマの制約を満たしている
  • payload3
    • passwordにスキーマの制約に合っていない文字が含まれる

以上、Joiを使った値のバリデーションの簡単な使い方を見てきた。 あとは、これをGETに付加されたパラメータやPOSTのパラメータに適用していけば良いだろう。

今回passwordには簡単な正規表現で指定した制約のみを定義したが、より実用的にはパスワードにはもっと複雑なルールを課していくことになるだろう。