😎

Standard Schema とは何か?その使用例と実装方法

2025/02/09に公開

Standard Schemaとは何か

Standard Schemaは、TypeScriptのバリデータライブラリ(ZodValibotArkTypeなど)に共通のインターフェースを提供する仕様です。これにより、各バリデータごとに異なる処理を書く必要がなくなり、一貫した方法でバリデーションを行えるようになります。Zod, Valibot, ArkTypeの作成者の方達が仕様を設計しています。

https://standardschema.dev/

何が嬉しいのか?

さまざまなバリデータライブラリをサポートするためには、それぞれに対応したカスタムロジックやアダプターを書く必要がありました。
わかりやすい例として、React Hook Form(RHF)resolverがあります。

RHFは、フォームバリデーションのための人気フレームワークであり、resolverはRHFと各種バリデータライブラリを連携させるための仕組みです。従来は、Zod用のZodResolver、Valibot用のValibotResolverなど、ライブラリごとに異なるresolverを用意しメンテナンスする必要がありました。しかし、Standard Schemaに準拠したスキーマであれば、単一のインターフェース(~standard.validate())を用いることができるので、共通のresolverを作成し、利用することができます。

RHFではすでにStandard Schema対応のresolverがマージされているようです。ありがとうございます!

スキーマにZod, resolverにzodResolverを使用した例
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';

const schema = z.object({
  name: z.string().min(1, { message: 'Required' }),
  age: z.number().min(10),
});

const App = () => {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm({
    resolver: zodResolver(schema),
  });

  return (
    <form onSubmit={handleSubmit((d) => console.log(d))}>
      <input {...register('name')} />
      {errors.name?.message && <p>{errors.name?.message}</p>}
      <input type="number" {...register('age', { valueAsNumber: true })} />
      {errors.age?.message && <p>{errors.age?.message}</p>}
      <input type="submit" />
    </form>
  );
};

https://github.com/react-hook-form/resolvers?tab=readme-ov-file#zod

スキーマにZod, resolverにstandardSchemaResolverを使用した例
import { useForm } from 'react-hook-form';
import { standardSchemaResolver } from '@hookform/resolvers/standard-schema';
import { z } from 'zod';

const schema = z.object({
  name: z.string().min(1, { message: 'Required' }),
  age: z.number().min(10),
});

const App = () => {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm({
    resolver: standardSchemaResolver(schema),
  });

  return (
    <form onSubmit={handleSubmit((d) => console.log(d))}>
      <input {...register('name')} />
      {errors.name?.message && <p>{errors.name?.message}</p>}
      <input type="number" {...register('age', { valueAsNumber: true })} />
      {errors.age?.message && <p>{errors.age?.message}</p>}
      <input type="submit" />
    </form>
  );
};

https://github.com/react-hook-form/resolvers?tab=readme-ov-file#standard-schema

HonoでのStandard Schemaの使用例

Honoのv4.7.0のリリースにて Standard Schema Validator が追加されました。
これにより、Honoを利用する開発者は、Zod、ValibotなどのStandard Schemaに対応したバリデーションライブラリで実装されたユーザー定義のスキーマをStandard Schema Validatorで統一的に扱えるようになります。

以下のコード例にあるように、ArkType, Valibot, ZodのスキーマをStandard Schema Validatorで統一的に扱えるようになります。(コードはHono v4.7.0 releaseより拝借)

import { Hono } from 'hono'
import { sValidator } from '@hono/standard-validator'
import { type } from 'arktype'
import * as v from 'valibot'
import { z } from 'zod'

const aSchema = type({
  agent: 'string',
})

const vSchema = v.object({
  slag: v.string(),
})

const zSchema = z.object({
  name: z.string(),
})

const app = new Hono()

app.get(
  '/:slag',
  sValidator('header', aSchema),
  sValidator('param', vSchema),
  sValidator('query', zSchema),
  (c) => {
    const headerValue = c.req.valid('header')
    const paramValue = c.req.valid('param')
    const queryValue = c.req.valid('query')
    return c.json({ headerValue, paramValue, queryValue })
  }
)

const res = await app.request('/foo?name=foo', {
  headers: {
    agent: 'foo',
  },
})

console.log(await res.json())

どのように実装されているのか?

Standard SchemaのREADMEに仕様の記載がありますが

Standard Schemaの仕様を満たすバリデーションライブラリは、~standard.validate()関数を実装する必要があります。
なので、今回紹介したHonoのStandard Schema ValidatorやRHFのStandard Schema Resolverは、それぞれのバリデーションライブラリの~standard.validate()関数を呼び出すことでユーザー定義のスキーマを検証することができています。

https://github.com/honojs/middleware/blob/4f927dfaa8ce79a2ce02485a817e775cc1a82d47/packages/standard-validator/src/index.ts#L53

https://github.com/apple-yagi/resolvers/blob/b50e30a8270ecb5ecb383af4d9e0545622673eab/standard-schema/src/standard-schema.ts#L25

おわりに

Standard Schemaは、TypeScriptのバリデータライブラリに共通のインターフェースを提供する仕様です。
これにより、各バリデータごとに異なる処理を書く必要がなくなり、一貫した方法でバリデーションを行えるようになることを紹介しました。

他のライブラリでも導入が進んでいるようなので気になる方は見てみてください。
https://github.com/standard-schema/standard-schema?tab=readme-ov-file#what-tools--frameworks-accept-spec-compliant-schemas

また、最近YouTubeを始めまして、そちらでも同様の内容を(拙い英語で)紹介しています。
今後も技術に関する内容を発信していく予定なので、よろしければチャンネル登録お願いします😄
https://www.youtube.com/watch?v=mgbHMgIdEhc

Discussion