📌

Next.js Route handlers + Zodでスマートにクエリパラメータとリクエストボディを取得する

2024/12/01に公開

背景

こんにちは。Next.jsを使用してWebアプリ開発をしているけーじといいます。
Next.jsのRoute handlersを使用していると、リクエストボディやクエリパラメータのパースとバリデーションがめんどうですよね。とくにDateオブジェクトなんかがあると大変です。
毎度毎度面倒な処理を書くのはスマートではないので何か良い方法はないかなと思ったところ、Zodを利用して楽に共通化できそうだったので今回はそれを紹介しようと思います。

クエリパラメータの取得

コード

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

/** クエリパラメータをパースして返す関数 */
export const extractSearchParams = <TSchema extends Schema>(
  req: NextRequest,
  schema: TSchema
): Promise<z.infer<typeof schema> | null> => {
  const searchParams = req.nextUrl.searchParams;
  const queryParamKeys = Array.from(new Set(searchParams.keys()));

  // パース前リクエストボディを生成
  const reqBody: { [key: string]: unknown } = {};
  queryParamKeys.forEach((queryParamKey) => {
    if (queryParamKey.endsWith("[]")) {
      const key = queryParamKey.replace("[]", "");
      const value = searchParams.getAll(queryParamKey);
      reqBody[key] = value;
    } else {
      reqBody[queryParamKey] = searchParams.get(queryParamKey);
    }
  });

  // パース
  const parseResult = schema.safeParse(reqBody);
  if (parseResult.success) {
    return parseResult.data;
  } else {
    return null;
  }
};

使用例

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

const ReqBodySchema = z.object({
  reqBodyStr: z.coerce.string(),
  reqBodyNum: z.coerce.number(),
  reqBodyDate: z.coerce.date(),
  reqBodyNumArr: z.array(z.coerce.number()),
});

export async function GET(request: NextRequest) {
  const reqBody = await extractSearchParams(request, ReqBodySchema);
  return NextResponse.json(reqBody);
}

リクエストボディを取得

コード

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

/** リクエストbodyをパースして返す関数 */
export const extractReqBody = async <TSchema extends Schema>(
  req: NextRequest,
  schema: TSchema
): Promise<z.infer<typeof schema> | null> => {
  const reqJson = await req.json();
  const parseResult = schema.safeParse(reqJson);
  if (parseResult.success) {
    return parseResult.data;
  } else {
    return null;
  }
};

使用例

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

const ReqBodySchema = z.object({
  reqBodyStr: z.coerce.string(),
  reqBodyNum: z.coerce.number(),
  reqBodyDate: z.coerce.date(),
  reqBodyNumArr: z.array(z.coerce.number()),
});

export async function POST(request: NextRequest) {
  const reqBody = await extractReqBody(request, ReqBodySchema);
  return NextResponse.json(reqBody);
}

Discussion