logo

RouteHandlersを中間APIとして利用する

2023-10-14
a year ago

開発環境

  • next 13.4.10

前提

AppRouterを利用します。

本題

基本的なフロントエンド、バックエンドの構成ではフロントからバックエンドへ直接リクエストを送ることが多いと思います。

セキュリティ要件によりますが、リクエストヘッダーにトークンなど認証情報を外部に見せたくないこともあると思います。

そんな時の対応策の一つとしてNextサーバーを中間APIとして処理を挟む方法を紹介します。

処理の流れ

  1. RouteHandlersフロント→Nextサーバー へのエンドポイントを作成
  2. そのAPIでトークンやAPIキーなどの認証に関わる情報をセットする
  3. Nextサーバー→バックエンド へリクエストする

以上の流れになります。

この流れで実装すると、 フロント→Nextサーバー へのリクエスト時には認証に関わる情報を必要としないので外部に露出することがなくなります。

1. RouteHandlersフロント→Nextサーバー へのエンドポイントを作成

エンドポイントをAPIごとに作成しても良いですが、メソッドごとにまとめて定義するようにCatch-all Segmentsを利用します。

また、①フロントからNextサーバーへのリクエストと、②NextサーバーからバックエンドへのリクエストURLは同じように実装します。

例)

http://localhost:3000/api/proxy/hoge

http://localhost:3300/hoge

import { NextRequest } from 'next/server';

import { server } from '@/lib/http/server'; // バックエンドへのリクエスト用にbaseUrlなど少し調整したfetchAPI
import { removeProxyPrefix } from '@/utils/remove-proxy-prefix'; // URLの"/api/proxy"を取り除く

export async function GET(request: Request) {
  const requestUrl = removeProxyPrefix(request.url); // ここでURLを変換する
  return await server(requestUrl, {
    cache: 'no-store',
  });
}

export async function POST(request: NextRequest) {
  const requestBody = await request.json();
  const formData = JSON.stringify(requestBody);

  const requestUrl = removeProxyPrefix(request.url);

  return await server(requestUrl, {
    method: 'POST',
    body: formData,
  });
}
src/app/api/proxy/[...all]/route.ts

2. そのAPIでトークンやAPIキーなどの認証に関わる情報をセットする

認証情報を取得してヘッダーにセットするようにします。

import { NextRequest } from 'next/server';

import { server } from '@/lib/http/server';
import { getEncodedToken } from '@/utils/get-encoded-token'; // 認証情報取得関数を追加
import { removeProxyPrefix } from '@/utils/remove-proxy-prefix';

export async function GET(request: Request) {
  const encodedToken = await getEncodedToken(); // 認証情報を取得する
  const requestUrl = removeProxyPrefix(request.url);
  return await server(requestUrl, {
    headers: {
      Authorization: `Bearer ${encodedToken}`,
    },
    cache: 'no-store',
  });
}

export async function POST(request: NextRequest) {
  const encodedToken = await getEncodedToken();
  const requestBody = await request.json();
  const formData = JSON.stringify(requestBody);

  const requestUrl = removeProxyPrefix(request.url);

  return await server(requestUrl, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${encodedToken}`,
    },
    body: formData,
  });
src/app/api/proxy/[...all]/route.ts

3. Nextサーバー→バックエンド へリクエストする

上記の実装でリクエストまで行っています。

これでクライアント側からNextサーバーへのリクエスト時に認証などの情報をヘッダーにセットせずに最低限の情報だけでリクエストするようにすれば情報を外部に出す必要がなくなります。

さいごに

今回fetchAPIを利用していますが、axiosと比較するとエラーハンドリングはよしなにやってくれないので、今のところは以下のように利用しています。!res.okの判定を追加してエラーレスポンスをフロントへ返すようにしています。

import { NextResponse } from 'next/server';

import { SERVER_API_URL } from '@/config/constants';

export const server = async (
  url: RequestInfo,
  options?: RequestInit,
): Promise<NextResponse> => {
  try {
    const res = await fetch(`${SERVER_API_URL}${url}`, {
      headers: {
        'Content-Type': 'application/json',
        ...options?.headers,
      },
      ...options,
    });

    if (!res.ok) {
      return NextResponse.json(
        { error: res.statusText },
        { status: res.status },
      );
    }

    const data = await res.json();
    return NextResponse.json({ data });
  } catch (error) {
    return Promise.reject(error);
  }
};

参照