【DRF入門】Django REST FrameworkとReactアプリを作ってみた

API開発の勉強をしたくて、Django REST Framework(以下、DRF)を使ってみました。

また、DRFで作ったAPIを外部から操作してみたく、
その外部アプリをReactで作りました。

今回は、それらの開発を通して学んだことを記載致します。

# 想定読者
- Django REST Framework（DRF）って何？　という人。DRFをこれから学んでみたい人
- Pythonの基本的な文法はなんとなく知っているよ、という人
- Djangoの基本的なことはなんとなく知っているよ、と言う人（「URLディスパッチャ」「ビュー」「モデル」など）
- ローカルPCにPythonが入っている人

# 各ツールのバージョン
使用したツールのバージョンは以下になります

【DRFのAPI】
| Python | 3.11.4 |
|:-----------|------------:|
| Django       | 4.2.4        |
| djangorestframework     | 3.14.0      |
| python-dotenv     | 1.0.1      |

Reactアプリで使用している各ツールのバージョンは以下になります。
```
(base) y-kato@ykpc check-api-react % cat package.json | grep react
  "name": "check-api-react",
    "@testing-library/react": "^13.4.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-scripts": "5.0.1",
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
      "react-app",
      "react-app/jest"
```
※　後ほど説明しますが「check-api-react」は
今回私が作成したReactサンプルアプリの名称です。
check-api-reactのプロジェクト直下で上記のコマンドを実行し、Reactのバージョンを確認しています。

# そもそもAPIとは？

APIとは
「Application Programming Interface」の略で、
ソフトウェアやアプリケーションが他のソフトウェアやサービスから何か依頼された際に、**「受付窓口」の役割をする機能**。

例えば、今流行りのChatGPTも、「OpenAI API」を提供しています。
このAPIを利用することで、
自作アプリからOpen APIにリクエスト（要求）を送り、
その要求に応じたレスポンス（返答）を受け取ることができます。

APIを利用することで、
自分で一から実装することなく、外部のアプリから便利な機能を呼び出し、操ることができます。

<details><summary>専門用語の説明</summary>

```
・ リクエスト（Request）: サービスに対して何かを求める行為。例えば、特定のデータを要求すること。
・ 応答（Response）: リクエストに対するサービスの返答。データや状態の情報を含むことが多い。
```
</details>



# Django REST Framework（DRF）とは？
今回はそのAPIを
DRFを使って作ってみました。

DRFは、
PythonのフレームワークであるDjangoの追加機能のようなもので、
APIの開発を容易にするためのツールです。

DRFを使うことでAPI開発を効率よく進めることができます。

<details><summary>RESTとは何か？　ChatGPTに聞いてみた</summary>

RESTについては今回の記事のメイントピックではないので、説明割愛させていただきます。
以下、ChatGPTさんに聞いた回答です。
今度もっと、初心者でも分かるような記事を書きたいと思います
```
REST（Representational State Transfer）は、
ウェブ上で情報をやり取りするための一つの方法です。

簡単に言うと、ウェブサイトやアプリケーションがインターネットを通じてデータを交換する際のルールセットのようなものです。
RESTを使うことで、異なるコンピューターシステム同士がスムーズに「会話」し合い、情報を共有できるようになります。

RESTでは主に以下の原則が重視されます：
シンプルなURL（ウェブアドレス）を使用してリソース（ウェブ上の情報やサービス）を表現します。
例えば、https://example.com/usersは「users」という情報にアクセスするためのURLです。

標準的なHTTPメソッドを使ってリソースに対する操作を行います。
これには、取得（GET）、作成（POST）、更新（PUT）、削除（DELETE）などがあります。
たとえば、ウェブサイトから特定の情報を取得したいときはGETメソッドを使用します。

ステートレスな通信：各リクエストは独立しており、過去の通信からの情報を保持しません。
これにより、システムがシンプルで扱いやすくなります。

データ交換形式：リソースの状態（データ）は、HTML、XML、JSONなどの標準的なインターネット形式で送受信されます。
最も一般的に使われるのはJSONで、そのシンプルさと読みやすさから多くのウェブAPIで採用されています。

RESTのアプローチを採用することで、異なる技術を使用して開発されたアプリケーション間でも、データを簡単にやり取りできるようになります。
これは、インターネットがつながるあらゆるデバイスでスムーズな情報交換を可能にする重要な要素です。
```
</details>

# DRFで簡単なAPIを作ってみる
以下、開発の手順を記載します。

### DjangoとDRFをインストール
以下コマンドをターミナルで実行し、インストールします。
```
pip install django djangorestframework
```

### Djangoプロジェクトを作成
以下コマンドを実行していきます。
```
mkdir drf-spa-sample
cd drf-spa-sample
django-admin startproject config .
```
mkdirは、ディレクトリを作成するコマンドです。
今回はアプリ名を「def-spa-sample」としています。

cdで、上で作成したディレクトリ直下に移動しています。

django-adminは、Djangoの開発時に使える便利なコマンドをまとめたツールです。
このコマンドは、Djangoをインストール（pip install django）したら使えるようになります。

「django-adming startproject config .」は、
Djangoプロジェクトに必要なファイル群を、configディレクトリに入れて現在のディレクトリ直下「.（ドット）」に配置するという意味になります。

### Djangoアプリを作成
プロジェクト直下（今回は「drf-spa-sample」）で、以下コマンドを実行します
```
python manage.py startapp api
```

### コードを書く
ここからは実際にコードを書いていきます。

以下に今回作成および修正したファイルを記載致します。

 ### モデルの定義
```python api/models.py
from django.db import models # Djangoのデータベース機能（モデル）を使うために、modelsモジュールをインポート

class Item(models.Model): # Itemという名前の新しいモデル（データベースのテーブルに対応）を作成しており、models.Modelを継承する
    # nameというフィールド（データベースの列に対応）を定義している。文字列（CharField）で最大長は100文字
    name = models.CharField(max_length=100) 
    # descriptionというフィールドを定義。長いテキスト（TextField）を保存できるようにしている
    description = models.TextField() 

```

### シリアライザの定義
モデルインスタンスをJSONに変換するシリアライザを定義してます。
serializers.pyを新たにファイル作成し、以下コードを記載
```python api/serializers.py
from rest_framework import serializers # Django REST Frameworkのserializersモジュールをインポート
from .models import Item # 同じディレクトリにあるmodels.pyファイルからItemモデルをインポート

'''
ItemモデルのデータをJSON形式などに変換するクラス。
簡単にシリアライズ（データ構造を連続的な形に変換）・デシリアライズ（連続的なデータを元のデータ構造に戻す）できるようにするためのItemSerializerクラスを定義しており、
serializers.ModelSerializerを継承しています
'''
class ItemSerializer(serializers.ModelSerializer):
    # ItemSerializerクラスの設定を行う内部クラスMetaを定義
    class Meta:
        # シリアライザが扱うモデルをItemに指定しています
        model = Item
        # シリアライズされるときに含めるフィールドをid、name、descriptionに限定する
        fields = ['id', 'name', 'description']
```

### ビューの作成
DRFのviewsetsを使用して、CRUD操作をできるようにします。
```python
from rest_framework import viewsets # DRFからviewsetsをインポート。ウェブページの表示やデータ処理などの一連の操作をひとまとめにしたクラス（=ビューセット）
from .models import Item # 同じディレクトリにあるmodels.pyファイルからItemモデルをインポート
from .serializers import ItemSerializer # 同じディレクトリにあるserializers.pyファイルからItemSerializerをインポート

# Itemモデルに対するCRUD（Create, Read, Update, Delete）操作を処理するItemViewSetクラスを定義
# viewsets.ModelViewSetを継承している
class ItemViewSet(viewsets.ModelViewSet):
    # Itemモデルから全てのオブジェクトを取得する
    queryset = Item.objects.all()
    # このビューセットで使用するシリアライザクラスをItemSerializerに指定している。
    # これにより、クライアントとのデータ交換時にItemオブジェクトのシリアライズ（データ形式の変換）とデシリアライズ（元のデータ形式への復元）が行われる。
    serializer_class = ItemSerializer

```

### ルーティングの設定
drf-spa-sample/urls.pyファイルを開き、DRFのルーティングを設定
```python drf-spa-sample/urls.py
from django.contrib import admin # Djangoの管理サイト機能を利用するためのモジュールをインポート
from django.urls import path, include # URLパスを定義するためのpath関数と、他のURLconfを参照するためのinclude関数をインポート
from rest_framework.routers import DefaultRouter # DRFから、デフォルトのルータークラスDefaultRouterをインポート。ViewSetとURLの自動マッピングを可能にする
from api.views import ItemViewSet # api/views.pyからItemViewSetクラスをインポート

# DefaultRouterのインスタンスを作成
router = DefaultRouter()
# ItemViewSetをitemsパスでルーターに登録している。これにより、ItemViewSetに定義された操作に基づいて、自動的にURLが生成される
router.register(r'items', ItemViewSet)

# Djangoプロジェクト全体のURLパターンを定義するリスト
urlpatterns = [
    # Djangoの管理サイトへのURLパスを定義
    path('admin/', admin.site.urls),
    # routerで生成されたすべてのURLを/api/パス以下に含めるように定義。
    # これにより、ItemViewSetの操作に対応するURLが/api/items/のようにアクセスできるようにする
    path('api/', include(router.urls)),
]

```


### settings.pyに追記
作成したアプリ(api)とDRFを使えるようにするため、settings.pyに以下を追記します
```python drf-spa-sample/settings.py
INSTALLED_APPS = [
    ...

    'rest_framework', # 追記
    'api', # 追記
]
```



### マイグレーション

manage.pyが置かれたプロジェクト直下で、以下コマンドを実行し
マイグレーション（モデルの変更をデータベースに適用）を行います
```
python3 manage.py makemigrations
python3 manage.py migrate
```

### CORSポリシー違反の解決
Webブラウザはセキュリティ上の理由から、
異なるアプリ間のリソース共有（今回はデータベースの値）を制限するCORSポリシーなるものを持っています。

今回、DRFで作成したAPIと、Reactで作成したSPA（シングルページアプリケーション）でリソースを共有するのですが、このままだとエラーになります。

この問題を解決するため、
DjangoプロジェクトでCORSポリシーを操作する以下ライブラリをインストールします。
```
pip install django-cors-headers
```

インストールが完了したらsettings.pyの
MIDDLEWAREに'corsheaders.middleware.CorsMiddleware'を追加します。
これは、'django.middleware.common.CommonMiddleware'よりも前に追加します。
```python settings.py
MIDDLEWARE = [
     ....
     ....
    "corsheaders.middleware.CorsMiddleware", # 追記
    "django.middleware.common.CommonMiddleware",
```
その後、同ファイルの末尾に以下を追記します。
```python settings.py
# CORS. Reactアプリのみ許可
CORS_ALLOW_ALL_ORIGINS = False
CORS_ALLOWED_ORIGINS = [
  'http://localhost:3000',
]
```

これでReactアプリからDRF APIのデータを取得し、画面表示できるようになります。


<details><summary>補足：なぜCorsMiddlewareを前に置くのか</summary>

'corsheaders.middleware.CorsMiddleware'を他のミドルウェアよりも前に置く理由は、
すべての入ってくるリクエストが最初にCORS（異なるオリジンからの安全なリクエストを許可する規則）のチェックを受けることを保証するため。

これによってセキュリティを強化し、許可されていないオリジンからのリクエストが、アプリケーションの他の部分に到達することを防ぐことができます

</details>

:::note warn
CORSポリシーの問題ですが、
全てのアプリからアクセスを許可する以下のような設定も可能です。
```python settings.py
CORS_ALLOW_ALL_ORIGINS = True
```
ただ、こちらはセキュリティ的に問題があるため推奨しません。
試すとしたらローカルの動作確認のみにしましょう。
:::

ここまでが、今回のDRFによるAPI開発になります。
続いて、このAPIを呼び出すReactアプリを作ります

# Reactアプリを作ってみる
以下、作成手順です。
今回はDRFがメインなので駆け足で（今度Reactについても書きます）

:::note warn

Reactアプリケーションのために、Node.jsとnpm (Node.jsのパッケージマネージャ) が必要です。
以下、Nodeとnpmがインストールされている前提で説明します。


Node.js以下[公式](https://nodejs.org/en)からNode.jsをローカルにインストールします。
まだNodeが自身のPCに入っていない場合は「LTS」をダウンロードしましょう。

Nodeをインストールできたらnpmも使えるようになるはずです
:::

###  Reactアプリの作成

以下コマンドを実行し、Reactアプリを作成。
```
npx create-react-app check-api-react
cd myapp
npm start
```

### app.jsを以下コードに書き換え

やっていることは、DRF APIで登録したデータを画面表示しているだけです。
```react app.js
import React, { useEffect, useState } from 'react';
import './App.css';

function App() {
  const [items, setItems] = useState([]);

  useEffect(() => {
    fetch('http://localhost:8000/api/items/')
      .then(response => response.json())
      .then(data => setItems(data));
  }, []);

  return (
    <div className="App">
      <ul>
        {items.map(item => (
          <li key={item.id}>{item.name}: {item.description}</li>
        ))}
      </ul>
    </div>
  );
}

export default App;

```


# 動作確認



### Djangoサーバーの起動
Djangoプロジェクトのディレクトリに移動し、以下のコマンドを実行してDjango開発サーバーを起動します。
```
python manage.py runserver
```
起動できたら
http://127.0.0.1:8000/api/items/
にアクセスしてみます。
以下のような画面が表示されます。
![スクリーンショット 2024-03-17 18.25.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2640031/d4c387b0-4883-7484-6b8f-1fb414ff0477.png)
※上記画像ではすでに「id: 1」「id: 2」とデータが登録されていますが、
初期表示の際は「[ ]」と空の状態で表示されるでしょう。

### DRF APIを呼び出し、データを登録する
上記画面のName, Descriptionのフィールドに入力し、「POST」ボタンを押せばデータ登録できますが、
今回はCurlコマンドで登録してみます。

Django開発サーバーが起動中の状態で、
以下コマンドを実行
```
curl -X POST http://localhost:8000/api/items/ -H 'Content-Type: application/json' -d '{"name": "新しいアイテム", "description": "Hello API"}'
```
![スクリーンショット 2024-03-17 18.31.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2640031/44244831-25a9-6027-ce8b-441279589e41.png)

ItemList画面の「GET」ボタンを押すか、ブラウザを再読み込みすると、登録したデータが表示されます。
![スクリーンショット 2024-03-17 18.33.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2640031/5fd5fdb8-cb4a-0ad3-6fe8-b0454aa1f546.png)

### Reactアプリからデータを取得
Reactアプリケーションのディレクトリに移動し、以下のコマンドを実行して開発サーバーを起動します。
```
npm start
```
デフォルトだと 
http://localhost:3000/
にアクセスできるかと思います。

画面が表示され、APIで登録したデータが表示されたら成功です
![スクリーンショット 2024-03-17 18.36.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2640031/f2a6bb44-125b-6113-f5a1-ee22db4993e7.png)

# まとめ
- APIとは、ソフトウェアやアプリケーションが他のソフトウェアやサービスから何か依頼された際に、**「受付窓口」の役割をする機能**
- DRFは、PythonのフレームワークであるDjangoの追加機能のようなもので、APIの開発を容易にするためのツール

今回はReactアプリでデータを表示させるだけでしたが、
次は「保存」「更新」「削除」ボタン実装して、ReactアプリからDRF APIに更新かけたりして見たいと思います。

今回作成したコードは以下Gitにあげております。
https://github.com/katoyuki1/drf-spa-sample
https://github.com/katoyuki1/-check-api-react

ここまで読んでいただきありがとうございます。


# 参考
- [OpenAI Introduction](https://platform.openai.com/docs/introduction)
- [現場で使えるDjangoの教科書](https://www.amazon.co.jp/%E7%8F%BE%E5%A0%B4%E3%81%A7%E4%BD%BF%E3%81%88%E3%82%8B-Django-%E3%81%AE%E6%95%99%E7%A7%91%E6%9B%B8%E3%80%8A%E5%9F%BA%E7%A4%8E%E7%B7%A8%E3%80%8B-%E6%A8%AA%E7%80%AC-%E6%98%8E%E4%BB%81-ebook/dp/B07GK7BWB7)
- [現場で使えるDjango REST Framework](https://www.amazon.co.jp/%E7%8F%BE%E5%A0%B4%E3%81%A7%E4%BD%BF%E3%81%88%E3%82%8B-Django-Framework-%EF%BC%88Django-%E3%81%AE%E6%95%99%E7%A7%91%E6%9B%B8%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA%EF%BC%89-ebook/dp/B07XWL8FPM)

# 今後の課題
- DRFでセキュリティを意識した実装をできるようになる
- AWSだけでなく、GCPやAzureなどのクラウドサービスでもデプロイしてみる
- 個人開発で今回学んだ技術を活用する
- 睡眠をきちんととるzzz


# 補足
GitHubにコードをあげるにあたって、
settings.pyに記載のSECRET_KEYを.envファイルに記載し、
.envを.gitignoreに記載してGitに上がらないようにしました。

以下手順です。

### 必要なライブラリのインストール
```
pip install python-dotenv
```

### .envに追記
プロジェクトの直下（manage.pyがあるとこ）に.envファイルを作成し、以下を追記
```
DJANGO_SECRET_KEY=自身のSECRET_KEYに書かれていた値
DEBUG=True
```

### .gitignoreに.envを追記

### settings.pyに以下を追記
```python settings.py
from dotenv import load_dotenv
import os

#.envファイルの内容を読み込む
load_dotenv() 
SECRET_KEY = os.getenv('DJANGO_SECRET_KEY', 'デフォルトキー')
DEBUG = os.getenv('DEBUG') == 'True'
```

あとは開発サーバーを起動し、画面が表示されれば成功です。