precognition
nuxt-precognition

Laravel Precognition プロトコルを Nitro で実装する Nuxt モジュール

Nuxt Precognition

npm versionnpm downloadsLicenseNuxt

Nuxt Precognition は、Precognition プロトコルをバックエンドに依存しない方法で実装する Nuxt 用のバリデーションモジュールです。任意のバックエンドまたはバリデーションライブラリをサポートしており、Laravel に限定されません。

目次

要件

  • Nuxt >= 3.x
  • Node.js >= 18

Nuxt Precognition を使う理由

  • バックエンドに依存しない: Precognition プロトコルをサポートするあらゆるバックエンドで動作します。
  • バリデーションライブラリに依存しない: Zod、Yup、その他のバリデーションライブラリを使用できます。
  • クライアント & サーバーサイドバリデーション: 両端でシームレスなバリデーション。
  • 最適な TypeScript サポート: 型安全なフォームとエラーハンドリング。
  • 高度にカスタマイズ可能: 独自のエラーパーサーとステータスハンドラーをプラグインできます。

簡単な例

interface User {
  email: string
  password: string
}

const form = useForm(
  (): User => ({ email: '', password: '' }),
  (body, headers) => $fetch('/api/login', { method: 'POST', headers, body })
)

機能

  • Laravel 準拠
  • バリデーションライブラリに依存しない
  • クライアントおよびサーバーサイドバリデーション
  • TypeScript サポート
  • カスタマイズ可能なエラー解析とステータスハンドリング

インストール

Nuxt アプリにモジュールをインストール

npx nuxi module add nuxt-precognition

仕組み

核となるコンセプトはエラーパーサーです。これは、スローされたエラーからバリデーションエラーを抽出する関数です。

type ValidationErrors = Record<string, string | string[]>

interface ValidationErrorsData {
  message: string
  errors: ValidationErrors
}

type ValidationErrorParser = (error: Error) => ValidationErrorsData | undefined | null

Zod エラーパーサーの定義

// app/utils/precognition.ts or shared/utils/precognition.ts
import { ZodError } from 'zod'

export const zodPrecognitionErrorParser: ValidationErrorParser = (error) => {
  if (error instanceof ZodError) {
    const errors: Record<string, string[]> = {}
    for (const issue of error.issues) {
      const key = issue.path.join('.')
      if (key in errors) {
        errors[key].push(issue.message)
        continue
      }
      errors[key] = [issue.message]
    }
    return { errors, message: error.message }
  }
  return null
}

注記
サーバーサイドバリデーションの場合、このファイルを shared/utils フォルダに配置してください。

クライアントサイドバリデーション

パーサーをクライアント側でグローバルに追加します。

// app/plugins/precognition.ts
export default defineNuxtPlugin(() => {
  const { $precognition } = useNuxtApp()

  $precognition.errorParsers.push(zodErrorParser)

  // ..
})

セットアップメソッドでコンポーザブルを使用します。

const UserSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
})

const form = useForm(
  (): z.infer<typeof UserSchema> => ({
    email: '',
    password: '',
  }),
  (body, headers) => $fetch('/api/login', {
    method: 'POST',
    headers,
    body,
  }),
  {
    clientValidation(data) {
      UserSchema.parse(data)
    },
  },
)

function login() {
  form.submit()
}

function reset() {
  form.reset()
  document.getElementById('email')?.focus()
}
<form
  @submit.prevent="login"
  @reset.prevent="reset"
>
  <div>
    <label for="email">Email address</label>
    <input
      id="email"
      v-model="form.email"
      name="email"
      type="email"
      @change="form.validate('email')"
    >
    <span v-if="form.valid('email')">OK!!</span>
    <span v-if="form.invalid('email')">{{ form.errors.email }}</span>
  </div>

  <div>
    <label for="password">Password</label>
    <input
      id="password"
      v-model="form.password"
      name="password"
      type="password"
      autocomplete="current-password"
      required
      @change="form.validate('password')"
    >
    <span v-if="form.valid('password')">OK!!</span>
    <span v-if="form.invalid('password')">{{ form.errors.password }}</span>
  </div>

  <div>
    <button type="submit">Sign in</button>
    <button type="reset">Reset</button>
  </div>
</form>

サーバーサイドバリデーション

  1. デフォルト設定を更新します。
// nuxt.config.ts

export default defineNuxtConfig({
  modules: [
    'nuxt-precognition'
  ],
  precognition: {
    backendValidation: true, 
    enableNuxtClientErrorParser: true,
  },
})
  1. サーバーエラーを解析するための Nitro プラグインを作成します
// server/plugins/precognition.ts
import { ZodError } from 'zod'

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('request', (event) => {
    event.context.$precognition.errorParsers = [
      zodErrorParser
    ]
  })
})
  1. definePrecognitiveEventHandler を「オブジェクト」形式で使用し、onRequest フックにバリデーションを追加します。
// server/api/login.post.ts
import { z } from 'zod'
import { definePrecognitiveEventHandler, readBody } from '#imports'

const loginSchema = z.object({
  email: z.string().email(),
  password: z.string()
}).refine((_data) => {
  // Check for email and password match
  // ...
  return true
},
{ message: 'invalid credentials', path: ['email'] },
)

export default definePrecognitiveEventHandler({
  async onRequest(event) {
    const body = await readBody(event)
    loginSchema.parse(body)
  },
  handler: () => {
    return {
      status: 200,
      body: {
        message: 'Success',
      },
    }
  },
})

Precognition プロトコル

Nitro の外部で独自のバックエンドロジックを定義する必要がある場合は、以下の要件に従ってください。

  • Precognitive リクエストには以下が必要です。
    1. Precognitive ヘッダー { 'Precognitive': 'true' }
  • 特定の変数をバリデーションするには、各キーを ValidateOnly ヘッダー内にカンマ区切りで指定し、ドット表記を使用する必要があります { 'Precognition-Validate-Only': 'name,age,address.street,address.number' }
  • フォーム全体を検証するには、ValidateOnly ヘッダーを省略するか、空の文字列として定義する必要があります。
  • バリデーション成功レスポンスには以下が必要です。
    1. Precognitive ヘッダー { 'Precognitive': 'true' }
    2. Precognitive 成功ヘッダー { 'Precognition-Success': 'true' }
    3. Precognitive 成功ステータスコード: 204
  • エラーバリデーションレスポンスには以下が必要です。
    1. Precognitive ヘッダー { 'Precognitive': 'true' }
    2. 必要に応じて Precognition-Validate-Only ヘッダー { 'Precognition-Validate-Only': 'name,age,address.street,address.number' }
    3. バリデーションエラーのステータスコード: 422
    4. バリデーションエラーとメッセージは、定義されたロジックに従って、または標準の errorParsers を使用して解析されます。
      • NuxtErrorParsers: NuxtPrecognitiveErrorResponse: Response & { _data: { data: ValidationErrorsData }}
      • LaravelErrorParsers: LaravelPrecognitiveErrorResponse: Response & { _data: ValidationErrorsData }

設定

nuxt.config.ts に追加してください

export default defineNuxtConfig({
  modules: ['nuxt-precognition'],
  precognition: {
    backendValidation: true,
    enableNuxtClientErrorParser: true,
    // ...other options
  }
})

オプション

nameタイプdefault説明
validationTimeoutnumber15002つの先行バリデーションリクエスト間のデバウンス時間(ミリ秒)。
backendValidationブール値false先行バリデーションを有効にするフラグ。
validateFilesブール値false先行リクエストでファイルのバリデーションを有効にするフラグ。
enableNuxtClientErrorParserブール値falseクライアントサイド(form.validate および form.submit)で *nuxtErrorParsers* を有効にするフラグ。
enableLaravelClientErrorParserブール値falseクライアントサイド(form.validate および form.submit)で *laravelErrorParsers* を有効にするフラグ。
enableLaravelServerErrorParserブール値falseサーバーサイド(definePrecognitiveEventHandler)で *laravelErrorParsers* を有効にするフラグ。

ステータスハンドラー

公式パッケージと同様に、特定のコードエラーに対するカスタムハンドラをグローバルまたはインスタンスレベルで定義できます。

// plugins/precognition.ts

export default defineNuxtPlugin(() => {
  const { $precognition } = useNuxtApp()

  $precognition.statusHandlers = {
    401: async (error, form) => {
      form.error = createError('Unauthorized')
      await navigateTo('/login')
    },
    403: async (error, form) => {
      form.error = createError('Forbidden')
    },
  }
})

Laravel 連携

Laravel を使用する場合、Nuxt Nitro 統合は不要です。

  1. バックエンドバリデーションとエラーパーサーを有効にする
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['nuxt-precognition'],
  precognition: {
    backendValidation: true,
    enableLaravelClientErrorParser: true,
  }
})
  1. プラグインの例

Sanctum トークンのプリフェッチを追加し、すべての事前認知リクエストを適切に処理するようにします。

// plugins/laravel.ts
export default defineNuxtPlugin((app) => {
  const { $precognition } = useNuxtApp()
  const token = useCookie('XSRF-TOKEN')

  const api = $fetch.create({
    baseURL: 'https://',
    credentials: 'include',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json',
    },
    onRequest: ({ options }) => {
      if (token.value) {
        const headers = new Headers(options.headers)
        headers.set('X-XSRF-TOKEN', token.value)
        options.headers = headers
      }
    },
    onResponse: (context) => {
      // ensure non false positive validations
      $precognition.assertSuccessfulPrecognitiveResponses(context)
    },
  })

  async function fetchSanctumToken() {
    try {
      await api('/sanctum/csrf-cookie')
      token.value = useCookie('XSRF-TOKEN').value
      if (!token.value) throw new Error('Failed to get CSRF token')
    } catch (e) {
      console.error(e)
    }
  }

  app.hook('app:mounted', fetchSanctumToken)

  return {
    provide: {
      api,
      sanctum: {
        fetchToken: fetchSanctumToken,
        token,
      },
    },
  }
})
  1. Laravel CORS 設定

Precognitive ヘッダーが Nuxt アプリケーションと共有されるようにします。

// config/cors.php
return [
  'paths' => ['*'],
  'allowed_methods' => ['*'],
  'allowed_origins' => ['*'],
  'allowed_origins_patterns' => [env('FRONTEND_URL', 'https://:3000')],
  'allowed_headers' => ['*'],
  'exposed_headers' => ['Precognition', 'Precognition-Success'],
  'max_age' => 0,
  'supports_credentials' => true,
];
  1. Precognition ミドルウェアを有効にする

必要に応じて Precognitive ミドルウェアを適用します。

// routes/api.php
Route::middleware('precognitive')->group(function () {
    Route::apiResource('posts', \App\Http\Controllers\PostController::class);
});

これで終わりです。Nuxt のバリデーションは Laravel と同期されます!!


コントリビューション

# Install dependencies
npm install

# Generate type stubs
npm run dev:prepare

# Develop with the playground
npm run dev

# Build the playground
npm run dev:build

# Run ESLint
npm run lint

# Run Vitest
npm run test
npm run test:watch

# Release new version
npm run release

ライセンス

MIT © sot1986