バリデーション

バリデーション

AdonisJSでのデータバリデーションは通常、コントローラーレベルで行われます。これにより、アプリケーションがリクエストを処理し、フォームフィールドの横に表示できるエラーをレスポンスで送信する前に、ユーザー入力をバリデーションできるようになります。

バリデーションが完了したら、信頼できるデータを使用してデータベースクエリ、スケジュールされたキュージョブ、メールの送信など、他の操作を実行できます。

バリデーションライブラリの選択

AdonisJSのコアチームは、フレームワークに依存しないデータバリデーションライブラリであるVineJSを作成しました。VineJSを使用する理由のいくつかは以下の通りです。

  • Node.jsエコシステムで最速のバリデーションライブラリの1つです。

  • 実行時のバリデーションとともに静的な型安全性を提供します。

  • webおよびapiのスターターキットには事前に構成されています。

  • 公式のAdonisJSパッケージは、VineJSにカスタムルール(例:Lucidはuniqueexistsルールを提供)を追加します。

ただし、AdonisJSは厳密にVineJSの使用を強制しません。自分やチームに最適なバリデーションライブラリを使用できます。単に@vinejs/vineパッケージをアンインストールし、使用したいパッケージをインストールしてください。

VineJSの設定

次のコマンドを使用してVineJSをインストールおよび設定します。

参照も: VineJSのドキュメント

node ace add vinejs
  1. 検出されたパッケージマネージャーを使用して@vinejs/vineパッケージをインストールします。

  2. adonisrc.tsファイル内に以下のサービスプロバイダーを登録します。

    {
    providers: [
    // ...他のプロバイダー
    () => import('@adonisjs/core/providers/vinejs_provider')
    ]
    }

バリデータの使用

VineJSでは、バリデータのコンセプトを使用します。アプリケーションが実行できる各アクションごとにバリデータを作成します。たとえば、新しい投稿の作成のためのバリデータ、投稿の更新のための別のバリデータ、および投稿の削除のためのバリデータを定義します。

ブログを例にして、投稿の作成/更新のためのバリデータを定義します。まず、いくつかのルートとPostsControllerを登録しましょう。

ルートの定義
import router from '@adonisjs/core/services/router'
const PostsController = () => import('#controllers/posts_controller')
router.post('posts', [PostsController, 'store'])
router.put('posts/:id', [PostsController, 'update'])
コントローラーの作成
node ace make:controller post store update
コントローラーのスキャフォールド
import { HttpContext } from '@adonisjs/core/http'
export default class PostsController {
async store({}: HttpContext) {}
async update({}: HttpContext) {}
}

バリデータの作成

PostsControllerを作成し、ルートを定義したら、次のaceコマンドを使用してバリデータを作成できます。

参照も: バリデータの作成コマンド

node ace make:validator post

バリデータはapp/validatorsディレクトリ内に作成されます。バリデータファイルはデフォルトでは空であり、複数のバリデータをエクスポートするために使用できます。各バリデータは、vine.compileメソッドの結果を保持するconst変数です。

次の例では、createPostValidatorupdatePostValidatorを定義しています。両方のバリデータはスキーマにわずかな違いがあります。作成時には、ユーザーが投稿のためにカスタムスラッグを提供できるようにしますが、更新時には許可しません。

バリデータスキーマ内の重複についてはあまり心配しないでください。重複を避けるためにすべてのコストを払う代わりに、理解しやすいスキーマを選択することをオススメします。WETコードベースの類推は、重複を受け入れるのに役立つかもしれません。

app/validators/post_validator.ts
import vine from '@vinejs/vine'
/**
* 投稿の作成アクションをバリデーションします
*/
export const createPostValidator = vine.compile(
vine.object({
title: vine.string().trim().minLength(6),
slug: vine.string().trim(),
description: vine.string().trim().escape()
})
)
/**
* 投稿の更新アクションをバリデーションします
*/
export const updatePostValidator = vine.compile(
vine.object({
title: vine.string().trim().minLength(6),
description: vine.string().trim().escape()
})
)

コントローラー内でバリデータを使用する

PostsControllerに戻り、バリデータを使用してリクエストボディをバリデーションします。request.all()メソッドを使用してリクエストボディにアクセスできます。

import { HttpContext } from '@adonisjs/core/http'
import {
createPostValidator,
updatePostValidator
} from '#validators/post_validator'
export default class PostsController {
async store({ request }: HttpContext) {
const data = request.all()
const payload = await createPostValidator.validate(data)
return payload
}
async update({ request }: HttpContext) {
const data = request.all()
const payload = await updatePostValidator.validate(data)
return payload
}
}

以上です!ユーザー入力のバリデーションは、コントローラー内の2行のコードです。バリデーションされた出力には、スキーマから推論された静的な型情報が含まれています。

また、validateメソッド呼び出しをtry/catchでラップする必要はありません。エラーの場合、AdonisJSは自動的にエラーをHTTPレスポンスに変換します。

エラーハンドリング

HttpExceptionHandlerは、バリデーションエラーを自動的にHTTPレスポンスに変換します。例外ハンドラはコンテンツネゴシエーションを使用し、Acceptヘッダーの値に基づいてレスポンスを返します。

ExceptionHandlerのコードベースをのぞいて、バリデーション例外がHTTPレスポンスに変換される方法を確認してください。

また、セッションミドルウェアは、renderValidationErrorAsHTMLメソッドを上書きし、フラッシュメッセージを使用してフォームとバリデーションエラーを共有します。

  • Accept=application/jsonヘッダーを持つHTTPリクエストは、SimpleErrorReporterを使用して作成されたエラーメッセージの配列を受け取ります。

  • Accept=application/vnd.api+jsonヘッダーを持つHTTPリクエストは、JSON API仕様にしたがってフォーマットされたエラーメッセージの配列を受け取ります。

  • セッションパッケージを使用したサーバーレンダリングされたフォームは、セッションフラッシュメッセージを介してエラーを受け取ります。

  • その他のリクエストは、エラーをプレーンテキストで受け取ります。

request.validateUsingメソッド

コントローラー内でバリデーションを実行する推奨される方法は、request.validateUsingメソッドを使用することです。request.validateUsingメソッドを使用する場合、バリデーションデータを明示的に定義する必要はありません。リクエストボディクエリ文字列の値ファイルがマージされ、データがバリデータに渡されます。

import { HttpContext } from '@adonisjs/core/http'
import {
createPostValidator,
updatePostValidator
} from '#validators/posts_validator'
export default class PostsController {
async store({ request }: HttpContext) {
const data = request.all()
const payload = await createPostValidator.validate(data)
const payload = await request.validateUsing(createPostValidator)
}
async update({ request }: HttpContext) {
const data = request.all()
const payload = await updatePostValidator.validate(data)
const payload = await request.validateUsing(updatePostValidator)
}
}

クッキー、ヘッダー、ルートパラメータのバリデーション

request.validateUsingメソッドを使用する場合、クッキー、ヘッダー、ルートパラメータを次のようにバリデーションできます。

const validator = vine.compile(
vine.object({
// リクエストボディのフィールド
username: vine.string(),
password: vine.string(),
// クッキーのバリデーション
cookies: vine.object({
}),
// ヘッダーのバリデーション
headers: vine.object({
}),
// ルートパラメータのバリデーション
params: vine.object({
}),
})
)
await request.validateUsing(validator)

バリデータへのメタデータの渡し方

バリデータはリクエストライフサイクルの外部で定義されているため、バリデータはリクエストデータに直接アクセスできません。これは通常良いことですが、ランタイムデータにアクセスする必要がある場合は、validateメソッド呼び出し中にメタデータとして渡す必要があります。

ただし、バリデータがランタイムデータにアクセスする必要がある場合は、validateメソッド呼び出し中にメタデータとして渡す必要があります。

uniqueバリデーションルールの例を見てみましょう。データベース内でユーザーのメールアドレスが一意であることを確認したいが、現在ログインしているユーザーの行はスキップしたい場合です。

export const updateUserValidator = vine
.compile(
vine.object({
email: vine.string().unique(async (db, value, field) => {
const user = await db
.from('users')
.whereNot('id', field.meta.userId)
.where('email', value)
.first()
return !user
})
})
)

上記の例では、meta.userIdプロパティを介して現在ログインしているユーザーにアクセスしています。HTTPリクエストでuserIdを渡す方法を見てみましょう。

async update({ request, auth }: HttpContext) {
await request.validateUsing(
updateUserValidator,
{
meta: {
userId: auth.user!.id
}
}
)
}

メタデータの型安全性

前の例では、バリデーション時にmeta.userIdを渡すことを覚えておく必要があります。TypeScriptにそれを思い出させてもらえると素晴らしいです。

次の例では、vine.withMetaData関数を使用して、スキーマで使用するメタデータの静的な型を定義しています。

export const updateUserValidator = vine
.withMetaData<{ userId: number }>()
.compile(
vine.object({
email: vine.string().unique(async (db, value, field) => {
const user = await db
.from('users')
.whereNot('id', field.meta.userId)
.where('email', value)
.first()
return !user
})
})
)

注意してください、VineJSはランタイムでメタデータをバリデーションしません。ただし、それを行いたい場合は、withMetaDataメソッドにコールバックを渡し、手動でバリデーションを行うことができます。

vine.withMetaData<{ userId: number }>((meta) => {
// メタデータをバリデーションする
})

VineJSの設定

startディレクトリ内にプリロードファイルを作成して、VineJSをカスタムエラーメッセージで設定するか、カスタムエラーレポーターを使用できます。

node ace make:preload validator

次の例では、カスタムエラーメッセージを定義しています。

start/validator.ts
import vine, { SimpleMessagesProvider } from '@vinejs/vine'
vine.messagesProvider = new SimpleMessagesProvider({
// すべてのフィールドに適用される
'required': '{{ field }}フィールドは必須です',
// すべてのフィールドに適用される
'string': '{{ field }}フィールドの値は文字列である必要があります',
// すべてのフィールドに適用される
'email': '有効なメールアドレスではありません',
// usernameフィールドのエラーメッセージ
'username.required': 'アカウントのユーザー名を選択してください',
})

以下の例では、カスタムエラーレポーターを登録しています。

start/validator.ts
import vine, { SimpleMessagesProvider } from '@vinejs/vine'
import { JSONAPIErrorReporter } from '../app/validation_reporters.js'
vine.errorReporter = () => new JSONAPIErrorReporter()

AdonisJSによって提供されるルール

以下は、AdonisJSによって提供されるVineJSルールのリストです。

  • AdonisJSのコアパッケージによって追加されたvine.fileスキーマタイプ。

次は何ですか?