React×FastAPIをDockerで連携する手順|CORS設定とdocker composeで動かす【2026年版】
ReactのフロントエンドとFastAPIのバックエンドを1つのアプリとしてつなぐとき、つまずくのは「どうAPIを呼ぶか」ではなく「オリジンの違い(CORS)」と「開発環境と本番環境で構成が変わること」の2点に集約されます。本記事は、React(Vite)+FastAPI+PostgreSQLをDocker Composeで1コマンド起動する開発環境を作り、本番でCORSを畳む同一オリジン化の考え方までを、動くコード付きで解説します。フロントは非推奨化されたcreate-react-appではなくViteを前提にします。
まとめ
先に結論をまとめます。詳細は各セクションでコードとともに示します。
- 構成は3コンテナ:web(React/Vite)・api(FastAPI)・db(PostgreSQL)を
compose.yamlにまとめ、docker compose upで一括起動する。 - 連携の要はCORS:開発中はオリジンが分かれる(5173と8000)ため、FastAPI側で
CORSMiddlewareを設定するか、Viteのproxyで/apiを8000へ転送する。本番はリバースプロキシで同一オリジンにするのが堅い。 - 開発と本番で構成が違う:開発はVite開発サーバ+uvicorn
--reload、本番はReactをビルドしてNginxが静的配信し、/apiだけFastAPIへプロキシする。 - フロントはVite一択:create-react-appはReact公式が2025年2月に非推奨化。新規はViteで作る。
- 認証Cookieを使うなら:
allow_credentials=Trueと具体的なオリジン指定がセット。allow_origins=["*"]とCookieはCORS仕様上両立しない。
React×FastAPI×Dockerの全体構成と開発・本番の違い
React(表示)とFastAPI(API)は別プロセスで動くため、両者を同じ土台に載せる方法を最初に決めます。ここではDocker Composeで、フロント・API・DBを3つのサービスとして定義します。ディレクトリはフロントとバックを分け、Compose定義を頂点に置く構成にします。
myapp/
├─ backend/
│ ├─ app/main.py
│ ├─ requirements.txt
│ └─ Dockerfile
├─ frontend/
│ ├─ src/
│ ├─ package.json
│ ├─ vite.config.ts
│ └─ Dockerfile
├─ nginx/default.conf
├─ compose.yaml # 開発用
└─ compose.prod.yaml # 本番用
重要なのは、開発環境と本番環境で「Reactの動かし方」と「APIへのつなぎ方」が変わる点です。開発はホットリロード重視でVite開発サーバを立てますが、本番でVite開発サーバを動かすことはしません。ビルドした静的ファイルをNginxが配信します。次の表が対応関係です。
| サービス | 開発環境 | 本番環境 |
|---|---|---|
| web(React) | Vite開発サーバ(:5173) | Nginxで静的配信(:80) |
| api(FastAPI) | uvicorn –reload(:8000) | uvicorn(–reloadなし) |
| db(PostgreSQL) | postgresコンテナ | postgresコンテナ/マネージドDB |
| APIへの接続 | Viteプロキシ or CORS | 同一オリジン(リバースプロキシ) |
つまり「開発でCORSを許可する」設定と「本番で同一オリジンに寄せる」設定は別物です。この違いを踏まえずに開発時のCORS設定を本番へ持ち込むと、後述の認証Cookieでハマります。
FastAPIバックエンド:APIの作成とCORS設定
先にAPIを作り、次にReactから叩けるようCORSを開けます。FastAPI自体の最短起動やasync/defの使い分けはFastAPIとは|最短で動かす手順とasync/defの使い分けで詳しく扱っているため、ここでは連携に必要な部分に絞ります。
FastAPIアプリとJSONを返すエンドポイントの実装
backend/app/main.pyに、ReactがfetchするためのJSONエンドポイントを1つ用意します。FastAPIは戻り値のdictをそのままJSONにして返します。
# backend/app/main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/api/hello")
def hello():
return {"message": "FastAPIからのレスポンス"}
依存パッケージはrequirements.txtにfastapiとuvicorn[standard]を書きます。FastAPIはPython 3.10以上が必須です(2026年7月時点でFastAPIは0.139系)。/api/を接頭辞にしておくと、本番でリバースプロキシがAPIとフロントを振り分けやすくなります。作成したAPIの動作確認は、FastAPIが自動生成するSwagger UI(/docs)から実行できます。
CORSMiddlewareでReactのオリジンを許可する設定
開発中、Reactはlocalhost:5173、FastAPIはlocalhost:8000で動きます。オリジン(プロトコル・ホスト・ポートの組)が違うため、そのままfetchするとブラウザがCORSでレスポンスをブロックします。FastAPI側で許可オリジンを明示します。
# backend/app/main.py(追記)
from fastapi.middleware.cors import CORSMiddleware
origins = ["http://localhost:5173"] # Viteの開発サーバ
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
allow_originsにはhttp://localhost:5173のように完全なオリジンを渡します。ここでallow_credentials=True(Cookie送信を許可)にしている場合、allow_origins=["*"]は使えません。ワイルドカードと資格情報の同時指定はCORS仕様で禁止されており、FastAPIも実際のオリジンをそのまま返さないためリクエストが失敗します。厳密にはallow_methods・allow_headersのワイルドカードも資格情報付きでは仕様上不可ですが、Starletteが実際の値を反射して応答するため動作はします。いずれにせよ認証Cookieを扱う予定なら、開発でも本番でもオリジンは具体値で列挙するのが原則です。
Reactフロントエンド:Vite構築とAPI呼び出し
フロントはViteで作ります。create-react-appはReact公式が2025年2月の「Sunsetting Create React App」で非推奨化しており、React 19では新規作成が失敗するケースもあるため、新規プロジェクトでは選びません。
Viteでのプロジェクト作成(create-react-appを使わない理由)
frontendディレクトリをViteのReact+TypeScriptテンプレートで作成します。
npm create vite@latest frontend -- --template react-ts
cd frontend
npm install
Viteを選ぶ理由は開発体験の速さだけではありません。create-react-appは公式にメンテナンスが終了し、React 19の依存関係と噛み合わない問題が残っています。React側の最新の変更点はReact 19とは?18からの変更点・新機能にまとめています。Vite 8(2026年3月)はRust製バンドラのRolldownを採用し、ビルドが従来比で大きく短縮されました。
fetchとViteプロキシによるFastAPIへの接続
ReactからAPIを呼ぶコードです。呼び出し先のベースURLは環境変数で差し替えられるようにし、開発・本番でコードを変えずに済ませます。
// frontend/src/App.tsx
import { useEffect, useState } from "react";
const API_BASE = import.meta.env.VITE_API_BASE ?? "";
export default function App() {
const [msg, setMsg] = useState("");
useEffect(() => {
fetch(`${API_BASE}/api/hello`)
.then((r) => r.json())
.then((d) => setMsg(d.message));
}, []);
return <h1>{msg}</h1>;
}
CORS設定の代わりに、Viteのプロキシで/api宛てを8000へ転送する方法もあります。この場合フロントからは同一オリジンに見えるため、開発中はCORS設定なしでも動きます。
// frontend/vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
server: {
host: true, // コンテナ外からアクセスできるようにする
port: 5173,
proxy: {
// Composeではサービス名 api。ローカルで直接動かす場合は localhost:8000
"/api": "http://api:8000",
},
},
});
CORSMiddlewareとViteプロキシは、開発ではどちらか一方で足ります。プロキシは本番のリバースプロキシ構成と発想が同じで移行がスムーズ、CORSは連携の仕組みを明示できる、という違いです。認証Cookieを使うならCORS+allow_credentialsの理解が本番でも必要になるため、本記事ではCORSを軸に進めます。
Docker Composeによる開発環境の統合
ここまでを1コマンドで起動できるようにします。各サービスのDockerfileを用意し、compose.yamlでapi・web・dbを束ねます。
バックエンドとフロントエンドのDockerfile
APIはPythonの軽量イメージにライブラリを入れ、uvicornを起動します。開発用に--reloadを付けます。
# backend/Dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
フロントは本番用に「ビルド→Nginx配信」のマルチステージにします。開発では後述のCompose側でNode公式イメージを直接使うため、このDockerfileは本番配信用と割り切ります。
# frontend/Dockerfile(本番配信用)
FROM node:22-slim AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:1.27-alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx/default.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
compose.yamlでのapi・web・dbの連携
開発用compose.yamlです。webはNode公式イメージでVite開発サーバを起動し、ソースをバインドマウントしてホットリロードを効かせます。dbはPostgreSQLを立て、apiはdepends_onでdbの後に起動します。
# compose.yaml(開発用・version: キーは不要)
services:
db:
image: postgres:17
environment:
POSTGRES_PASSWORD: devpass
POSTGRES_DB: appdb
volumes:
- db-data:/var/lib/postgresql/data
api:
build: ./backend
environment:
DATABASE_URL: postgresql://postgres:devpass@db:5432/appdb
ports:
- "8000:8000"
volumes:
- ./backend:/app
depends_on:
- db
web:
image: node:22-slim
working_dir: /app
command: sh -c "npm install && npm run dev -- --host"
ports:
- "5173:5173"
volumes:
- ./frontend:/app
depends_on:
- api
volumes:
db-data:
起動はdocker compose up --buildです(ハイフン付きの旧v1docker-composeは非推奨・更新終了で、現在はv2のdocker composeが標準)。apiからdbへは、サービス名dbがそのままホスト名になるためdb:5432で届きます。dbサービスに接続するモデル層は、SQLModelやFastAPIとSQLAlchemyでのモデル構築を参照してください。ここでdbを最初からComposeに入れておくと、React・FastAPI・PostgreSQLをまとめて扱う「react fastapi postgresql」型の構成にそのまま拡張できます。
本番でCORSを畳む:同一オリジン化の考え方
開発で使ったCORS設定をそのまま本番へ持ち込まないことが、この構成で最も事故の多い分岐点です。本番はリバースプロキシを1枚挟み、/をReactのビルド成果物、/apiをFastAPIへ振り分けて、ブラウザから見えるオリジンを1つに統一します。同一オリジンになれば本番ではCORSそのものが不要になり、allow_originsのワイルドカード問題も起きません。
Reactはnpm run buildで生成したdistを配信対象に置き、Vite開発サーバは本番では動かしません。APIコンテナは外部にポートを開かず、プロキシ経由でのみ到達させます。コンテナ化して本番へデプロイするまでの具体的な手順(本番用Compose、配信用イメージのビルド、公開ポートの絞り込み)はDockerを用いたFastAPIとReactのコンテナ化とデプロイ方法で扱っています。本記事は連携(CORS・API接続・プロジェクト構成)に絞ります。
どうしてもフロントとAPIを別オリジンに分ける場合は、許可オリジンを本番ドメインで具体的に列挙し、CookieをhttpOnly+Secure+SameSiteで発行します。本番でallow_origins=["*"]のまま認証Cookieを扱う構成は採用すべきではありません。前述のとおりCORS仕様で資格情報付きリクエストとワイルドカードは両立せず、動いたとしてもCSRFの観点で危険です。API URLはVITE_API_BASEのような環境変数でビルド時に注入し、ソースへ直書きしません。
よくある質問
ReactとFastAPIは同じポートで動かせますか?
プロセスとしては別ポート(例:Reactが5173、FastAPIが8000)で動きますが、利用者から1つのポートに見せることはできます。開発ではViteのproxyで/apiを8000へ転送し、本番ではNginxのリバースプロキシで/をフロント、/apiをFastAPIへ振り分けます。これで見かけ上は同一オリジン・同一ポートになり、CORSも不要になります。
CORSエラーが出るのはなぜですか?どう直しますか?
ReactとFastAPIのオリジン(ホストやポート)が異なると、ブラウザがセキュリティ上レスポンスをブロックするためです。直し方は2つあります。FastAPI側でCORSMiddlewareにReactのオリジンを許可登録する方法と、ViteのproxyやNginxのリバースプロキシで同一オリジンに寄せる方法です。認証Cookieを使う場合は前者でallow_credentials=Trueと具体的なオリジン指定を組み合わせます。
create-react-appは今も使えますか?
新規プロジェクトでは推奨されません。React公式が2025年2月14日に「Sunsetting Create React App」を公表して非推奨化し、React 19では依存関係の不整合で新規作成が失敗するケースがあります。公式はViteなどのビルドツールやフレームワークへの移行を案内しています。本記事もViteを前提にしています。
docker composeでPostgreSQLも一緒に起動できますか?
できます。compose.yamlにdbサービスとしてpostgresイメージを追加し、apiのdepends_onにdbを指定するだけです。api側の接続先はサービス名がホスト名になるためdb:5432で届きます。React・FastAPI・PostgreSQLを1コマンドで立ち上げる構成になり、DBを含むフルスタック開発をローカルで完結できます。
本番ではReactのビルド成果物をどこに置きますか?
npm run buildで生成されるdistディレクトリを、Nginxの配信ディレクトリ(/usr/share/nginx/html)へコピーします。フロントエンドのDockerfileをマルチステージにし、ビルド段の成果物を配信段のNginxへ渡すのが定番です。Vite開発サーバを本番で動かすことはしません。