📝

React + Django + CORSを使ったフロントエンド / バックエンドのデータ連携

に公開

概要

現在再開している個人開発プロジェクトの一環で、以前実装した環境を一新して再構築しています。
その中で企業レベルのアプリ開発でも使われる機会が多そうな技術スタックを採用し取り入れているので、備忘も兼ねて投稿します。
JSONをデータ連携の手法として使うことを想定し、バックエンドで定義したデータ構成、生成データをフロントエンドで受け取り表示させる流れとしました。

実装機能

  • Reactを使ったデータ表示
  • DjangoでのModel構築 別記事に移動しました(下記参照)
  • CORSを使ったフロントエンド / バックエンドのデータ連携
項番 記事
1 React + Django + CORSを使ったフロントエンド / バックエンドのデータ連携(本記事)
2 Django 管理画面のカスタマイズ(後日公開)
3 Django REST framework(DRF)を使ったAPIサーバーとReactとのデータ連携(後日公開)
4 Django REST frameworkのserializersを使った外部キーモデルの参照(後日公開)
5 React + Redux / Redux Toolkitを使った非同期通信の検証(後日公開)
6 APIをテストツール「Postman」を使ったDjangoとのCRUD機能実装(設計編)(後日公開)
7 APIをテストツール「Postman」を使ったDjangoとのCRUD機能実装(実装編)(後日公開)

参考文献

  • React Routerを使ったルーティング

https://qiita.com/jima-r20/items/c7d636262a9ebf0bedbd

  • CORSでのデータ連携

https://qiita.com/shun198/items/9ebf19d8fd2c412396dd

https://morioh.com/a/18027196003c/react-json#google_vignette

  • VS Code 拡張機能: Ascii Tree Generator
    • 実装必須ではありませんが、フォルダツリーの書き出しに役立ったのでメモ書き

https://marketplace.visualstudio.com/items?itemName=aprilandjan.ascii-tree-generator

フォルダ構成

今回使用しているものをメインに抜粋

  • フロントエンド(React / JavaScript)
[Reactプロジェクト]
├── src
    ├── App.css
    ├── App.js
    ├── App.test.js
    ├── BaseApp.css
    ├── BaseApp.js
    ├── index.css
    ├── index.js
    ├── logo.svg
    ├── reportWebVitals.js
    └── setupTests.js
├── .gitignore
├── package-lock.json
├── package.json
└── Readme.md
  • バックエンド(Python / Django)
[Djangoプロジェクト]
├── [Djangoプロジェクト_meta]
│   ├── __init__.py
│   ├── asgi.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── [Djangoアプリ]
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── models.py
│   ├── tests.py
│   ├── urls.py
│   └── views.py
├── db.sqlite3
└── manage.py

実装方法

フロントエンド

React公式サイトでもReact フレームワークが推奨されており、Next.jsなどを使えばバックエンドも一括で実装できるのですが、今回はバックエンドの機能をDjangoに当てるため、ネイティブなReactを使って実装します。

  • Reactプロジェクト作成 + サーバーの立ち上げ。今回はデフォルトのIPを参照します。
    http://localhost:3000/
Reactプロジェクトフォルダ
npx create-react-app my-app
cd my-app
npm start
  • 必要パッケージをインストール
Reactプロジェクトフォルダ
npm install axios

(補足)
Reactのバージョンサポート等でエラーが吐き出される場合は、以下を試してみるのも手です。

  1. --legacy-peer-deps オプションを使用する
    npm installコマンドに--legacy-peer-depsオプションを追加して、依存関係の競合を無視してインストールします。

    npm install axios --legacy-peer-deps
    
  2. --forceオプションを使用する
    依存関係の競合を強制的に解決するために、--forceオプションを使用することもできます。

    npm install axios --force
    
  3. package.jsonの修正
    package.jsonの依存関係を手動で追加し、再度インストールを試みます。

    その後、以下のコマンドで依存関係をインストールします。

    npm install --legacy-peer-deps
    

これで、axiosがインストールされ、依存関係の競合が解消されるはずです。再度プロジェクトをビルドして、エラーが解消されたか確認してください。

  • index.js ではデフォルトの App.js の代わりに BaseApp.js を新規作成して読み込んでいます。
src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import BaseApp from './BaseApp';
  :

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <BaseApp />
  </React.StrictMode>
);

  :
  • データ表示画面の実装
src/BaseApp.js
import React, { useState, useEffect } from "react";
import Axios from 'axios';

const BaseApp = () => {

  // JSONデータを取得する
  const [projectList, setProjectList] = useState([]);

  const fetchData = async () => {
    const response = await Axios.get("http://localhost:8000/api/data");
    console.log(response);
    setProjectList(response.data);
  }

  useEffect(() => {
    fetchData();
  }, []);

  const filteredProjectList = projectList.filter((item) => {
    return item.id <= 3;
  });
  
  return (
    <div className="app">
      :
          <div className='section-wrapper'>
              {filteredProjectList.map((item) => (
                <dl key={item.id}>
                  <dt>{item.date}</dt>
                  <dd>{item.content}</dd>
                </dl>
              ))}
            </div>
          </div>

        </div>
      </div>
    </div>
  );
}

export default BaseApp;

画面表示時に必要なのは useState , useEffect で、バックエンドのデータ構成を非同期で受信し表示させる仕組みです。
なお Axios.get で指定しているURLは後述するDjango側の設定により変動します。

バックエンド

  • 環境設計: Minicondaを使用しています
# 環境作成
conda create -n pythonenv python=3.11
 
# 作成した環境に接続
conda activate pythonenv  # 環境名を設定

バックエンド用フォルダのルートディレクトリで、 DjangoプロジェクトDjangoアプリ の2点を作成します。
今回のディレクトリは下記の構造となります。

  • Djangoプロジェクト: backend_django
  • Djangoアプリ名: django_app
django-admin startproject [プロジェクト名]
cd [プロジェクト名]
python manage.py startapp [アプリ名]
  • 初回はモデルのmigrationが必要なので、ルートディレクトリで下記を実行
python manage.py makemigrations [アプリ名]
python manage.py showmigrations [アプリ名]  # migrate一覧を表示
python manage.py migrate [アプリ名]
  • Djangoサーバーの立ち上げ。今回はデフォルトのIPを参照します。
python manage.py runserver

と入力することで、 http://localhost:8000/ のURLでプレビューが可能になります。

CORSを使ったフロントエンド / バックエンドのデータ連携

  • バックエンド用フォルダのルートディレクトリで、フロントエンドとバックエンドを連携するためのパッケージ django-cors-headers をインストール
pip install django-cors-headers
  • settings.pyでCORS連携用の設定項目を追記
backend_django/backend_django/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
      :
    '[Djangoアプリ名]',
    'corsheaders',  # フロントエンドとの通信を許可するために追加
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',  # フロントエンドとの通信を許可するために追加
    'django.middleware.security.SecurityMiddleware',
      :
]

# CORSによるフロントエンドとの通信を許可するために追加
CORS_ORIGIN_WHITELIST = [
    'http://localhost:3000',  # フロントエンド側のIP
]

  • project/urls.py でアプリごとのURL定義を追加
backend_django/backend_django/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path("api/", include("django_app.urls")),
]
  • app/urls.py を新規作成
backend_django/django_app/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path("data/", views.data, name="data"),
]
  • app/views.py でデータの中身を定義
    • 本記事では data の部分がJSONの仮想データとして扱う形を取っています。
backend_django/django_app/views.py
from django.shortcuts import render
from django.http import JsonResponse

def data(request):
  data = [
    {
      "id": 1,
      "date": "2022-10-01",
      "content": "【テスト】[プロジェクト]「プロジェクト名1」デプロイされました。"
    },
    {
      "id": 2,
      "date": "2022-10-02",
      "content": "[プロジェクト]「プロジェクト名2」デプロイされました。"
    },
    {
      "id": 3,
      "date": "2022-10-03",
      "content": "[プロジェクト]「プロジェクト名3」デプロイされました。"
    },
    {
      "id": 4,
      "date": "2022-10-04",
      "content": "[プロジェクト]「プロジェクト名4」デプロイされました。"
    },
      :
  ]

  return JsonResponse(data, safe=False)

ここまで設定することで、pythonアプリのURL
http://localhost:8000/api/data
が構築できるようになります。

実装ビュー・まとめ

スクリーンショット 2024-01-10 13.44.10.png

CORSを使ってデータ連携させるためには、最低限バックエンド側でのパッケージ追加・設定をしておけば実装できることがわかりました。
今回はフロント・バックともlocalhostを使っているため双方のローカルサーバーを立ち上げておく必要がありますが、実際の現場ではVPNやクラウドサーバーに代替して設定すると言うイメージでしょうか。
最終的な構成としては、以下のような形になります。

  • バックエンド
    • settings.py でCORS設定
    • app/views.py で出力(+定義)したJSONデータをフロントで参照
    • project/urls.py, app/urls.py で参照用URLを定義
  • フロントエンド
    • Axios パッケージを使用してバックエンド側URLをデータフェッチ
    • 取得データをフィルタリング、 map でループ表示

Discussion