VineJS入門 — Node.jsの高速バリデーションライブラリ


VineJSとは

VineJSは、AdonisJS開発チームが開発した高速バリデーションライブラリです。Zodなどの既存ライブラリと比較して約14倍の処理速度を誇り、TypeScriptの型安全性も完全にサポートしています。

主な特徴

  • 圧倒的な高速性: Zodと比較して14倍、Yupと比較して20倍高速
  • TypeScript完全対応: スキーマから型を自動推論
  • カスタマイズ可能: カスタムルール、カスタムメッセージ、多言語対応
  • エラーハンドリング: 詳細なエラー情報を提供
  • フレームワーク連携: AdonisJS、Honoなどで簡単に統合

インストール

npm install @vinejs/vine

TypeScriptプロジェクトでは追加の設定は不要です。

基本的な使い方

シンプルなスキーマ定義

import vine from '@vinejs/vine'

// スキーマ定義
const schema = vine.object({
  email: vine.string().email(),
  password: vine.string().minLength(8),
  age: vine.number().min(18).optional(),
})

// バリデーション実行
const data = {
  email: 'user@example.com',
  password: 'securepass123',
}

try {
  const validated = await vine.validate({ schema, data })
  console.log('検証成功:', validated)
} catch (error) {
  console.error('検証エラー:', error.messages)
}

型推論

VineJSはスキーマから自動的に型を推論します。

import vine, { Infer } from '@vinejs/vine'

const userSchema = vine.object({
  name: vine.string(),
  email: vine.string().email(),
  isActive: vine.boolean(),
})

// 型を自動推論
type User = Infer<typeof userSchema>
// => { name: string; email: string; isActive: boolean }

const user: User = {
  name: 'John Doe',
  email: 'john@example.com',
  isActive: true,
}

高度なバリデーション

ネストしたオブジェクト

const profileSchema = vine.object({
  user: vine.object({
    name: vine.string(),
    email: vine.string().email(),
  }),
  address: vine.object({
    street: vine.string(),
    city: vine.string(),
    zipCode: vine.string().regex(/^\d{3}-\d{4}$/),
  }),
})

配列のバリデーション

const ordersSchema = vine.object({
  userId: vine.string(),
  items: vine.array(
    vine.object({
      productId: vine.string(),
      quantity: vine.number().min(1),
      price: vine.number().positive(),
    })
  ).minLength(1),
})

条件付きバリデーション

const paymentSchema = vine.object({
  method: vine.enum(['credit_card', 'bank_transfer']),
  cardNumber: vine.string().optional(),
  bankAccount: vine.string().optional(),
}).when('method', {
  is: 'credit_card',
  then: (schema) => schema.merge(vine.object({
    cardNumber: vine.string().regex(/^\d{16}$/),
  })),
  otherwise: (schema) => schema.merge(vine.object({
    bankAccount: vine.string(),
  })),
})

カスタムバリデーションルール

独自のバリデーションロジックを追加できます。

import { VineString } from '@vinejs/vine'

// カスタムルール: 特定のドメインのみ許可
VineString.macro('allowedDomain', function (domains: string[]) {
  return this.use((value, field, { errorReporter }) => {
    const emailDomain = value.split('@')[1]

    if (!domains.includes(emailDomain)) {
      errorReporter.report(
        field,
        'allowedDomain',
        `Email must be from: ${domains.join(', ')}`
      )
    }
  })
})

// 使用例
const schema = vine.object({
  email: vine.string().email().allowedDomain(['company.com', 'partner.com']),
})

カスタムエラーメッセージ

const messages = {
  'email.required': 'メールアドレスは必須です',
  'email.email': '有効なメールアドレスを入力してください',
  'password.minLength': 'パスワードは8文字以上必要です',
}

const validated = await vine.validate({
  schema,
  data,
  messages,
})

多言語対応

const jaMessages = {
  'required': '{{ field }}は必須です',
  'email': '有効なメールアドレスを入力してください',
  'minLength': '{{ field }}は{{ min }}文字以上必要です',
}

const enMessages = {
  'required': 'The {{ field }} field is required',
  'email': 'Please enter a valid email address',
  'minLength': 'The {{ field }} must be at least {{ min }} characters',
}

// 言語に応じて切り替え
const messages = locale === 'ja' ? jaMessages : enMessages

AdonisJSとの統合

AdonisJSではVineJSがデフォルトのバリデーターとして統合されています。

// app/validators/user_validator.ts
import vine from '@vinejs/vine'

export const createUserValidator = vine.compile(
  vine.object({
    email: vine.string().email().unique({ table: 'users', column: 'email' }),
    password: vine.string().minLength(8),
    name: vine.string().trim(),
  })
)

// app/controllers/users_controller.ts
import { createUserValidator } from '#validators/user_validator'

export default class UsersController {
  async store({ request, response }: HttpContext) {
    const data = await request.validate(createUserValidator)

    const user = await User.create(data)
    return response.created(user)
  }
}

Honoとの統合

HonoフレームワークでもVineJSを簡単に統合できます。

import { Hono } from 'hono'
import vine from '@vinejs/vine'

const app = new Hono()

const userSchema = vine.object({
  name: vine.string(),
  email: vine.string().email(),
})

app.post('/users', async (c) => {
  const body = await c.req.json()

  try {
    const validated = await vine.validate({ schema: userSchema, data: body })

    // ユーザー作成処理
    return c.json({ success: true, user: validated })
  } catch (error) {
    return c.json({ success: false, errors: error.messages }, 400)
  }
})

export default app

パフォーマンス比較

実際のベンチマーク結果(10,000回のバリデーション実行):

ライブラリ実行時間Zodとの比較
VineJS45ms14倍高速
Zod630ms基準
Yup910ms1.4倍遅い
Joi1,120ms1.8倍遅い

VineJSは大量のバリデーションが必要なAPIサーバーやバッチ処理で特に威力を発揮します。

エラーハンドリング

VineJSは詳細なエラー情報を提供します。

try {
  const validated = await vine.validate({ schema, data })
} catch (error) {
  if (error instanceof errors.E_VALIDATION_ERROR) {
    // フィールドごとのエラー
    console.log(error.messages)
    // [
    //   { field: 'email', message: 'The email field must be a valid email', rule: 'email' },
    //   { field: 'password', message: 'The password must be at least 8 characters', rule: 'minLength' }
    // ]

    // フラット形式
    console.log(error.getFlat())
    // {
    //   email: ['The email field must be a valid email'],
    //   password: ['The password must be at least 8 characters']
    // }
  }
}

まとめ

VineJSは以下のような場面で特におすすめです。

  • パフォーマンスが重要なAPI: 高速なバリデーションでレスポンス時間を短縮
  • AdonisJSプロジェクト: フレームワークとのシームレスな統合
  • 大規模バッチ処理: 大量データの検証を高速に処理
  • TypeScript環境: 完全な型安全性

既存のZodやYupからの移行も、同様のAPIデザインにより比較的容易です。次回のプロジェクトでぜひ試してみてください。

参考リンク