🫐

Next.jsのAPI Routesを中間APIとして使う方法

2023/03/09に公開
2

全体の概要

基本的な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の使い方を解説していきます。

下記のような構成を想定しています。

  1. フロント側からNext API Routes(中間API)にリクエストを送る
  2. Next API Routes(中間API)がサーバー側のAPIにリクエストを送る
  3. サーバー側からNext API Routesにレスポンスデータが返ってくる
  4. Next API Routesからフロントへレスポンスデータを返す

なお今回はサーバー側のAPIの想定で{JSON} Placeholderを利用します。

https://jsonplaceholder.typicode.com/

中間APIを利用しない場合

UIに関してはサンプルのため簡易的に作成します。下記の手順で作成していきます。

  1. /pages配下にsampleを作成する (ファイル名は任意)
  2. /pages配下のファイルにてuseAsyncを用いてjsonplaceholderへリクエスト
  3. レスポンスデータをstateに保存
  4. データをリストで表示

まずレスポンスデータの型を下記のように定義します。

src/types/index.ts
export type PostType = {
  userId: number;
  id: number;
  title: string;
  body: string;
};

/pages配下でAPIリクエストのロジックとUIの表示の実装を書いてきます。

pages/sample/index.tsx
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にリクエストヘッダーを追加します。

pages/sample/index.tsx
  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で記述をします。

pages/api/post/index.ts
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に変更する

pages/sample/index.tsx
  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側でリクエストヘッダーを設定します

pages/api/posts/index.ts
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として利用する方法とメリットについて説明をしました。

他にも色々な記事を書いているので、ぜひ読んでいただけると嬉しいです。

https://zenn.dev/sutamac/articles/27246dfe1b5a8e

https://qiita.com/KNR109/items/d3b6aa8803c62238d990

https://qiita.com/KNR109/items/e13a5c5b8b461e846902

Discussion

たまぬぎたまぬぎ

いくつか気になったので質問させてください。

セキュリティ上のメリット

中間APIを利用することでCSRF攻撃を防ぐことができる・・・とのことでしたが、ご提示されているサンプルコードですと、自前でAuthorization ヘッダーにJWTをつけているので中間APIを使わなくてもCSRF攻撃を防ぐことができているような気がしますがどうでしょうか・・・?

また例えば中間APIを使っていたとしてもcookieを使って認証をしている限りでは、CSRF攻撃をされてしまう可能性があると思います。

トークンの隠蔽について

こうすることでフロントから中間APIを叩く際にリクエストヘッダー等を付与せず(隠くして)リクエストを送ることができます。

ご提示されているサンプルコードでは中間APIへのリクエストにも、トークンを付与してリクエストをしているように見えました。
こちらは単純に修正漏れでしょうか・・・?

      // 中間APIのエンドポイントに変更
      const response = await axios.get("http://localhost:3000/api/posts", {
        headers: {
          Authorization: "Bearer access-token-sample",
        },
      });

トークンの隠蔽について2

トークンの隠蔽についてですが、どういったユースケースを想定されているのでしょうか・・・?
外部APIを叩く場合、例えば今どきですと OpenAI のAPIなどを呼び出すときに、APIのアクセスキーやシークレットキーをユーザーに隠したいという場面では中間APIを使うモチベーションがあるように思えました。
(というかそうするべき)

ただ普通のユーザー認証トークンを隠蔽したいというモチベーションがあまり想定できず、少し混乱してしまいました。

KNR109KNR109

コメントありがとうございます。

セキュリティ上のメリット

CSRF攻撃については「Next API Routesを中間APIとして挟むことで、外部から直接本体のAPIにアクセスされることを防ぐことができます。」の一例として記載したのですが、たまぬぎ様がおっしゃられている通り、続くコード例との整合性が合わず誤解を与えてしまう内容になっていたので、CSRF攻撃の例は削除させていただきます。

トークンの隠蔽について

こちらご指摘の通り、修正漏れだったので、修正します。

トークンの隠蔽について2

こちら想定としては、Next API Routes側でリクエストヘッダーを設定します で紹介している下記の関数において、

  // アクセストークンを取得する関数(ダミー)
  const getAccessToken = () => {
    return "access-token-sample";
  };

セッションに保存されている「Authorization Code」を取得し、それをリクエストheaderに付与し、外部APIからアクセストークンを取得するような構成を想定していました。

この部分のコードをより詳細に書くとすると下記のような想定になります。

  const getAccessToken = () => {
    const headers = {Authorization Token code}; // getAuthorizationCode() 等でセッションからAuthorization Codeを取得する
    const accessToken = axios.get('/api/user/token', headers); // getAccessToken() 外部APIからアクセストークンを取得する
    return accessToken 
  };

こちら解説を簡略化させるために、だいぶ省略してしまっていました。
再度整理して修正します。