🚀

Remix × Zodix: パラメータやクエリのバリデーションをシンプルに書く

2024/03/21に公開

これは何?

Remix でアプリケーションを開発する際、route でのパラメータやクエリのバリデーションは必須です。通常、このバリデーションは手動で行う必要がありますが、zodzodix というモジュールを使うことでとてもシンプルに書くことができます。

たとえば、URL パラメータのバリデーションと値の取り出しでは以下のように定型句が多く煩雑になりがちですが、zodix はそこで威力を発揮します。

例:
https://remix-app/query?count=100&page=1 のバリデーションを行う方法

zodix がない場合

export async function loader({ request }: LoaderFunctionArgs) {
  const { searchParams } = new URL(request.url);
  const count = Number(searchParams.get("count"));
  const page = Number(searchParams.get("page"));
  if(isNaN(count) || isNaN(page)) {
    throw new Response("invalid query", { status: 400 })
  }

zodix を使う場合

export async function loader({ request }: LoaderFunctionArgs) {
  const { count, page } = zx.parseQuery(request, {
    count: zx.NumAsString, // 100
    page: zx.NumAsString, // 1
  });
}

この記事では、zodix の基本的な使い方と、Remix アプリケーションでの活用方法について解説します。

zodix とは

zodix は、Remix の loader や action でパラメータやクエリのバリデーションを簡単に行うための Zod ユーティリティのコレクションです。FormDataURLSearchParams のパースとバリデーションをシンプルにし、loader や action をクリーンで型安全に保ちます。

https://github.com/rileytomasek/zodix

セットアップ

まず、zodixzod をインストールします。

pnpm install zodix zod

次に、基本になる zx オブジェクトと、zod のスキーマ定義のための z をインポートします。

import { zx } from "zodix";
import { z } from "zod";

使い方

route パラメータのバリデーション

zx.parseParams を使って、LoaderFunctionArgs['params']ActionFunctionArgs['params'] から params オブジェクトをパースしバリデーションできます。

// route: users.$userId.notes.$noteId.tsx
// URL: https://remix-app/users/coji/notes/y8XparQV
export async function loader({ params }: LoaderFunctionArgs) {
  const { userId, noteId } = zx.parseParams(params, {
    userId: z.string(), // "coji"
    noteId: z.string(), // "y8XparQV"
  });
}

クエリ文字列 URLSearchParams のバリデーション

zx.parseQuery を使って、Request のクエリ文字列(検索パラメータ)をパースしバリデーションできます。

// URL https://remix-app/users?count=100&page=1
export async function loader({ request }: LoaderFunctionArgs) {
  const { count, page } = zx.parseQuery(request, {
    count: zx.NumAsString, // 100
    page: zx.NumAsString, // 1
  });
}

zx.NumAsString は zodix が提供するヘルパー zod スキーマです。詳細は後述します。

フォームデータのバリデーション

zx.parseForm を使って、Remix の actionFormData をパースしバリデーションできます。

export async function action({ request }: ActionFunctionArgs) {
  const { email, password, saveSession } = await zx.parseForm(request, {
    email: z.string().email(),
    password: z.string().min(6),
    saveSession: zx.CheckboxAsString,
  });
}

ヘルパー Zod スキーマ

FormDataURLSearchParams は全ての値を文字列にシリアライズするため、"5"、"on"、"true" のような値を扱うことがよくあります。zodix には、これらの文字列を適切な型にパースしバリデーションするためのヘルパースキーマが用意されています。

  • zx.BoolAsString: "true" を true に、"false" を false にパースします。
  • zx.CheckboxAsString: "on" を true に、undefinedfalse にパースします。
  • zx.IntAsString: "3" を 3 にパースします。小数点以下は切り捨てられます。
  • zx.NumAsString: "3" を 3 に、"3.14" を 3.14 にパースします。

これらのヘルパースキーマを使用すると、次のようにフォームデータやクエリ文字列をパースできます。

const Schema = z.object({
  isAdmin: zx.BoolAsString,
  agreedToTerms: zx.CheckboxAsString,
  age: zx.IntAsString,
  cost: zx.NumAsString,
});

export async function action({ request }: ActionFunctionArgs) {
  const { isAdmin, agreedToTerms, age, cost } = await zx.parseForm(
    request,
    Schema
  );
  // isAdmin: boolean
  // agreedToTerms: boolean
  // age: number
  // cost: number
}

export async function loader({ request }: LoaderFunctionArgs) {
  const { isAdmin, agreedToTerms, age, cost } = zx.parseQuery(request, Schema);
  // isAdmin: boolean
  // agreedToTerms: boolean
  // age: number
  // cost: number
}

この例では、zx.BoolAsStringzx.CheckboxAsStringzx.IntAsStringzx.NumAsString を使用して、フォームデータとクエリ文字列の値を適切な型にパースしています。これにより、action 関数と loader 関数内で、変換された値を型安全に使用できます。

エラーハンドリング

parseParamsparseFormparseQuery は、パースに失敗した場合に 400 Response をスローします。これは Remix の error boundary とうまく連動し、カスタムエラーハンドリングを必要としない場合に使用するのに適しています。

export async function loader({ params }: LoaderFunctionArgs) {
  const { postId } = zx.parseParams(
    params,
    { postId: zx.NumAsString },
    { message: "Invalid postId parameter", status: 400 }
  );
  const post = await getPost(postId);
  return { post };
}

export function ErrorBoundary() {
  const error = useRouteError();
  return (
    <div>
      <h1>{String(error)}</h1>
    </div>
  );
}

実際の使用例 (SPA Mode)

zodix は 通常の SSR を行う Remix だけでなく、SPA mode でも使うことができます。
たとえば以下のように、Remix SPA のアプリで実際に使っています。

https://github.com/coji/remix-spa-example/blob/main/app/routes/%24handle%2B/posts.%24id.edit.tsx#L36-L52

上記コードを含むリポジトリはこちらです。参考になるとうれしいです。

https://github.com/coji/remix-spa-example

まとめ

zodix を使うことで、Remix アプリケーションでのパラメータやクエリのバリデーションを簡単に実装できます。zodix は、定義済みの Zod スキーマや独自のスキーマを使用でき、エラーハンドリングも容易です。ぜひ zodix を活用して、Remix アプリケーションの開発を効率化しましょう。

GitHubで編集を提案

Discussion