はじめに
フロントエンドから外部APIを利用するとき、ブラウザから直接APIへアクセスするのではなく、一度自分のサーバを経由させたい場面があります。
たとえば、次のようなケースです。
- APIキーやアクセストークンをブラウザに置きたくない
- CORSの制約を避けたい
- リクエストやレスポンスのログを取りたい
- 共通の認証ヘッダを付与したい
- 複数の外部APIへのアクセス入口をひとつにまとめたい
この記事では、Node.js と Express、http-proxy-middleware を使って、外部APIへリクエストを転送する最小構成のリバースプロキシを作ります。接続先は .env で切り替えられるようにします。
作るもの
今回は例として、クライアントからは http://localhost:3000/api/... にアクセスし、実際のリクエスト先は https://httpbingo.org/... にする構成を作ります。
つまり、ローカル側では /api をプロキシの入口として使い、外部APIへ転送するときは /api を含めない形にします。
クライアント
|
| http://localhost:3000/api/get
v
Express
|
| https://httpbingo.org/get
v
外部API
動作確認には、受け取ったリクエスト内容を返してくれる https://httpbingo.org を使います。
同じ用途では https://httpbin.org がよく使われます。httpbin.org が本家ですが、執筆時点では一時的に 503 が返る状態だったため、この記事では同等の確認ができる httpbingo.org を利用します。
なお、/api という入口や、転送時に /api を外す処理はあくまで今回の例です。アプリケーションの設計や転送先APIのパスに合わせて変更できます。
使用するパッケージ
今回使う主なパッケージは次のとおりです。
| パッケージ | 用途 |
|---|---|
express |
Webサーバを作る |
http-proxy-middleware |
リクエストを別のサーバへ転送する |
dotenv |
.env の環境変数を読み込む |
nodemon |
開発時にサーバを自動再起動する |
環境
この記事では、次の環境で動作確認しています。
| ツール | バージョン |
|---|---|
| Node.js | v24.14.0 |
| npm | 11.17.0 |
| express | ^5.2.1 |
| http-proxy-middleware | ^4.1.1 |
| dotenv | ^17.4.2 |
| nodemon | ^3.1.14 |
プロジェクトを作成する
まずはプロジェクトを作成します。
mkdir express-reverse-proxy
cd express-reverse-proxy
npm init -y
npm install express http-proxy-middleware dotenv
npm install --save-dev nodemon
ES Modules を使うため、package.json の "type" を "module" にします。npm 11.17.0 では npm init -y の初期状態で "type": "commonjs" が入っていたため、今回は "module" に変更します。"type" がない場合は追加してください。
また、起動用のスクリプトも追加しておきます。
{
"name": "express-reverse-proxy",
"version": "1.0.0",
"type": "module",
"scripts": {
"start": "node src/server.js",
"dev": "nodemon src/server.js"
}
}
.gitignore には node_modules と .env を追加しておきます。
node_modules/
.env
ディレクトリ構成
今回は次の構成で作ります。
express-reverse-proxy/
├─ .env
├─ .env.example
├─ .gitignore
├─ package.json
└─ src/
├─ config/
│ └─ env.js
├─ routes/
│ └─ proxy.js
└─ server.js
作成コマンドは次のとおりです。
mkdir -p src/config src/routes
touch src/server.js src/config/env.js src/routes/proxy.js
touch .env .env.example .gitignore
環境変数を設定する
ポート番号と転送先URLを .env に定義します。
PORT=3000
TARGET_URL=https://httpbingo.org
.env.example も同じ内容で用意しておきます。
PORT=3000
TARGET_URL=https://httpbingo.org
.env を読み込む設定ファイルを作ります。
import dotenv from 'dotenv';
dotenv.config();
export const config = {
port: process.env.PORT || 3000,
targetUrl: process.env.TARGET_URL
};
Expressサーバを作成する
まずは Express のサーバ本体を作ります。
import express from "express";
import { config } from "./config/env.js";
import proxyRouter from "./routes/proxy.js";
const app = express();
app.get("/", (req, res) => {
res.send("Express Reverse Proxy Server");
});
app.use(proxyRouter);
app.listen(config.port, () => {
console.log(`Server is running on http://localhost:${config.port}`);
});
/ にアクセスしたときは確認用の文字列を返し、プロキシ処理は routes/proxy.js に分けます。
プロキシを実装する
次に、/api へのアクセスを外部APIへ転送するルートを作ります。
import { Router } from "express";
import { createProxyMiddleware } from "http-proxy-middleware";
import { config } from "../config/env.js";
const router = Router();
router.use(
"/api",
createProxyMiddleware({
target: config.targetUrl,
changeOrigin: true,
pathRewrite: {
"^/api": "",
},
on: {
proxyReq: (proxyReq, req) => {
console.log(`[Proxy] ${req.method} ${req.originalUrl} -> ${config.targetUrl}${req.url}`);
},
error: (err, req, res) => {
console.error(err.message);
res.status(502).json({
error: "Bad Gateway",
message: "Failed to connect to the target server.",
});
},
},
}),
);
export default router;
プロキシ設定のポイント
target には転送先のベースURLを指定します。
target: config.targetUrl
今回は .env の TARGET_URL=https://httpbingo.org を参照しています。別のAPIへ向けたい場合は、コードではなく環境変数を変更します。
今回の例では、クライアントからは /api/get にアクセスしますが、実際の転送先は https://httpbingo.org/get にしたいです。
そのため、転送前に先頭の /api を取り除きます。
pathRewrite: {
"^/api": "",
}
変換イメージは次のとおりです。
| クライアントからのリクエスト | 実際の転送先 |
|---|---|
/api/get |
https://httpbingo.org/get |
/api/post |
https://httpbingo.org/post |
/api/anything/sample |
https://httpbingo.org/anything/sample |
pathRewrite は必須ではありません。ローカル側で受け取ったパスをそのまま転送したい場合は、この設定を省略できます。
changeOrigin: true を指定すると、転送先へ送る Host ヘッダをターゲットに合わせて書き換えます。
changeOrigin: true
イメージとしては次のような変換です。
Host: localhost:3000
↓
Host: httpbingo.org
外部APIによっては Host ヘッダを見てリクエストを判定することがあります。外部APIへ転送する用途では、基本的に true にしておくと扱いやすいです。
今回のコードでは、プロキシされるリクエストを簡単にログ出力しています。
proxyReq: (proxyReq, req) => {
console.log(`[Proxy] ${req.method} ${req.originalUrl} -> ${config.targetUrl}${req.url}`);
}
たとえば GET /api/get にアクセスすると、サーバ側に次のようなログが出ます。
[Proxy] GET /api/get -> https://httpbingo.org/get
転送先に接続できない場合は 502 Bad Gateway を返すようにしています。
error: (err, req, res) => {
console.error(err.message);
res.status(502).json({
error: "Bad Gateway",
message: "Failed to connect to the target server.",
});
}
起動する
サーバを起動します。
npm run dev
通常起動する場合は npm start を使います。
npm start
起動すると、次のようなログが表示されます。
Server is running on http://localhost:3000
ルートパスを確認します。
curl http://localhost:3000
次の文字列が返れば Express サーバは起動できています。
Express Reverse Proxy Server
GETリクエストを確認する
次に、/api/get へリクエストしてみます。
curl "http://localhost:3000/api/get?name=sample&lang=ja"
レスポンスには次のような内容が含まれます。
{
"args": {
"lang": [
"ja"
],
"name": [
"sample"
]
},
"url": "https://httpbingo.org/get?name=sample&lang=ja"
}
args にクエリパラメータが入り、url が https://httpbingo.org/get... になっていれば、Express 経由で外部APIへ転送できています。
POSTリクエストを確認する
JSONボディも送ってみます。
curl -X POST http://localhost:3000/api/post \
-H "Content-Type: application/json" \
-d '{"name":"sample","lang":"ja"}'
レスポンスには次のような内容が含まれます。
{
"json": {
"lang": "ja",
"name": "sample"
},
"url": "https://httpbingo.org/post"
}
送信したJSONが json に入って返ってくれば、POSTリクエストのボディも正しく転送されています。
この最小構成では express.json() を使っていません。プロキシとしてそのまま転送するだけなら、Express側でボディをパースしない方がシンプルです。
この構成を拡張するなら
実際のアプリケーションで使う場合は、次のような拡張が考えられます。
-
Authorizationヘッダをサーバ側で付与する - APIキーを
.envから読み込んでクエリやヘッダに追加する - リクエストIDを付けてログを追いやすくする
- レスポンスログやエラーログを整備する
-
/api/usersと/api/ordersで転送先を切り替える - 認証済みユーザーだけがプロキシを使えるようにする
おわりに
Express と http-proxy-middleware を使うと、少ないコードでリバースプロキシを作れます。
最小構成としてはここまでで十分動きます。ここから認証ヘッダの付与、ログ出力、複数APIへの振り分けを足していくと、実運用に近いプロキシサーバへ発展させられます。