📄

Next.js API 内でファイルを取得する方法のメモ

2024/12/23に公開

Next.js app router でプロトタイプのアプリを作っています。
DBからデータを取得して返す部分を Next.js の API でモック的に作っており、API 内でダミーのデータファイルを取得してフロントに返す実装をしたメモ。

/root
  |-/public
  |  |-/data
  |     |-dummyData.json #ダミーのデータファイル
  |-/src
     |-/api
        |-/users
           |-route.ts

1. fs でファイルあを取得して返す

/src/api/users/route.ts
import path from "path";
import fs from "fs";
import { NextRequest } from "next/server";

export const GET = async (req: NextRequest) => {
  try {
    const filePath = path.join(process.cwd(), "/public/data/dummyData.json");
    const file = JSON.parse((fs.readFileSync(filePath, "utf8"));
    return Response.json({data: file}, {status: 200});
  } catch(error) {
    console.log('Error: Failed to read file', error);
    return Response.json({error: "Failed to read file"}, {status: 500});
  }
};

process.cwd() で node コマンドが実行されている root のディレクトリが取得できるので、それを起点に path.join でファイルへのパスを作り、fs.readFileSync でファイルを取得する方法。
fs.readFileSync はただのテキストファイルになるので JSON.parse で JSON に変換してから返した

process.cwd()
The process.cwd() method returns the current working directory of the Node.js process.
https://nodejs.org/api/process.html#processcwd

fs.readFileSync(path[, options])
For detailed information, see the documentation of the asynchronous version of this API: fs.readFile().
If the encoding option is specified then this function returns a string. Otherwise it returns a buffer.
https://nodejs.org/api/fs.html#fsreadfilesyncpath-options

2. API 内から fetch する

.env
NEXT_PUBLIC_API_BASE_URL=http://localhost:3000
/src/api/users/route.ts
import path from "path";
import { NextRequest } from "next/server";

const baseURL = process.env.NEXT_PUBLIC_API_BASE_URL ?? "http://localhost:3000";

export const GET = async (req: NextRequest) => {
  try {
    const json = await fetch(
      path.join(baseURL, "/public/data/dummyData.json")
    ).then((res) => {
      if (res.status !== 200) {
        throw new Error("Fetch Error");
      }
      return res.json();
    });
    return Response.json({data: json}, {status: 200});
  } catch(error) {
    console.log('Error: Failed to fetch file', error);
    return Response.json({error: "Failed to fetch file"}, {status: 500});
  }
};

API 内から fetch する際に / 始まりで fetch できないので、アプリの protocl と host が必要になる。
プロトタイプなら .env に定義してしまうのが楽。

req から protocl と host を取得することもできた

/src/api/users/route.ts
import path from "path";
import { NextRequest } from "next/server";

const buildBasePath = (req: Request, fallbackHost: string = "localhost:3000") => {
  const protocl = req.headers.get("x-forwarded-proto") ?? "http";
  const host = req.headers.get("host") ?? fallbackHost;
  return `${protocol}://${host}`;
};

export const GET = async (req: NextRequest) => {
  try {
  const baseURL = buildBasePath(req);
  const json = await fetch(
   path.join(baseURL, "/public/data/dummyData.json")
  ).then((res) => res.json());
  return Response.json({data: json}, {status: 200});
  } catch (error) {
  // 略
  }
};

他にも req.url が API 自身の URL になっているので、不要なパスを replace で消してしまうのも楽かもしれない

X-Forwarded-Proto
X-Forwarded-Proto (XFP) ヘッダーは、プロキシーまたはロードバランサーへ接続するのに使っていたクライアントのプロトコル (HTTP または HTTPS) を特定するために事実上の標準となっているヘッダーです。サーバーのアクセスログにはサーバーとロードバランサーの間で使われたプロトコルが含まれていますが、クライアントとロードバランサーの間で使用されたプロトコルは含まれていません。クライアントとロードバランサーの間で使用されたプロトコルを特定するには、 X-Forwarded-Proto リクエストヘッダーを使用することができます。
https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/X-Forwarded-Proto

追記 nextUrl から origin を取得する方法が良さそう

https://nextjs.org/docs/app/api-reference/functions/next-request#nexturl

/src/api/users/route.ts
export const GET = async (req: NextRequest) => {
  try {
  const baseURL = req.nextUrl.origin;
  const json = await fetch(
   path.join(baseURL, "/public/data/dummyData.json")
  ).then((res) => res.json());
  return Response.json({data: json}, {status: 200});
  } catch (error) {
  // 略
  }
};

nextUrl
Extends the native URL API with additional convenience methods, including Next.js specific properties.
https://nextjs.org/docs/app/api-reference/functions/next-request#nexturl

3. json なら require で取得できる

そもそも JSON ファイルなら const json = require("/path/to/file.json") で取得できる

/src/api/users/route.ts
export const GET = async (req: NextRequest) => {
  try {
  const data = require("../../../../public/data/dummyData.json");
  return Response.json({ data }, {status: 200});
  } catch (error) {
  console.log('Error: Failed to read file', error);
  return Response.json({error: "Failed to read file"}, {status: 500});
  }
};

この方法なら /public にデータを置いておく必要もない。

ただし取得したいデータが .geojson など設定外の拡張子の場合はエラーになるので next.config.ts などで設定する必要がある

おわり

Discussion