ReactとDjangoを連携させてWeb開発を行うための環境構築&基本的なデータ通信&PaaSデプロイ

この記事はDjangoプロジェクト内にReactを組み込んで開発するための環境構築をすることについて記述する．実行時にはDockerコンテナを使って最終的には"Hello World!"の表示と，データベースからデータを取り出すことを目標とする．その後，herokuにデプロイすることも行う．

>今回の完成したコードをgithubに公開している
>https://github.com/tarakokko3233/QiitaExample_WebApp_React-Django

# DjangoとReactの連携

### Djangoプロジェクトの作成
プロジェクトのルートディレクトリを作成．
```
$ mkdir yourprojectname
```
作成したディレクトリに移動する．
```
$ cd yourprojectname
```

環境構築用にPython仮想環境の構築を行う．

```
$ python3 -m venv venv
```

また，node.jsがインストールされていないのならば，このタイミングでインストールすること．
https://nodejs.org

:::note info
Node.jsがインストールされているかどうかの確認方法は
```
$ npm
```
で表示されればOK

:::


仮装環境を起動する．

Mac
```
$ source venv/bin/activate
```
Windows
```
$ venv\Scripts\activate
```


:::note info
のちに実行時にはDockerコンテナを使用して立ち上げるので仮の仮想環境だと思って欲しい．
:::

仮装環境に必要なライブラリをインストールする．
```
$ pip install django djangorestframework
```
Dockerコンテナ用にどのライブラリをインストールするかを記述する`requirements.txt`を作成する．
```
$ touch requirements.txt
```
`requirements.txt`に以下のライブラリを記述する．
```requirements.txt
asgiref
Django
djangorestframework
sqlparse

```

:::note info
開発中に他のPythonライブラリが必要になったらrequirements.txtに追記すること．
:::


Djangoのプロジェクトを作る．
```
$ django-admin startproject yourprojectname
```

作成したDjangoプロジェクトに移動する．
```
$ cd yourprojectname
```

次にDjangoプロジェクト上でReact開発用のDjangoアプリを作成する．
```
$ django-admin startapp frontend
```
:::note info
アプリの名前はfrontendにしたが実際はなんでもいい．後の設定のfrontendの部分を変更することを忘れないように
:::


`settings.py`の`INSTALLED_APPS`に必要なアプリを追加する．
```settings.py
INSTALLED_APPS = [
   'django.contrib.admin',
   'django.contrib.auth',
   'django.contrib.contenttypes',
   'django.contrib.sessions',
   'django.contrib.messages',
   'django.contrib.staticfiles',
   'rest_framework', # これと
   'frontend', # これを追加する
]
```

`settings.py`の`ALLOWED_HOSTS`以下のコードを追加する．
```settings.py
ALLOWED_HOSTS = ['localhost',
                '127.0.0.1']
```


この時点でデータベースにデフォルトのテーブルを適用する．
```
$ python manage.py migrate
```

### Reactのコードを入れるためのディレクトリを作成

:::note info
この章ではカレントディレクトリを`yourprojectname`(ルートディレクトリ)で実行すること
:::

Reactで作成したコードを表示するためのテンプレートを格納するためのディレクトリを作成する．
```
$ mkdir frontend/templates
```

静的ファイル(CSS，画像，main.js)格納用のディレクトリを作成する．
```
$ mkdir frontend/static
```

わかりやすいように用途ごとにディレクトリを作成する．
```
$ mkdir frontend/static/frontend
$ mkdir frontend/static/css
$ mkdir frontend/static/images
```

Reactのコードを入れるためのディレクトリを作成する．
```
$ mkdir frontend/src
$ mkdir frontend/src/components
```
### node.jsのモジュールをインストール

:::note info
この章ではカレントディレクトリを`frontend`で実行すること．
```
$ cd frontend
```

:::


はじめにNodeプロジェクトを初期化する．
```
$ npm init -y
```

複数のJavaScriptライブラリを一つにまとめるためのwebpackをインストールする．
```
$ npm i webpack webpack-cli --save-dev
```
> `npm i`は`npm install`のこと，-save-devオプションは、モジュールをローカルインストールするためのオプション

JavaScriptの最新バージョンでは最新の構文が登場するが，その構文は古いブラウザでは動作しないことがあるため，最新の構文を古いブラウザでも動くように変換するツールであるbabelをインストールする．
```
$ npm i @babel/core babel-loader @babel/preset-env @babel/preset-react --save-dev
$ npm i @babel/plugin-proposal-class-properties
```

最後にReactをインストールする．
```
$ npm i react react-dom --save-dev
$ npm i react-router-dom
```
### node.jsモジュールの設定ファイルのセットアップ
webpackのconfigファイルを作成する．
```
$ touch webpack.config.js
```

`webpack.config.js`に以下の内容を記述する．
```webpack.config.js
const path = require("path");
const webpack = require("webpack");

module.exports = {
   entry: "./src/index.js",
   output: {
       path: path.resolve(__dirname, "./static/frontend"),
       filename: "[name].js",
   },
   module: {
       rules: [
           {
               test: /\.js$/,
               exclude: /node_modules/,
               use: {
                   loader: "babel-loader",
               },
           },
       ],
   },
   optimization: {
       minimize: true,
   },
   plugins: [
       new webpack.DefinePlugin({
           'process.env.NODE_ENV' : JSON.stringify('production')
       })
   ]
}
```

次に，babelのconfigファイルを作成する．
```
$ touch babel.config.json
```

`babel.config.json`に以下の内容を記述する．
```babel.config.json
{
   "presets": [
       [
           "@babel/preset-env",
           {
               "targets": {
                   "node": "14"
               }
           }
       ],
       "@babel/preset-react"
   ],
   "plugins": ["@babel/plugin-proposal-class-properties"]
}
```

Reactのエントリーポイントである`index.js`を作成する．
```
$ touch src/index.js
```

最後に`package.json`の`"scripts"`を以下の内容に変更する．
```package.json
"scripts": {
   "test": "echo \"Error: no test specified\" && exit 1",
   "dev": "webpack --mode development --watch",
   "build": "webpack --mode production"
 }
```

>`package.json`において，もし
>`"@babel/plugin-proposal-class-properties"`が`"dependencies":`にあるときは，`"devDependencies":`に記述すること．
>>`package.json`はこのようになっているはずである．
>>```package.json
>>{
>>  "name": "frontend",
>>  "version": "1.0.0",
>>  "description": "",
>>  "main": "index.js",
>>  "scripts": {
>>    "test": "echo \"Error: no test specified\" && exit 1",
>>    "dev": "webpack --mode development --watch",
>>    "build": "webpack --mode production"
>>  },
>>  "keywords": [],
>>  "author": "",
>>  "license": "ISC",
>>  "devDependencies": {
>>    "@babel/core": "^7.24.4",
>>    "@babel/preset-env": "^7.24.4",
>>    "@babel/preset-react": "^7.24.1",
>>    "babel-loader": "^9.1.3",
>>    "react": "^18.3.1",
>>    "react-dom": "^18.3.1",
>>    "webpack": "^5.91.0",
>>    "webpack-cli": "^5.1.4",
>>    "@babel/plugin-proposal-class-properties": "^7.18.6"
>>  },
>>  "dependencies": {
>>    "react-router-dom": "^6.23.0"
>>  }
>>}
>>```


### テンプレートの作成

:::note info
この章ではカレントディレクトリを`frontend`で実行すること．
:::

Reactのコードを表示するためのテンプレートを作成する．
```
$ mkdir templates/frontend
$ touch templates/frontend/index.html
```

`index.heml`を以下のように記述する．
```index.html
<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>Document</title>
   {% load static %}
</head>
<body>
   <div id="main">
       <div id="app"></div>
   </div>

   <script src="{% static "frontend/main.js" %}"></script>
</body>
</html>
```
>もしCSSファイルを作成するのならば以下のコードをheadタグの一番最後に記述する．
>```index.html
>   <!-- cssを追加する場合 -->
>   <link rel="stylesheet" href="{% static "css/index.css" %}">
>```

簡単にReactのコードを書く．`frontend/src/components/App.js`を作成する．
```
$ touch src/components/App.js
```
```App.js
import React, { Component } from "react"
import { render } from "react-dom"

export default class App extends Component {
   constructor(props) {
       super(props);
   }

   render() {
       return <h1>Hello World!</h1>;
   }
}

const appDiv = document.getElementById("app");
render(<App/>, appDiv)
```



### ビューとエンドポイントの作成
`yourprojectname/frontend/views.py`に以下のビューを追加する．
```views.py
def index(request, *args, **kwargs):
   return render(request, 'frontend/index.html')
   
```
`yourprojectname/yourprojectname/urls.py`に以下のビューを作成する．
```yourprojectname/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
   path('admin/', admin.site.urls),
   path('', include('frontend.urls')),
]
```

`frontend`ディレクトリ内にurls.pyを作成する．
```
$ touch urls.py
```
作成した`frontend/urls.py`を以下のように編集する．

```frontend/urls.py
from django.urls import path
from .views import index

urlpatterns = [
   path('', index)
]
```

最後に`index.js`で`App.js`を読み込むようにする．
```yourprojectname/frontend/src/index.js
import App from "./components/App";
```
これで完成である．

### Dockerコンテナの作成

:::note info
この章ではカレントディレクトリを`yourprojectname`(ルートディレクトリ)で実行すること
:::


`yourprojectname`ディレクトリ(ルートディレクトリ)でDockerfileを作成する．

```
$ touch Dockerfile
```

```Dockerfile
# Node.jsの公式イメージを使用
FROM node:14 as build

# 作業ディレクトリの設定
WORKDIR /app/frontend

# package.jsonとpackage-lock.jsonをコピー
COPY frontend/package*.json ./

# Node.jsの依存パッケージをインストール
RUN npm install

# 残りのフロントエンドファイルをコピー
COPY frontend/ .

# Djangoアプリ用のイメージをベースに
FROM python:3.9

# 作業ディレクトリの設定
WORKDIR /app

# 必要なパッケージをインストール
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 残りのファイルをコピー
COPY . .

# データベースファイルを永続化するためのボリュームを作成
VOLUME /app/db_data

# ポートの公開
EXPOSE 8000

# Djangoアプリの起動コマンド
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]

```
`yourprojectname`ディレクトリ(ルートディレクトリ)で`docker-compose.yml`を作成する．


```
$ touch docker-compose.yml
```
```docker-compose.yml
services:
  backend:
    build: .
    ports:
      - 8000:8000
    volumes:
      - .:/app
      - db_data:/app/db_data

  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile.dev
    volumes:
      - ./frontend:/app
    ports:
      - 3000:3000

volumes:
  db_data:

```
`yourprojectname/frontend`ディレクトリで`Dockerfile.dev`を作成する．

```
$ touch frontend/Dockerfile.dev
```
```Dockerfile.dev
# Node.jsの公式イメージを使用
FROM node:14

# 作業ディレクトリの設定
WORKDIR /app

# package.jsonとpackage-lock.jsonをコピー
COPY package*.json ./

# Node.jsの依存パッケージをインストール
RUN npm install

# ソースコードをコピー
COPY . .

# ホットリロードサーバーを起動
CMD ["npm", "run", "dev"]

```

これで`yourprojectname`ディレクトリ(ルートディレクトリ)において，以下のコマンドを実行すればDockerコンテナをビルド&立ち上げができる．
```
$ docker compose up
```
:::note info
この時点でdockerコンテナを用いて実行をするので最初に作成したPython仮装環境であるvenvファイルは不要である．削除してよい．
:::






>**Dockerコンテナをうまく立ち上げられなかった場合**
>ターミナル(コマンドプロンプト，WindowsPowerShell)を2つ立ち上げる．
>1つはルートディレクトリで**Python仮装環境を起動したこと**を確認して以下のコマンドを実行する．
>```
>$ python manage.py runserver
>```
>もう1つは`frontend`ディレクトリで以下のコマンドを実行する．
>```
>$ npm run dev
>```

サーバーを立ち上げたら，http://localhost:8000 にアクセスする．アクセスしたら左上にHello World!と表示されていたらOK

つぎはデータのやり取りをDjangoとReactでするための設定をする

# データベースからデータを取得する
ここからはデータベースからデータを受信してテンプレートに表示する方法について記述する．今回はUsers(id,name,email)モデルを作成し，nameとemailを表示する．

### モデル，ビュー，エンドポイントの作成

:::note info
この章ではカレントディレクトリを`frontend`ディレクトリで実行する
```
$ cd frontend
```
:::



`frontend`ディレクトリでモデルを作成する．
```models.py
from django.db import models

class Users(models.Model):
    name = models.CharField(max_length=100)
    email = models.EmailField()

```
コンテナを立ち上げた状態(`docker compose up`)で以下のコマンドを実行する．これでマイグレーションファイルを作成し，データベースに適用する．
```
$ docker compose exec backend python manage.py makemigrations
$ docker compose exec backend python manage.py migrate
```
>Dockerが使えない場合は仮装環境を立ちあげて以下のコマンドを実行する．
>```
>$ python manage.py makemigrations
>$ python manage.py migrate
>```

`frontend`ディレクトリで`serializers.py`を作成する．

```
$ touch serializers.py
```

```serializers.py
from rest_framework import serializers
from .models import Users

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = Users
        fields = ['id', 'name', 'email']

```

`frontend`ディレクトリの`views.py`に追記する．
```views.py
from django.shortcuts import render
from rest_framework import viewsets
from .serializers import UserSerializer
from .models import Users

# Create your views here.
def index(request, *args, **kwargs):
   return render(request, 'frontend/index.html')

class UserViewSet(viewsets.ModelViewSet):
    queryset = Users.objects.all()
    serializer_class = UserSerializer

```

`frontend`ディレクトリの`urls.py`を以下のように変更する．
```frontend/urls.py
from django.urls import path
from .views import index
from rest_framework import routers
from .views import UserViewSet

router = routers.DefaultRouter()
router.register('api/users', UserViewSet)

urlpatterns = [
   path('', index, name='frontend')
]+ router.urls

```
### React側でJavaScriptでデータ表示する

:::note info
この章ではカレントディレクトリを`yourprojectname`(ルートディレクトリ)で実行する
:::



`frontend/src/components`ディレクトリに`UsersList.js`を作成する．

```
$ touch frontend/src/components/UsersList.js
```

```UsersList.js
// UsersList.js
import React, { useState, useEffect } from 'react';

const UsersList = () => {
 const [users, setUsers] = useState([]);

 useEffect(() => {
   const fetchUsers = async () => {
     const response = await fetch('api/users/');
     const data = await response.json();
     setUsers(data);
   };

   fetchUsers();
 }, []);

 return (
   <div>
     <h1>UsersList</h1>
     <ul>
       {users.map((user) => (
         <li key={user.id}>
           {user.name} - {user.email}
         </li>
       ))}
     </ul>
   </div>
 );
};

export default UsersList;

```

`frontend/src/components/App.js`で`UserList.js`に遷移するようのリンクを設定する．
```App.js
// App.js
import React from 'react';
import { createRoot } from 'react-dom/client';
import { BrowserRouter as Router, Route, Link, Routes } from 'react-router-dom';
import UsersList from './UsersList';

const App = () => {
  return (
    <Router>
      <div>
        <h1>Hello World!</h1>
        <nav>
          <ul>
            <li>
              <Link to="/">Home</Link>
            </li>
            <li>
              <Link to="/users">Users</Link>
            </li>
          </ul>
        </nav>

        <Routes>
          <Route path="/users" element={<UsersList />} />
        </Routes>
      </div>
    </Router>
  );
};

const container = document.getElementById('app');
const root = createRoot(container);
root.render(<App />);

```
>今回はUsersList.jsへのエンドポイントは`/users`とする

この時点で試しに`db.sqlite3`へデータを挿入すること.

:::note info
データベースにアクセスするGUIソフトウェアをインストールしておくとSQliteなどデータベースを管理しやすい.
例えばTablePlusというソフトウェアがある.
https://tableplus.com
:::



:::note info
リモートやローカルのPostgreSQLやMySQLにアクセスしてもよいがその時は`settings.py`のDATABASE設定を変更すること
:::



>たとえばDBサーバとしてHerokuPostgresを使う場合の設定は以下の記事を参照すること
>https://qiita.com/tarakokko3233/items/0961933e2b9695cb561a
:::note warn
ただし上記の記事はデプロイの仕方まで記載してあるが,今回は上記の記事の仕方でデプロイ出来ないので注意すること
:::


>データベース接続情報を直接`settings.py`に書き込むことはセキュリティ上危険なので以下の記事を参考にして環境変数を使用して欲しい
>https://qiita.com/tarakokko3233/items/bb63dbef9d91f44d3917



これで`yourprojectname`ディレクトリ(ルートディレクトリ)において，以下のコマンドを実行すればDockerコンテナをビルド&立ち上げができる．
```
$ docker compose up
```

>**`再掲`Dockerコンテナをうまく立ち上げられなかった場合**
>ターミナル(コマンドプロンプト，WindowsPowerShell)を2つ立ち上げる．
>1つはルートディレクトリで**Python仮装環境を起動したこと**を確認して以下のコマンドを実行する．
>```
>$ python manage.py runserver
>```
>もう1つは`frontend`ディレクトリで以下のコマンドを実行する．
>```
>$ npm run dev
>```

サーバーを立ち上げたら，http://localhost:8000 にアクセスする．

# PaaSにデプロイする
今回はHerokuにデプロイする．

### 静的ファイルの収集とALLOWED_HOSTSの追加
`settings.py`に静的ファイルを収集するための設定を追記する．
```settings.py
import os

ALLOWED_HOSTS = ['localhost',
                '127.0.0.1',
                '.herokuapp.com' ] #この行を追加
MIDDLEWARE = [
    # 他のミドルウェア．．．
    'whitenoise.middleware.WhiteNoiseMiddleware', #これを追加
]

STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')

STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'frontend', 'build', 'static'),
]

STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
```

WhitenoiseのApacheDeployHandler設定を追加するために，`wsgi.py`を変更する．
```wsgi.py
# wsgi.py
import os
from django.core.wsgi import get_wsgi_application
from whitenoise import WhiteNoise

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'yourprojectname.settings')

application = get_wsgi_application()
application = WhiteNoise(application)
```

 ### requirements.txtへの追記
 デプロイ用のPythonライブラリを`requirements.txt`に追記する．
 ```requirements.txt
 asgiref
Django
django-environ
gunicorn
psycopg2
djangorestframework
django-filter
whitenoise
dj-database-url
```

### runtime.txtの作成
デプロイ時のwebサーバで使うpythonバージョンを指定するファイルを`yourprojectname`ディレクトリ(ルートディレクトリ)に追加する
```runtime.txt
python-3.12.2
```


### Procfileの作成
HerokuでDjangoアプリケーションをデプロイする際に，Herokuがアプリケーションをどのように実行すべきかを指示するファイルを`yourprojectname`ディレクトリ(ルートディレクトリ)に追加する
```Procfile
web: gunicorn yourprojectname.wsgi --log-file -
react-build: npm install --prefix frontend
react-build: npm run build --prefix frontend
release: python manage.py migrate
```

ここまでのファイルをgitにプッシュする．プッシュする際にリポジトリに.gitignoreを追加することをお勧めする．
```.gitignore
# Python
*.pyc
*~
__pycache__

# Database
db.sqlite3

# Static files
static/

# Media files
media/

# Environment
.env
.venv
env/

# Node
node_modules/
npm-debug.log

# OS files
.DS_Store
Thumbs.db

# Editor files
.vscode/
.idea/

```
この状態でherokuアプリケーションにプッシュしたgitリポジトリを接続してデプロイする．
herokudashboardの使い方については以下の記事を参考にすること
https://qiita.com/tarakokko3233/items/0961933e2b9695cb561a

:::note warn
上記の記事ではデプロイするためのファイルは今回とは異なるので注意
:::


お疲れ様でした．