ReactとDjangoを連携する方法|REST API・CORS・JWT認証の実装手順
ReactとDjangoの連携は、Djangoを「JSONを返すAPIサーバー」、Reactを「そのJSONを表示するSPA」として役割分担させる構成が基本です。この形にすると詰まる箇所はほぼ決まっていて、Django REST Framework(DRF)でのエンドポイント作成、CORSの許可設定、axiosでのリクエスト、そしてログインを伴うアプリでのJWT認証の4点に集約されます。本記事は、この4点を最短で動かすための設定とコードを、実際のプロジェクト構成に沿って解説します。
まとめ:ReactとDjango連携の要点
新規開発なら、DjangoはDRFでAPI専用にし、ReactはViteで作った独立したSPAにする「フロントエンド分離」構成を選びます。連携の実装は次の順に進めれば動きます。
- API:DRFの
ModelViewSetとDefaultRouterでCRUDエンドポイントを自動生成する。 - CORS:
django-cors-headersで、Reactの開発サーバー(localhost:5173)だけを許可する。全許可は本番で使わない。 - リクエスト:Reactは
axiosでGET/POSTを送り、useEffectとuseStateでレスポンスを画面に反映する。 - 認証:ログインが要るならSimpleJWTでトークンを発行し、Reactは
Authorizationヘッダーに付与する。
以降で、構成の選び方から各設定の具体的なコードまで順に見ていきます。
ReactとDjangoの構成パターンと選び方
「組み合わせる価値があるか」は構成次第で答えが変わります。ReactとDjangoの組み合わせ方には大きく2通りあり、開発の前提が違います。ここを最初に決めないと、後述のCORS設定が要る/要らないも変わってきます。
フロントエンド分離(React+DRF)を基本にすべきケース
ReactをViteで作った独立アプリにし、DjangoはDRFでJSON APIだけを返す構成です。フロント(例:localhost:5173)とバック(例:localhost:8000)でオリジンが分かれるためCORS設定が必須になりますが、画面遷移が多いSPA・スマホアプリとのAPI共用・フロントとバックを別チームで開発する場合はこの構成が向きます。新規開発では原則こちらを選んで問題ありません。本記事もこの構成を前提に進めます。
Djangoテンプレートに部分的にReactを埋め込む構成が向くケース
既存のDjangoアプリ(テンプレートでHTMLを返している)の一部だけをReact化する場合は、Djangoが返すHTMLの中にReactのビルド済みJSを読み込む形にします。同一オリジンで完結するためCORSは不要ですが、ルーティングや状態管理はページ単位に限定されます。「Django資産があり、特定画面だけリッチにしたい」ときの現実解で、フルSPA化の前段としても使えます。
開発環境の構築(Django+React)
バックエンドとフロントエンドを別ディレクトリで立ち上げます。Djangoは仮想環境、Reactは非推奨化されたcreate-react-appではなくViteで作成します。
Djangoプロジェクトと必要ライブラリのセットアップ
仮想環境を作り、連携に使う4つのライブラリ(DRF・CORS・JWT)をまとめて入れます。
python -m venv venv
source venv/bin/activate
pip install django djangorestframework django-cors-headers djangorestframework-simplejwt
django-admin startproject config .
python manage.py startapp api
Django本体は5.2 LTS(2028年4月までサポート)を基準にすれば長く使えます。startproject config . の末尾のドットで、余分な入れ子ディレクトリを作らずカレントに展開します。
ViteでReactプロジェクトを作成する(create-react-app非推奨化への対応)
React公式は2025年2月にcreate-react-appを非推奨とし、新規プロジェクトはViteなどのビルドツールを推奨しています。以下でReactアプリとaxiosを用意します。
npm create vite@latest frontend -- --template react
cd frontend
npm install
npm install axios
npm run dev
Viteの開発サーバーは既定で http://localhost:5173 で起動します。このポートが、次のCORS設定で「許可するオリジン」になります。
Django REST FrameworkでAPIを構築する
DRFは、DjangoのモデルをJSONを返すREST APIに変換するための標準的なライブラリです。役割は「シリアライザーでモデルをJSON化」「ViewSetとルーターでURLを生成」の2つに分かれます。DRFの全体像や導入の利点はDjango REST Frameworkとは?その特徴と利点を徹底解説で補足しています。
シリアライザーでモデルをJSON化する
シリアライザーは、DjangoモデルとJSONを相互変換するクラスです。ModelSerializerを使えば、対象モデルと公開するフィールドを指定するだけで済みます。
from rest_framework import serializers
from .models import Task
class TaskSerializer(serializers.ModelSerializer):
class Meta:
model = Task
fields = ["id", "title", "done"]
fieldsは"__all__"でも動きますが、パスワードや内部フラグを誤って公開しないよう、返す列を明示するのが安全です。フィールド指定やバリデーションの詳細はDRF Serializerの役割とは?Pythonでの基本的な使い方を解説を参照してください。
ViewSetとルーターでエンドポイントを自動生成する
ModelViewSetは一覧・取得・作成・更新・削除の5操作をまとめて提供し、DefaultRouterがそれらのURLを自動で割り当てます。
from rest_framework import viewsets
from rest_framework.routers import DefaultRouter
from .models import Task
from .serializers import TaskSerializer
class TaskViewSet(viewsets.ModelViewSet):
queryset = Task.objects.all()
serializer_class = TaskSerializer
router = DefaultRouter()
router.register(r"tasks", TaskViewSet)
urlpatterns = router.urls
このルーターはプロジェクトのurls.pyでpath("api/", include("api.urls"))のようにapi/配下へマウントします。こうすると/api/tasks/(一覧・作成)と/api/tasks/{id}/(個別操作)になり、後述のaxiosのベースURL/api/と一致します。settings.pyのINSTALLED_APPSに"rest_framework"と自作アプリの追加を忘れないようにします。
CORSとCSRFの設定(連携で最初に詰まる箇所)
フロントエンド分離構成でほぼ全員がつまずくのがCORSエラーです。原因はブラウザのオリジン制限で、Django側で許可オリジンを明示すれば解消します。あわせて、SPAでは似た用語のCSRFと混同しやすいため、違いを整理しておきます。
django-cors-headersで許可オリジンを設定する
settings.pyに3か所追記します。ミドルウェアの位置が重要で、CorsMiddlewareはCommonMiddlewareより前に置きます。
INSTALLED_APPS = [
# ...
"corsheaders",
]
MIDDLEWARE = [
"corsheaders.middleware.CorsMiddleware",
"django.middleware.common.CommonMiddleware",
# ...
]
CORS_ALLOWED_ORIGINS = [
"http://localhost:5173",
]
ここで許可するのはReactの開発サーバーのオリジンです。CORS_ALLOW_ALL_ORIGINS = Trueは手早く動きますが、任意サイトからのアクセスを許すことになるため本番では使わず、信頼するオリジンだけを列挙します。CORSの仕組み自体はクロスオリジンリソース共有(CORS)とは何か?その基本概念と重要性を解説で詳しく扱っています。
SPAではCORSとCSRFを取り違えない
CORSは「別オリジンからのアクセスを許すか」の話、CSRFは「なりすましリクエストを防ぐか」の話で、対象が違います。JWTのようにトークンをAuthorizationヘッダーで送る認証では、Cookieに依存しないためCSRF対策の優先度は下がります。逆に、DjangoのセッションCookie認証をSPAから使う場合はCSRFトークンの送信が必要になります。「JWTならCSRF不要、セッションCookieならCSRF必要」と切り分けると迷いません。CSRFの攻撃手法とリスクはCSRF(クロスサイトリクエストフォージェリ)とは何か?概要とそのリスクについて解説で確認できます。
ReactからDjango APIにリクエストを送る
APIが用意できたら、Reactから呼び出します。fetchでも可能ですが、ベースURL設定や後述のトークン付与が書きやすいため、ここではaxiosを使います。
axiosでGET・POSTを送る
ベースURLを持つaxiosインスタンスを1つ作り、API関数を分離しておくと再利用しやすくなります。
import axios from "axios";
const api = axios.create({ baseURL: "http://localhost:8000/api/" });
export async function fetchTasks() {
const res = await api.get("tasks/");
return res.data;
}
export async function addTask(title) {
const res = await api.post("tasks/", { title, done: false });
return res.data;
}
取得はGET、作成はPOST、更新はPUT/PATCH、削除はDELETEと、DRFのViewSetがHTTPメソッドと操作を対応づけています。POSTでは第2引数のオブジェクトがリクエストボディとしてJSON送信されます。
useEffectとuseStateでレスポンスを画面に反映する
取得したデータはコンポーネントの状態に入れて描画します。読み込み中と失敗時の表示も同時に用意しておくと、通信の遅延やAPI停止でも画面が壊れません。
import { useEffect, useState } from "react";
import { fetchTasks } from "./api";
function TaskList() {
const [tasks, setTasks] = useState([]);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchTasks()
.then(setTasks)
.catch(() => setError("取得に失敗しました"))
.finally(() => setLoading(false));
}, []);
if (loading) return <p>読み込み中</p>;
if (error) return <p>{error}</p>;
return (
<ul>
{tasks.map((t) => (
<li key={t.id}>{t.title}</li>
))}
</ul>
);
}
useEffectの第2引数を空配列にすると、初回マウント時に1度だけAPIを呼びます。catchでエラーメッセージ、finallyでローディング解除を必ず行うのが、実運用で崩れないコツです。
JWT認証でAPIを保護する
ログインが必要なアプリでは、誰でもAPIを叩ける状態は避けます。フロントエンド分離構成では、サーバーにセッションを持たせないトークン方式のJWTが扱いやすく、SimpleJWTで実装します。
SimpleJWTでトークン発行エンドポイントを用意する
DRFの認証クラスをJWTに切り替え、ログイン用とトークン更新用のURLを追加します。
# settings.py
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework_simplejwt.authentication.JWTAuthentication",
),
}
# urls.py
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
)
urlpatterns += [
path("api/token/", TokenObtainPairView.as_view()),
path("api/token/refresh/", TokenRefreshView.as_view()),
]
/api/token/にユーザー名とパスワードをPOSTすると、アクセストークンとリフレッシュトークンが返ります。ViewSet側にpermission_classes = [IsAuthenticated]を付ければ、そのエンドポイントは認証必須になります。上の設定は認証をJWTのみに絞るため、開発中にDRFのブラウザブルAPIからログインしたい場合は"rest_framework.authentication.SessionAuthentication"も併記します。SimpleJWTは執筆時点で5.5.1が最新です。
Reactでトークンを保存しAuthorizationヘッダーへBearer付与する
ログインで受け取ったトークンを保存し、以降のリクエストに自動で付けます。axiosのリクエストインターセプターを使うと、API関数ごとに書かずに済みます。
api.interceptors.request.use((config) => {
const token = localStorage.getItem("access");
if (token) config.headers.Authorization = `Bearer ${token}`;
return config;
});
リフレッシュトークンと保存先のセキュリティ判断
アクセストークンは有効期限が短いため、切れたら/api/token/refresh/にリフレッシュトークンをPOSTして再取得します。保存先は判断が分かれますが、localStorageはXSSでトークンを盗まれるリスクがあるため、機密性の高いアプリではリフレッシュトークンをhttpOnly Cookieに置き、アクセストークンはメモリ保持にする方が安全です。学習用や社内ツールならlocalStorageでも実用上は動きますが、公開サービスではXSS対策とセットで設計してください。
よくある質問
ReactとDjangoの認証は何を使えばよいですか?
フロントエンドを分離したSPA構成なら、SimpleJWTによるJWT認証が標準的です。サーバーにセッションを持たせず、Reactがトークンをヘッダーで送るだけで済むためです。DjangoのテンプレートにReactを埋め込む同一オリジン構成なら、DjangoのセッションCookie認証(+CSRFトークン)でも構いません。
ReactとDjangoはどちらから学ぶべきですか?組み合わせる価値はありますか?
API側の設計が先に決まるため、Django(DRF)でエンドポイントを作れるようになってからReactで表示を組むと進めやすいです。組み合わせる価値は構成によります。画面遷移の多いSPAやモバイルとのAPI共用があるなら分離構成の価値は高く、静的に近いサイトならDjangoテンプレートだけで十分なこともあります。
CORSエラーが消えないときは何を確認しますか?
まずCORS_ALLOWED_ORIGINSにReactの実際の起動オリジン(Viteならhttp://localhost:5173)が入っているかを確認します。次にMIDDLEWAREでCorsMiddlewareがCommonMiddlewareより前にあるか、末尾スラッシュの有無でURLがずれていないかを見ます。ブラウザの開発者ツールのネットワークタブで、失敗したリクエストのオリジンとレスポンスヘッダーを照合すると原因を特定できます。
create-react-appはもう使えないのですか?
2025年2月にReact公式が非推奨化しました。既存プロジェクトが動かなくなるわけではありませんが、新規ではVite(本記事の構成)やNext.jsなどのフレームワークが推奨されます。Viteはビルドと開発サーバーの起動が速く、Django APIと組み合わせるSPA用途に適しています。
本番環境へのデプロイはどう構成しますか?
フロント(Reactのビルド成果物)とバック(Django API)を分けてデプロイするのが基本です。Reactはnpm run buildで生成した静的ファイルをCDNや静的ホスティングに置き、DjangoはAPIサーバーとして稼働させます。本番ではCORSの許可オリジンを本番ドメインに変更し、HTTPSを前提にトークンの保存先を見直してください。デプロイ先(クラウドやPaaS)ごとの手順は本記事の範囲を超えるため、各サービスの公式ドキュメントに従います。