🍮

【Next.js】Route Handlersを使用する際にzodを組み込んでみる

2024/01/10に公開

概要

Next.jsのAPIでzodを使用する場合の実装例としてはNext.js API Routes に Zod を組み込むが参考になると思います。Next.js13.2においてAPIでRoute Handlersの機能が実装されました、機能の概要についてはNext.js 13のRoute Handlersに移行したぞ!の記事をご参照ください。
このRoute Handlersにzodを組み込む実装をしてみたので、今回メモ書きします。

前提

  • 今回使用した使用したNext.jsのバージョンは14.0.4です。

実装で留意した点

NextRequestの仕様が変更になっていて、GETではURLSearchParamsを介してパラメータの値取得が必要となります。なのでzodのsafeParseを通すためには一度、objectの形式にしてあげる必要があります。その実装としてはa way to parse URLSearchParams with Zodの記事が参考になります。

実装サンプル

以下はzodのパース・チェック部分の実装です。

import { NextRequest, NextResponse } from "next/server";
import { z, ZodSchema } from "zod";

function searchParamsToValues(
  searchParams: URLSearchParams
): Record<string, any> {
  return Array.from(searchParams.keys()).reduce((record, key) => {
    const values = searchParams.getAll(key);
    return { ...record, [key]: values.length > 1 ? values : values[0] };
  }, {} as Record<string, any>);
}

function makeSearchParamsObjSchema<T extends ZodSchema>(schema: T) {
  return z
    .instanceof(URLSearchParams)
    .transform(searchParamsToValues)
    .pipe(schema);
}

export async function withZod<T extends ZodSchema>(
  schema: T,
  req: NextRequest,
  next: (reqValue: z.infer<T>) => Promise<NextResponse>
) {
  const parsed =
    req.method == "POST"
      ? schema.safeParse(await req.json())
      : makeSearchParamsObjSchema(schema).safeParse(req.nextUrl.searchParams);
  if (!parsed.success) {
    // 共通のバリデーションエラーレスポンスとして処理
    return NextResponse.json({ error: parsed.error.message }, { status: 400 });
  }
  return await next(parsed.data as z.infer<T>);
}

以下が呼び出し部分の実装です。

import { z } from "zod";
import { NextRequest, NextResponse } from "next/server";

export const addSampleUserPostSchema = z.object({
  id: z.string().min(1, {
    message: "ユーザIDは必須です",
  }),
  name: z.string().min(1, {
    message: "ユーザ名は必須です",
  }),
});

export type AddSampleUserPostRequest = z.infer<typeof addSampleUserPostSchema>;

export const addSampleUserPostHandler = async (req: NextRequest) => {
  return withZod(
    addSampleUserPostSchema,
    req,
    async (reqValue: AddSampleUserPostRequest) => {
      // 実処理の内容は記載割愛
      ・
      ・
      ・
      return NextResponse.json({}, { status: 200 });
    }
  );
};

Discussion