📘

ブラウザでAPIを叩くのに苦戦した話

に公開

はじめに

先日、ブラウザでAPIを叩こうとしたところ、CORSエラーとやらに苦しめられたので、その解決方法を残しておこうと思います。

状況

まずは普通にブラウザ上でAPIを叩いてみましょう。

async function getApi() {
  const res = await fetch("https://api.mihomo.me/sr_info_parsed/830647229?lang=jp");
  console.log(res);
}

getApi();

そうすると次のようなエラーが発生します。

これはCORS制限によってブラウザが外部APIへのアクセスを拒否したために発生したエラーです。
このCORSがカギというわけですね。

CORSとは

MDN によりますと

オリジン間リソース共有 (Cross-Origin Resource Sharing, CORS) は、 HTTP ヘッダーベースの仕組みを使用して、あるオリジンで動作しているウェブアプリケーションに、異なるオリジンにある選択されたリソースへのアクセス権を与えるようブラウザーに指示するための仕組みです。

端的に言うと、異なるオリジン間の通信を、サーバー側の許可によって可能にする仕組みです。
セキュリティの関係で通常は外部からのアクセスを制限しているのです。

解決方法

解決方法は色々あると思いますが、とりあえず2つ紹介します。

サーバー側でCORSを許可する

一番理想的な解決方法です。
APIの管理者が私たちのオリジンを許可するように設定してもらう方法です。
この方法であれば、ブラウザで直接APIを取得することができるようになりますね。

ただ、これは管理者が操作しなくてはならず、個人である私たちでは設定することはできません。

回避プロキシを立てる

自前のサーバーを立て、ブラウザから自分のサーバーにアクセスしてAPIを取得する方法です。
ブラウザとプロキシサーバー間の通信は同一オリジンとみなされ、プロキシサーバーで外部通信を行うため、ブラウザはCORSの影響を受けません。

例えば、 Node.jsExpressを活用して以下のようにサーバーを構築します。

const express = require('express');
const cors = require('cors');

const app = express();
const PORT = 3000

app.use(cors()); // ここでCORSを許可

app.get('/api/mihomo', async (req, res) => {
  try {
    const targetUrl = 'https://api.mihomo.me/sr_info_parsed/830647229?lang=jp';
    const response = await fetch(targetUrl);

    if (!response.ok) {
      return res.status(response.status).json({ error: 'Failed to fetch data from target API' });
    }

    const data = await response.json();
    res.json(data);
  } catch (error) {
    console.error('Proxy error:', error);
    res.status(500).json({ error: 'Internal server error' });
  }
});

app.listen(PORT, () => {
  console.log(`Proxy server is running at http://localhost:${PORT}`);
});

これにより、http://localhost:3000/api/mihomoにリクエストを送ることでCORSの制限を回避することが可能です。

自前でサーバーを立てる以上、セキュリティや金銭の面で色々と注意しないといけませんね。そのあたりも考慮して運用が必要になりそうです。

Vercelでの運用

Vercel とは、webサイトやwebアプリの開発、デプロイ、公開を行うためのプラットフォームです。
みなさんもよくご存知のNext.jsを開発していることでも知られていますね。

最近はよくVercelが使われると思うので、Vercelでの使用例についても触れておきます。

サーバーレス関数を活用する

Vercelにはサーバーレス関数というものがあります。
これは、サーバーの構築や管理を気にせずに、関数単位でデプロイできる仕組みです。
運用コストも低く、導入も簡単なので誰でも扱いやすいと思います。

具体的な使い方としては、apiというディレクトリを作り、そこにjsファイルを置くだけです。

api/hello.js
module.exports = async (req, res) => {
  res.status(200).json({ message: "Hello from Vercel Serverless!" });
};

あとは、https://your-vercel-app.vercel.app/api/helloにリクエストを送ればいいです。

具体的な使用例

さて、上記を踏まえてAPIを叩くためのコードは以下のとおりです。

api/mihomo.js
module.exports = async (req, res) => {
  try {
    const response = await fetch('https://api.mihomo.me/sr_info_parsed/830647229?lang=jp')

    if (!response.ok) {
      return res.status(response.status).json({
        error: "Failed to fetch data from mihomo",
        details: await response.text()
      })
    }

    const data = await response.json()
    res.json(data)
  } catch (error) {
    res.status(500).json({
      error: "Fetch request failed",
      details: error.message
    })
  }
}

https://your-vercel-app.vercel.app/api/mihomoにアクセスすれば情報が取得できるというわけですね。
あとはパラメータなどを指定したければその処理を追加すればいいです。

ちなみに、同じプロジェクト内であれば/api/mihomoにリクエストを送るだけで取得ができます。

まとめ

さて、今回はCORSを回避しブラウザでAPIを叩く方法についてまとめてきました。
私自身、初めて遭遇した問題だったので、手探りな部分が多いです。
有識者の方はコメントいただけますと嬉しい限りです。

ではまた。

Discussion