0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Expressとhttp-proxy-middlewareで最小構成のリバースプロキシを作る

0
Posted at

はじめに

フロントエンドから外部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 に定義します。

.env
PORT=3000
TARGET_URL=https://httpbingo.org

.env.example も同じ内容で用意しておきます。

.env.example
PORT=3000
TARGET_URL=https://httpbingo.org

.env を読み込む設定ファイルを作ります。

src/config/env.js
import dotenv from 'dotenv';

dotenv.config();

export const config = {
  port: process.env.PORT || 3000,
  targetUrl: process.env.TARGET_URL
};

Expressサーバを作成する

まずは Express のサーバ本体を作ります。

src/server.js
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へ転送するルートを作ります。

src/routes/proxy.js
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

今回は .envTARGET_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 にクエリパラメータが入り、urlhttps://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への振り分けを足していくと、実運用に近いプロキシサーバへ発展させられます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?