AdonisJSにValidator機能を追加する
AdonisJSにValidator機能を追加する
2022/11/07

こちらは、現時点で最新のAdonisJS v5の記事になります。

Validatorの導入手順とAdonis v4との違いについて紹介します。

Validatorとは、リクエストで送られてきたデータを解析・検証して異常があれば、エラーメッセージをレスポンスすることができる機能です。

公式リファレンス
https://docs.adonisjs.com/guides/validator/introduction

Adonis v4ではRouteにvalidatorというメソッドがありましたが、v5からはController内で行います。

Adonis v4

Route.post('users', 'UserController.store').validator('User/CreateUser')

Adonis v5

export default class UsersController {
  public async store({ auth, request, response }: HttpContextContract) {
    const payload = await request.validate(StoreUserValidator)
    const user = await User.create(payload)
...

ValidatorClassを作っていく

リクエストで送られてくるデータの検証をするスキーマ、エラーの場合のエラーメッセージを定義するClassを作ります。

例に上記で書いたStoreUserValidatorを作っていきます。

$ node ace make:validator User/StoreUser
CREATE: app/Validators/User/StoreUserValidator.ts
import { schema, CustomMessages } from '@ioc:Adonis/Core/Validator'
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'

export default class StoreUserValidator {
  constructor(protected ctx: HttpContextContract) {}

  public schema = schema.create({})

  public messages: CustomMessages = {}
}

ValidatorClassが生成されたので、schemaとmessagesを編集していく。

Validator Schemaの書き方

schemaでは、リクエストで送られてくるデータを検証するためのValidationRuleを決めます。

リクエストで、メールアドレスとパスワードを送って来てほしい場合は以下のようになります。

export default class StoreUserValidator {
  public schema = schema.create({
    email: schema.string([
      rules.required(),
      rules.email(),
      rules.unique({ table: 'users', column: 'email' })
    ]),
    password: schema.string([rules.required()]),
  })
}

emailは、string型で必須required, メール形式email, DB上のusersテーブルで重複がないかuniqueをルールにしています。

passwordは、string型で必須requiredをルールにしています。

他にも様々なルールが用意されているので、以下のリンクの左メニューから探しください。

https://docs.adonisjs.com/reference/validator/rules/alpha

Validationメッセージの書き方

続いてはルールに違反したときのメッセージについてですが、これは定義しなくてもデフォルトで設定されているのでエラーがでない心配は無いのですが、英語です。。。

ですので、日本語で返せるようにだったり、「メールアドレスは必須です」や「パスワードを入力してください」のように同じrequiredルールでも表示を変えたい場合はカスタムメッセージを定義する必要があります。

簡単に書くと以下のようになります。

export default class StoreUserValidator {
	...
  public messages: CustomMessages = {
    required: 'The {{ field }} is required to create a new account',
    'email.required': 'メールアドレスは必須です',
    'password.required': 'パスワードを入力してください',
  }
}

これでも動作はするのですが、StoreUserValidatorだったりLoginUserValidator, UpdateUserValidatorだったり、Validatorが増えたときにそれぞれで同じメッセージを書く必要があり、メール, メールアドレス, Emailのような表記のゆれや、変更があったときに修正漏れの可能性が高くなります。

なので、メッセージを共通化をしていきます。

Validatorメッセージの共通化

やり方としては、

  • 各ルールのエラーメッセージを一つのファイルにまとめる。
  • email, passwordといった項目がメールアドレス、パスワードといった表示名を一つのファイルにまとめる
  • カスタムメッセージでそこから参照できるように設定する

これを実現するために少しトリッキーかもしれませんが多言語サポートで使われるi18nを使おうと思います。

@adonisjs/i18nを導入。

$ yarn add @adonisjs/i18n
$ node ace configure @adonisjs/i18n
Server.middleware.register([
  () => import('App/Middleware/DetectUserLocale')
])

日本語しか使わないから、設定を日本語のみに変更

defaultLocale: 'ja',
supportedLocales: ['ja'],

翻訳データはjsonでもyamlでも書ける。
yaml推しなのでyamlで書いていく。

i18nはValidatorMessageに対応しています。
https://docs.adonisjs.com/guides/i18n#validator-messages

validator.yamlのsharedに書くと自動的にValidationMessageに紐づきます。

shared:
	maxLength: "最大{maxLength}文字までです"
  required: "{field}は必須です"
  email: "{field}が正しくありません"
  unique: "{field}は既に存在しています"

ただこのままだと、fieldが翻訳されずにemailが正しくありませんのようにemailと表示されてしまうので、メールアドレスが正しくありませんと日本語名で表示できるように修正していきます。

fieldの翻訳データを用意する。

email: 'メールアドレス'
password: 'パスワード'

Validatorをカスタマイズする。

export default class StoreUserValidator {
	// 追加
  get fields() {
    return {
      email: this.ctx.i18n.formatMessage('user.email'),
      password: this.ctx.i18n.formatMessage('user.password'),
    }
  }
  public messages: CustomMessages = {
    '*': (field, rule, arrayExpressionPointer, options) => {
      try {
        return this.ctx.i18n.formatMessage(`validator.shared.${rule}`, { field: this.fields[field] })
      } catch (_) {
        return this.ctx.i18n.validatorMessages()['*'](field, rule, arrayExpressionPointer, options)
      }
    },
  }
}

i18n.formatMessage(`validator.shared.${rule}`, { field: this.fields[field] })が、validator.yamlで定義したValidationメッセージに翻訳されたfieldを渡して、fieldも翻訳されたエラーメッセージを出力している。

これでemailフィールドはresouces/locale/*/user.yamlから翻訳データを取得してValidatorメッセージのfieldに出力でき、メールアドレスが正しくありませんと表示される。

もしfieldが定義されていなかったりのエラーの可能性も考慮してtry-catchで通常のi18n.validatorMessages()に渡している。

以上にて、AdonisでValidator設定が完了。

AdonisJS v5へのアップグレードが大変だったのでこちらにまとめました。