Next.jsのAPI Routesを中間APIとして使う方法
全体の概要
基本的なAPI結合
基本的にはフロント(Next.js)からaxios
等を利用して、サーバー側のAPIエンドポイントに対して直接リクエストを送る構成かと思います。
今回紹介するNext.jsのAPI Routesを中間APIとして挟んだ場合
今回紹介する構成は下記のようになっています。
この構成にするメリット・デメリットは後の章で解説をしていきます。
この記事の対象者
- フロントエンド初級者から中級者
- Next.jsのAPI Routesの基本を学びたい人
- セキュリティーを考慮したAPI通信手法について知りたい人
Next.jsのAPI Routesを中間APIとして挟むメリット
- セキュリティーが強化できる
- ログの管理ができる
- APIエンドポイントの抽象化
セキュリティーが強化できる
Next API Routesを中間APIとして挟むことで、外部から直接本体のAPIにアクセスされることを防ぐことができます。
自分が今回担当したプロジェクトでは「トークン等のリクエスト情報を外部から見えないようにしたい」という要望があり中間APIを挟む設計にしました。
一例としてフロントから直接APIを叩くと下記のようにリクエスト情報が外部に見えてしまいます。
中間APIを挟むことでこの部分を隠すことができます。
具体的なやり方についてはこの後の章で解説します。
ログの管理ができる
Next API Routesを間に挟むことで、APIリクエストに関するログを中央集権的に管理することができます。
これによって、APIリクエストを分析することができ、アプリケーションの問題を素早く特定することができます。
APIエンドポイントの抽象化
Next.jsのAPI Routesを中間APIとして利用することで、フロントエンドとバックエンドの間でのAPIエンドポイントの抽象化が可能です。
これによって、APIのエンドポイントの変更や追加が発生た場合でも、フロントエンド側のコード変更量を減らすことができる。
Next.jsのAPI Routesを中間APIとして挟むデメリット
- APIの管理が複雑になる
- 開発工数が増える
APIの管理が複雑になる
Next.jsのAPI Routesを中間APIとして開発をすることで、エンドポイントの数が増えるので、APIの管理が中間APIを挟まない場合と比較すると複雑になってしまいます。
開発工数が増える
中間APIを開発するための工数が増えてしまいます。
また、コードが分散されてしまうのでデバックや保守の際にも問題が発生してしまう可能性が増えてしまいます。
中間APIを挟むべきタイミング
紹介したメリット・デメリットを踏まえた上で、中間APIの導入を検討することが重要です。
個人的には、適切なセキュリティー対策の様件等が不要な場合は中間APIを挟むメリットは特にないかと思います。
プロジェクト規模や要望に応じて対応することが重要です。
Next.jsのAPI Routesを中間APIとして利用する開発例
ここからは具体的にコードを用いて、NextAPI Routesの使い方を解説していきます。
下記のような構成を想定しています。
- フロント側からNext API Routes(中間API)にリクエストを送る
- Next API Routes(中間API)がサーバー側のAPIにリクエストを送る
- サーバー側からNext API Routesにレスポンスデータが返ってくる
- Next API Routesからフロントへレスポンスデータを返す
なお今回はサーバー側のAPIの想定で{JSON} Placeholderを利用します。
中間APIを利用しない場合
UIに関してはサンプルのため簡易的に作成します。下記の手順で作成していきます。
- /pages配下にsampleを作成する (ファイル名は任意)
- /pages配下のファイルにて
useAsync
を用いてjsonplaceholderへリクエスト - レスポンスデータをstateに保存
- データをリストで表示
まずレスポンスデータの型を下記のように定義します。
export type PostType = {
userId: number;
id: number;
title: string;
body: string;
};
/pages配下でAPIリクエストのロジックとUIの表示の実装を書いてきます。
import axios from "axios";
import type { NextPage } from "next";
// hooks
import { useState } from "react";
import { useAsync } from "react-use";
// types
import { PostType } from "../../src/types";
const Page: NextPage = () => {
const [posts, setPosts] = useState<PostType[]>([]);
useAsync(async () => {
try {
const response = await axios.get(
"https://jsonplaceholder.typicode.com/posts"
);
setPosts(response.data);
} catch (e) {
console.log(e);
}
}, []);
return (
<ul>
{posts.map((i) => (
<li key={i.id}>{i.title}</li>
))}
</ul>
);
};
export default Page;
実際にローカルホストを立ち上げると、データが表示されていることが確認できます。
リクエストにトークンを付与する
jsonplaceholderでは不要ですが、認証があることを想定してリクエストヘッダーにアクセストークンを付与してリクエストを送ってみます。
axios
にリクエストヘッダーを追加します。
useAsync(async () => {
try {
const response = await axios.get(
"https://jsonplaceholder.typicode.com/posts",
{
// 追加箇所
headers: {
Authorization: "Bearer access-token-sample",
},
}
);
setPosts(response.data);
} catch (e) {
console.log(e);
}
}, []);
Chromの検証ツールよりリクエストヘッダーを確認するとAuthorization
が追加されていることが確認できます。
こういったフロントからサーバーにAPIを送る際に付与するリクエストヘッダー等の内容を隠したい場合に次に説明する中間APIを挟みます。
中間APIを利用する場合
Next API Routesを作成する
jsonplaceholderに直接リクエストを送っていた箇所をNext API Routesで記述をします。
import axios from "axios";
import type { NextApiRequest, NextApiResponse } from "next";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method === "GET") {
const result = await axios
.get("https://jsonplaceholder.typicode.com/posts")
.then((response) => response.data);
return res.status(200).json(result);
}
}
ローカルホストにアクセスすると、jsonplaceholderのデータが返ってくることが確認できます。
http://localhost:3000/api/posts
フロントのリクエスト先をNext API Routesに変更する
useAsync(async () => {
try {
// 中間APIのエンドポイントに変更
const response = await axios.get("http://localhost:3000/api/posts");
console.log(response);
setPosts(response.data);
} catch (e) {
console.log(e);
}
}, []);
データが取れていることを確認できます。
Next API Routes側でリクエストヘッダーを設定します
import axios from "axios";
import type { NextApiRequest, NextApiResponse } from "next";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
// アクセストークンを取得する関数(ダミー)
const getAccessToken = () => {
return "access-token-sample";
};
if (req.method === "GET") {
const result = await axios
.get("https://jsonplaceholder.typicode.com/posts", {
headers: {
Authorization: getAccessToken(),
},
})
.then((response) => {
// リクエストヘッダーの内容を確認する用
console.log(response);
return response.data;
});
return res.status(200).json(result);
}
}
Next API Routesの中間APIからjsonplaceholderに対してのリクエストのヘッダー内に、アクセストークンが付与されていることを確認できます。
こうすることでフロントから中間APIを叩く際にリクエストヘッダー等を付与せず(隠くして)リクエストを送ることができます。
最後に
いかがだったでしょうか。
今回はNext API Routesを中間APIとして利用する方法とメリットについて説明をしました。
他にも色々な記事を書いているので、ぜひ読んでいただけると嬉しいです。
Discussion
いくつか気になったので質問させてください。
セキュリティ上のメリット
中間APIを利用することでCSRF攻撃を防ぐことができる・・・とのことでしたが、ご提示されているサンプルコードですと、自前で
Authorization
ヘッダーにJWTをつけているので中間APIを使わなくてもCSRF攻撃を防ぐことができているような気がしますがどうでしょうか・・・?また例えば中間APIを使っていたとしてもcookieを使って認証をしている限りでは、CSRF攻撃をされてしまう可能性があると思います。
トークンの隠蔽について
ご提示されているサンプルコードでは中間APIへのリクエストにも、トークンを付与してリクエストをしているように見えました。
こちらは単純に修正漏れでしょうか・・・?
トークンの隠蔽について2
トークンの隠蔽についてですが、どういったユースケースを想定されているのでしょうか・・・?
外部APIを叩く場合、例えば今どきですと OpenAI のAPIなどを呼び出すときに、APIのアクセスキーやシークレットキーをユーザーに隠したいという場面では中間APIを使うモチベーションがあるように思えました。
(というかそうするべき)
ただ普通のユーザー認証トークンを隠蔽したいというモチベーションがあまり想定できず、少し混乱してしまいました。
コメントありがとうございます。
CSRF攻撃については「Next API Routesを中間APIとして挟むことで、外部から直接本体のAPIにアクセスされることを防ぐことができます。」の一例として記載したのですが、たまぬぎ様がおっしゃられている通り、続くコード例との整合性が合わず誤解を与えてしまう内容になっていたので、CSRF攻撃の例は削除させていただきます。
こちらご指摘の通り、修正漏れだったので、修正します。
こちら想定としては、Next API Routes側でリクエストヘッダーを設定します で紹介している下記の関数において、
セッションに保存されている「
Authorization Code
」を取得し、それをリクエストheader
に付与し、外部APIからアクセストークンを取得するような構成を想定していました。この部分のコードをより詳細に書くとすると下記のような想定になります。
こちら解説を簡略化させるために、だいぶ省略してしまっていました。
再度整理して修正します。