RouteHandlersを中間APIとして利用する
2023-10-14
a year ago
開発環境
- next 13.4.10
前提
AppRouterを利用します。
本題
基本的なフロントエンド、バックエンドの構成ではフロントからバックエンドへ直接リクエストを送ることが多いと思います。
セキュリティ要件によりますが、リクエストヘッダーにトークンなど認証情報を外部に見せたくないこともあると思います。
そんな時の対応策の一つとしてNextサーバーを中間APIとして処理を挟む方法を紹介します。
処理の流れ
- RouteHandlersで フロント→Nextサーバー へのエンドポイントを作成
- そのAPIでトークンやAPIキーなどの認証に関わる情報をセットする
- 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,
});
}
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,
});
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);
}
};