ng serve の開発サーバーに「/api で始まるリクエストは API サーバーへ転送する」と設定すると、ブラウザから見て画面と API が同じオリジン(http://localhost:4200)になり、開発中の CORS エラーが出なくなります。 設定は、プロキシの設定ファイルを作り、angular.json の serve の options.proxyConfig で指定します。

// src/proxy.conf.json
{
  "/api/**": {
    "target": "http://localhost:3000",
    "secure": false,
    "changeOrigin": true
  }
}
// angular.json(projects.<名前>.architect の中)
"serve": {
  "builder": "@angular/build:dev-server",
  "options": {
    "proxyConfig": "src/proxy.conf.json"
  },
  "configurations": { … },
  "defaultConfiguration": "development"
}
// コードでは「ホスト名を書かない」相対 URL で呼ぶ
this.http.get('/api/users');            // ○ プロキシを通る
// this.http.get('http://localhost:3000/api/users');   // ✕ プロキシを通らず CORS エラー

同じ画面から、API サーバー(CORS のヘッダーを返さない Node のサーバー)を2通りに呼んだ結果です。

呼び方 結果
http://localhost:3000/api/users(直接) blocked by CORS policy。HttpClient は status=0
/api/users(プロキシ経由) 成功:{"server":"users","path":"/api/users"}

確認環境:Angular 22.2.2(@angular/cli・@angular/build の dev-server。中で Vite 8.3.4 が動く)、Node.js 24.21.0(Linux)。Node で作った小さな API サーバー(ポート 3000・3001・3443)を立て、ng serve の画面を Chromium(Playwright 1.56.1)で開いて、上の結果とこの記事のエラー文・ログを得ました。本番の構成は nginx で同じ形を動かしました。各オプションの意味は angular.dev・Vite のドキュメントと、@angular/build の配布コードで確かめました(確認日 2026年10月12日)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Angular proxy.conf.jsonの書き方|開発中のCORSを回避」

なぜプロキシで CORS エラーが消えるのか

CORS(Cross-Origin Resource Sharing)は、ブラウザが「画面と違うオリジン(スキーム・ホスト・ポートの組)への通信の結果を、そのサーバーが許可していなければ JavaScript に渡さない」しくみです。localhost:4200 の画面から localhost:3000 を呼ぶと、ポートが違うので別オリジンになります。

直接呼んだときに、Chromium のコンソールに出たエラーです。

Access to fetch at 'http://localhost:3000/api/users' from origin 'http://localhost:4200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

POST で JSON を送ると、本番の前に OPTIONS の確認(プリフライト)が飛び、そこで止まりました。

Access to fetch at 'http://localhost:3000/api/users' from origin 'http://localhost:4200' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

API サーバーのログを見ると、GET はサーバーまで届いていて、ブラウザが結果を捨てていたことが分かります。POST は OPTIONS だけが届き、POST 本体は送られませんでした。

[users:3000] GET /api/users host=localhost:3000 origin=http://localhost:4200
[users:3000] OPTIONS /api/users host=localhost:3000 origin=http://localhost:4200

プロキシを使うと、通信の流れが次のように変わります。

ブラウザ ──(/api/users)──▶ ng serve(localhost:4200)──▶ API(localhost:3000)
           同じオリジン              サーバー同士の通信なので CORS は関係しない

ブラウザは localhost:4200 とだけ話すので、CORS の検査が起きません。CORS のしくみとエラー文の読み方は、関連記事「CORSエラーの原因と解決方法|エラーメッセージ別に解説」で扱っています。

設定の手順

1. プロキシの設定ファイルを作る

angular.dev の手順では src/proxy.conf.json に置きます。場所は自由で、angular.json で指定するパスはワークスペースのルート(angular.json のある場所)からの相対パスです。ファイルが無いと、ng serve は次のメッセージで止まる作りでした(@angular/build の配布コードによる)。

Proxy configuration file /…/src/proxy.conf.json does not exist.

2. angular.json で指定する(またはコマンドで渡す)

冒頭のとおり、serve の options に "proxyConfig" を書きます。v22 の ng new が作る angular.json の serve には options が無いので、builder の下に options ごと足します。毎回コマンドで渡す形もあります。

ng serve --proxy-config src/proxy.conf.json

3. HttpClient は相対 URL で呼ぶ

http://localhost:3000/... のように完全な URL を書くと、ブラウザは開発サーバーを通らずに直接 API へ行きます。プロキシを使うときは /api/... と書きます。 環境ごとに API の場所を変える方法は、関連記事「Angular environmentで環境変数を切り替える設定手順」で扱っています。

4. 設定を変えたら ng serve を再起動する

ng serve を動かしたまま設定ファイルの target を書き換えても、転送先は前のままでした(ECONNREFUSED 127.0.0.1:3001 が出続けた)。止めて起動し直すと、新しい転送先に変わりました。angular.dev にも「プロキシ設定の変更を反映するには ng serve の再起動が必要」とあります。

パスの書き方(どのリクエストが転送されるか)

キー("/api/**" の部分)で、転送するリクエストを決めます。angular.dev の説明と、@angular/build の配布コードの動きは次のとおりです。

キー 一致するもの 一致しないもの
/api /api・/api/users・/api-docs(前方一致) /v1/api
/api/* /api/users /api・/api/users/1
/api/** /api・/api/users・/api/users/1 /api-docs
^/api/(users|items) ^ で始めると正規表現 —

/api/* の設定で /api/users/1 を呼ぶと、転送されず、開発サーバーが index.html を返しました(200 text/html)。* と ** の違いで、深い階層が転送されないことがあります。 迷ったら /api/** か、前方一致の /api を使います。

@angular/build の配布コードでは、* を含むキーを正規表現に変換してから Vite に渡していました。複数のキーに一致するときは、上に書いたものが先に使われます(配布コードのコメントに「Vite proxies a request with the first entry that matches it」とあります)。

オプション一覧

v22 の開発サーバーは、Vite の server.proxy を使っています。Vite のドキュメントでは、各キーの設定は http-proxy-3 のオプションを広げたものと説明されています。よく使うものは次のとおりです。

オプション 意味 JSON で書けるか
target 転送先(http://localhost:3000 など) ○
changeOrigin Host ヘッダーを転送先のものに書き換える ○
secure false で、転送先の自己署名証明書を許す ○
pathRewrite パスを置き換える(Angular が rewrite に変換) ○
ws WebSocket も転送する ○
headers 転送時に足すヘッダー ○
rewrite パスを置き換える関数(Vite の書き方) JS のみ
configure プロキシ本体に処理を足す関数(ログなど) JS のみ
bypass 転送せずに返す・別の URL にする関数 JS のみ
logLevel v22 では何も出なかった(後述) ○(効かない)

changeOrigin:Host ヘッダーの書き換え

API サーバーのログで、受け取った Host ヘッダーを比べました。

設定 API が受け取った Host
"changeOrigin": true localhost:3000
書かない(JSON のオブジェクト形) localhost:4200
文字列だけで書いた("/auth": "http://localhost:3001") localhost:3001

バーチャルホスト(Host ヘッダーで振り分けるサーバー)や、Host を検査する API へ転送するときは changeOrigin: true にします。値を文字列だけで書くと、Vite は changeOrigin: true として扱いました。

secure:自己署名証明書の HTTPS へ転送する

自己署名証明書の HTTPS サーバー(ポート 3443)へ転送しました。

[vite] http proxy error: /secure-ng/x
Error: self-signed certificate; if the root CA is installed locally, try running Node.js with --use-system-ca

"secure": true(既定の動き)ではこのエラーで 502 になり、"secure": false にすると通りました。社内の検証環境など、証明書を検証できない転送先に使います。

pathRewrite:パスを置き換える

{
  "/v1/**": {
    "target": "http://localhost:3000",
    "pathRewrite": { "^/v1": "/api/v1" }
  }
}

/v1/orders を呼ぶと、API には /api/v1/orders で届きました。pathRewrite のキーは正規表現として扱われ、配布コードでは最初に置き換えが起きたものだけが使われます。先頭を消すだけなら { "^/api": "" } です。

複数の API に振り分ける

{
  "/api/**": { "target": "http://localhost:3000", "changeOrigin": true },
  "/v1/**":  { "target": "http://localhost:3000", "pathRewrite": { "^/v1": "/api/v1" } },
  "/auth":   { "target": "http://localhost:3001" }
}

この設定で3つを呼ぶと、/api/users と /v1/orders は 3000 番、/auth/me は 3001 番の API に届きました。

JS で書く(proxy.conf.mjs):ログを出す・ヘッダーを足す

関数を使うときは、JSON の代わりに JS で書きます。@angular/build の配布コードでは、拡張子が .json 以外なら JS のモジュールとして読み込み、default の書き出しを使っていました。.mjs にすると ES モジュールとして書けます。

// src/proxy.conf.mjs(angular.json の proxyConfig を "src/proxy.conf.mjs" にする)
export default {
  '/api/**': {
    target: 'http://localhost:3000',
    changeOrigin: true,
    configure: (proxy, options) => {
      proxy.on('proxyReq', (proxyReq, req) => {
        proxyReq.removeHeader('origin');   // バックエンドが Origin を見て拒否する場合
        console.log(`[proxy] ${req.method} ${req.url} -> ${options.target}${proxyReq.path}`);
      });
      proxy.on('proxyRes', (proxyRes, req) => {
        console.log(`[proxy] ${proxyRes.statusCode} ${req.url}`);
      });
    },
  },
  '/auth': 'http://localhost:3001',
};

ng serve の端末に、次のように出ました。

[proxy] POST /api/users -> http://localhost:3000/api/users
[proxy] 200 /api/users
[proxy] GET /api/users -> http://localhost:3000/api/users
[proxy] 200 /api/users

Origin ヘッダーはそのまま転送される

プロキシ経由の POST で、API が受け取ったヘッダーを見ました。

設定 Host Origin
changeOrigin: true だけ localhost:3000 http://localhost:4200
上のコードで removeHeader('origin') localhost:3000 (無し)

changeOrigin が変えるのは Host で、Origin はブラウザが付けたまま届きました。 バックエンド側が CORS の処理をしていて、Origin を見て拒否する(403 になる)ときは、上の configure のように開発中だけ消すか、setHeader('origin', …) で書き換えます。

ログを出す別の方法:DEBUG=vite:proxy

JSON のまま、どこへ転送したかだけを見たいときは、環境変数で Vite のデバッグ表示を出せました。

DEBUG=vite:proxy ng serve
2026-10-12T15:42:05.623Z vite:proxy /api/users -> http://localhost:3000

効かないときの確認表

実際に起きた症状と、その原因です。

症状 原因 対処
まだ CORS エラーが出る コードが http://localhost:3000/… と完全な URL /api/… の相対 URL にする
Http failure during parsing(status=200) キーに一致せず index.html が返った キー(* と **)を見直す
502・端末に ECONNREFUSED API が起動していない・ポート違い API を起動し target を確かめる
502・self-signed certificate HTTPS の証明書を検証できない "secure": false
設定を変えても前のまま ng serve が設定を読み直さない ng serve を再起動
API が 404・別のサイトを返す Host で振り分けるサーバー "changeOrigin": true
API が POST だけ 403 Origin: http://localhost:4200 で拒否 JS で origin を消す・書き換える
logLevel: "debug" でも何も出ない v22 の開発サーバーは使っていない configure か DEBUG=vite:proxy
does not exist で起動しない proxyConfig のパスが違う ルートからの相対パスにする

「Http failure during parsing」は、画面からは次のように見えました。プロキシに一致しないパスを呼ぶと、開発サーバーは SPA 用に index.html を 200 で返すので、HttpClient が JSON として読めずにこのエラーになります。

NG status=200 Http failure during parsing for http://localhost:4200/data/users

HttpClient のエラー処理は関連記事「Angular HttpClientの使い方|GET・POSTとエラー処理」で扱っています。

v22 の開発サーバー(Vite)での違い

v22 の ng new が使う @angular/build:dev-server は、中で Vite の開発サーバーを使っています。以前の webpack の開発サーバー(http-proxy-middleware を使う)向けに書かれた設定は、そのまま使えるものと、効かないものがありました。

項目 v22(Vite ベース)での扱い 確かめた方法
*・** を含むキー Angular が正規表現に変換して渡す 配布コード・/api/* の実行
pathRewrite(オブジェクト) Angular が rewrite 関数に変換 配布コード・/v1 の実行
context を使う配列の形 オブジェクトの形に変換して使う 配布コード
logLevel 使われない(何も出ない) "debug" で実行
onProxyReq など(http-proxy-middleware の書き方) Vite のオプションには無い Vite のドキュメント
ログ・ヘッダー変更 configure で proxy.on('proxyReq', …) 実行
bypass Vite が対応(JS のみ) Vite の配布コード

webpack ベースの browser ビルダーから application ビルダー(Vite の開発サーバー)に移したあとで logLevel のログが出なくなったら、この違いです。バージョンアップの手順は関連記事「Angularバージョンアップ手順|ng updateとエラー対処一覧」で扱っています。

本番では使えない理由と、本番の構成

proxyConfig は ng serve(開発サーバー)の設定で、ng build の成果物には入りません。 ng build で出るのは HTML・JS・CSS のファイルだけで、それを配る Web サーバーにはプロキシの機能がありません。本番では次のどちらかの形にします。

構成 やること 向いている場面
同じオリジンにまとめる Web サーバー(nginx など)で /api/ を API へ転送 画面と API を同じドメインで出す
別オリジンのまま API サーバーで CORS を許可する API を別ドメインで公開する

angular.dev のデプロイの説明でも、別のサーバーへのリクエストは「サーバー側で許可する必要があり、Angular やクライアント側でできることはない」とされています。

同じオリジンにまとめる(nginx の例)

ng build の成果物を nginx で配り、/api/ を API へ転送する設定です。この形で画面を開き、/api/users が成功することを確かめました。

server {
  listen 80;
  root /var/www/app/browser;          # ng build の出力(dist/<名前>/browser)

  location /api/ {
    proxy_pass http://localhost:3000;   # 末尾に / を付けないとパスはそのまま
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
  }

  location / {
    try_files $uri $uri/ /index.html;   # ルーティングの URL を直接開いたとき用
  }
}

/items/1 のようなルーティングの URL を直接開いても 200 で index.html が返りました。nginx の書き方は関連記事「Nginxリバースプロキシ設定|proxy_passの書き方と502/504対処」と「nginx location書き方|優先順位とtry_filesをcurlで検証」で扱っています。

別オリジンのまま(API 側で CORS を許可)

API を別ドメインで出すときは、API サーバーが Access-Control-Allow-Origin などのヘッダーを返すように設定します。Spring Boot の場合は関連記事「Spring Boot CORS設定|3つの書き方とSecurity併用のコード全文」で扱っています。Angular 側は、環境ごとに API の URL を変える形にします。

よくある質問

Q. ng serve --configuration production でもプロキシは効きますか。 A. proxyConfig を serve の options に書いていれば、構成(configuration)に関係なく読み込まれます。構成ごとに変えたいときは、configurations.production の中に proxyConfig を書きます。

Q. proxy.conf.js(CommonJS)でも書けますか。 A. 配布コードでは、.json 以外は JS のモジュールとして読み込み、module.exports でも export default でも使う作りでした。この記事では .mjs で動かしました。

Q. WebSocket も転送できますか。 A. "ws": true を付けると、WebSocket の接続(upgrade)も転送する作りです(Vite の配布コードによる)。

Q. プロキシを使えば、API 側の CORS 設定は要りませんか。 A. 開発中はブラウザから同じオリジンに見えるので、CORS の検査は起きませんでした。本番の構成で別オリジンにするなら、API 側の設定が要ります。

まとめ

  • プロキシは「ブラウザから同じオリジンに見せる」ことで CORS を起こさない。コードは /api/… の相対 URL で呼ぶ
  • v22 は angular.json の serve → options → proxyConfig。変えたら ng serve を再起動
  • キーは /api/** か前方一致の /api。/api/* は1階層だけ
  • changeOrigin は Host だけを変える。Origin はそのまま届く。ログは configure か DEBUG=vite:proxy(logLevel は効かなかった)
  • 本番では使えない。nginx などで同じオリジンにまとめるか、API 側で CORS を許可する

参考・出典

確認日はいずれも 2026年10月12日です。

  • Angular「Serving Angular apps for development(Proxying to a backend server)」:https://angular.dev/tools/cli/serve
  • Angular「Deployment」:https://angular.dev/tools/cli/deployment
  • Angular「Migrating to the new build system」:https://angular.dev/tools/cli/build-system-migration
  • Vite「Server Options(server.proxy)」:https://vite.dev/config/server-options
  • Medium「Angular Proxy: How to Bypass CORS Errors Using proxy.conf.json」:https://medium.com/@navneetsingh_95791/angular-proxy-how-to-bypass-cors-errors-using-proxy-conf-json-7ee80509d399

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Angular proxy.conf.jsonの書き方|開発中のCORSを回避」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?