Django REST FrameworkでAPIを作成してみた

### 背景
以前からDjangoにおけるWebアプリケーション作成において、ReactやFlutterなどのフロントエンドのフレームワークとの結合を考えております。
今回は、それに先立ちフロントエンドとバックエンドとのやりとりをDjango REST Frameworkを利用したRESTful APIで行なうために学習した記録です。

先人たちの知恵をお借りするなどして解決できたことを、この場をお借りして感謝するとともに、大変恐縮ですが**自分のメモ**として、こちらへまとめておきます。

### はじめに
チグサさんのブログ：「チグサウェブ」の記事を参考に、Django REST Frameworkを利用して、RESTful APIを作成してみました。
大変分かりやすくまとまっており、これから学習する方にもオススメです。

今回、チグサさんの記事と異なり、dockerでの環境構築やDjangoで利用するデータベースの変更は省いています。

:::note info
今回使用する環境等のバージョン
- Windows 11 Pro 22H2
- Python 3.12.0
- Django 4.2.6
- Django REST Framework 3.14.0
- SQLite 3.43.1
:::

https://chigusa-web.com/

#### そもそもAPIとは？
Application Programming Interfaceの略。
今回作成するのはHTTPやHTTPS通信でデータのやり取りを行うことができる、Web APIです。

#### Django REST Frameworkとは？
Django REST Framework (DRF)とはRESTful API開発に特化したDjangoのフレームワークです。
DRFを使うことでよりRESTfulなWebアプリケーションを作成することができます。

[RESTful API](https://ramble.impl.co.jp/2886/)とはRESTという原則を守っているHTTP呼び出しインターフェースのことです。
Django REST Frameworkを使用することにより、Djangoフレームワークで簡単にAPIの実装ができる優れもの。
バックエンドのAPIサーバーを実装する際に大変便利です。

https://www.django-rest-framework.org/

### 1.python仮想環境の構築
Python環境に、venvにより仮想環境を構築します。（すでにPython実行環境は構築済みの前提です。）
以下のコマンドを実行します。
```terminal
python -m venv venv
```
:::note info
2つめの「venv」は仮想環境名ですので、任意で指定してください。
:::

```config:ディレクトリ構成
root
└ venv
  ├ Include
  ├ Lib
  ├ Scripts
  └ pyvenv.cfg
```

以下のコマンドで仮想環境を有効にします。
```terminal
cd venv\Scripts
activate.bat

# rootディレクトリに戻る
(venv) cd ../..
```

以下のコマンドでpipをupgradeします。
```terminal
(venv) python -m install --upgrade pip
```

### 2.Djangoのインストール
以下のコマンドでdjangoをインストールします。
```terminal
(venv) pip install django
```

https://www.djangoproject.com/

### 3.Djangoプロジェクトの作成
以下のコマンドで、Djangoプロジェクトを作成します。
最後に「.」（ドット）を付けることで、同じ階層にプロジェクトファイルを作成しています。
```terminal
(venv) django-admin startproject djangoApiApp .
```
:::note info
「djangoApiApp」はプロジェクト名ですので、任意で指定してください。
:::

```config:ディレクトリ構成
root
├ djangoApiApp  # 追加
  ├ __pycache__
  ├ __init__.py
  ├ asgi.py
  ├ settings.py
  └ wsgi.py
└ venv
  ├ Include
  ├ Lib
  ├ Scripts
  └ pyvenv.cfg
db.sqlite3
manage.py
```

### 4.Djangoのデータベースの設定
デフォルトでsqlite3が指定されていますので、今回はこのまま使います。
```python:djangoApiApp/settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}
```
:::note info
MySQLやPostgreSQLに変更するには、[Djangoの公式ドキュメント](https://docs.djangoproject.com/ja/4.2/intro/tutorial02/#database-setup)をご参照ください。
:::

言語とタイムゾーンの項目を修正します。
```python:djangoApiApp/settings.py
LANGUAGE_CODE = 'ja'       # 修正

TIME_ZONE = 'Asia/Tokyo'   # 修正
```

:::note warn
設定ファイルについては、SECRET_KEYやデータベースの接続情報などの機密情報が含まれます。
GitHubの公開リポジトリなどに、そのままプッシュしてしまうのは大変危険です。
:::

今回は、これらを管理するツールとして便利なdjango-environを使います。

#### django-environのインストール
django-environは外部モジュールなのでインストールする必要があります。
Djangoアプリケーションが動作しているPython環境で、次のコマンドを実行します。

```terminal
(venv) pip install django-environ
```

インストールはこれだけです。

https://django-environ.readthedocs.io/en/latest/

#### .envファイルの作成と記述
manage.pyと同じ階層（今回の構成ではdjandoプロジェクト直下）に「.env」という名前でファイルを作成します。


```config:ディレクトリ構成
root
├ djangoApiApp
  ├ __pycache__
  ├ __init__.py
  ├ asgi.py
  ├ settings.py
  └ wsgi.py
└ venv
  ├ Include
  ├ Lib
  ├ Scripts
  └ pyvenv.cfg
.env              # 追加
db.sqlite3
manage.py
```

このファイルに環境変数などの機密情報を分離します。

```config:.env
SECRET_KEY=django-insecure-*************************************
DEBUG=True

DATABASE_ENGINE=django.db.backends.sqlite3  # 使用するDBのエンジン（sqlite3ならdjango.db.backends.sqlite3）
DATABASE_DB=db.sqlite3                      # データベース名（sqlite3ならdb.sqlite3）
DATABASE_USER=django_user                   # defaultのままで可
DATABASE_PASSWORD=password                  # defaultのままで可
DATABASE_HOST=localhost                     # defaultのままで可
DATABASE_PORT=5432                          # defaultのままで可
```

=の前後に半角スペースが入ったり、文字列をシングルクォーテーションで囲っていたりするとエラーになるので注意しましょう。

::: note warn
他にも、APIキーなど他人に知られてはいけない情報は、ソースコードに含めないように気を付けましょう。
:::

#### settings.pyで環境変数を呼び出す
.envファイルに環境変数をsettings.pyで呼び出します。

```python:djangoApiApp/settings.py
import os                       # 追加
import environ                  # 追加
 
env = environ.Env()             # 追加
env.read_env('.env')            # 追加
 
SECRET_KEY = env('SECRET_KEY')  # 修正
DEBUG = env('DEBUG')            # 修正
 
DATABASES = {
    'default': {
        'ENGINE': env.get_value('DATABASE_ENGINE', default='django.db.backends.sqlite3'),      # 修正
        'NAME': env.get_value('DATABASE_DB', default=os.path.join(BASE_DIR, 'db.sqlite3')),    # 修正
        'USER': env.get_value('DATABASE_USER', default='django_user'),                         # 追加
        'PASSWORD': env.get_value('DATABASE_PASSWORD', default='password'),                    # 追加
        'HOST': env.get_value('DATABASE_HOST', default='localhost'),                           # 追加
        'PORT': env.get_value('DATABASE_PORT', default='5432'),                                # 追加
    }
}
```

これだけ！

### 5.Django REST Frameworkのインストール
Djangoアプリケーションが動作しているPython環境に、Django REST Frameworkをインストールします。

```terminal
(venv) pip install djangorestframework
```


### 6.アプリ追加と設定
以下のコマンドで、Djangoプロジェクトにアプリケーションを追加します。
商品マスターを想定したアプリケーションとして、APIを実装します。

```terminal
(venv) python manage.py startapp products
```

```config:ディレクトリ構成
root
├ djangoApiApp
  ├ __pycache__
  ├ __init__.py
  ├ asgi.py
  ├ settings.py
  └ wsgi.py
├ products       # 追加
  ├ __pycache__
  ├ migtarions
  ├ __init__.py
  ├ admin.py
  ├ apps.py
  ├ models.py
  ├ tests.py
  └ views.py
└ venv
  ├ Include
  ├ Lib
  ├ Scripts
  └ pyvenv.cfg
.env
db.sqlite3
manage.py
```

djangoプロジェクトのsettings.pyに、アプリケーションを追加します。

```python:djangoApiApp/settings.py
INSTALLED_APPS = [
...
    'rest_framework',  # 追加
    'products',        # 追加
]
```

:::note info
Django REST Frameworkを使えるように、「rest_framework」を指定します。
:::

### 7.Modelとテーブルの作成
テーブル定義に合わせたモデルを作成します。
今回は商品を想定したモデルとしました。

```python:products/models.py
# 以下を追加
class Product(models.Model):

    name = models.CharField(max_length=200)
    price = models.IntegerField()
```

以下のコマンドでマイグレーションファイルを作成します。

```terminal
(venv) python manage.py makemigrations products
```

以下のファイルが自動で作成されます。
```python:products/migrations/0001_initial.py
# Generated by Django 4.0.3 on 2022-04-06 13:44

from django.db import migrations, models


class Migration(migrations.Migration):

    initial = True

    dependencies = [
    ]

    operations = [
        migrations.CreateModel(
            name='Product',
            fields=[
                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('name', models.CharField(max_length=200)),
                ('price', models.IntegerField()),
            ],
        ),
    ]
```

以下のコマンドでマイグレーションを実行し、テーブルを作成します。

```terminal
(venv) python manage.py migrate
```

#### 管理画面での確認
Djangoの管理画面から確認してみましょう。
まずは、管理画面にログインするためのユーザを作成します。

以下のコマンドを実行します。
```terminal
(venv) python manage.py createsuperuser
```
:::note info
ユーザ名、メールアドレス、パスワードを聞かれますので、任意で指定してください。
:::

以下のコマンドで開発サーバーを起動します。
```terminal
(venv) python manage.py runserver
```

以下のURLにアクセスします。
```
http://localhost:8000/admin/
```

管理画面が表示されますので、先ほど作成したユーザでログインします。
![スクリーンショット 2023-10-07 210240.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/902793/6ba048b0-a7c8-f895-13dc-2933902be5a4.png)

管理画面にアクセスすることができました。
![スクリーンショット 2023-10-07 181116.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/902793/eef31453-1f39-eb3b-97e3-a5ea36531e41.png)

今回テーブルを作成した商品マスタを編集できるようするため、以下のファイルを修正します。

```python:products/admin.py
from django.contrib import admin

# Register your models here.

from .models import Product   # 追加

admin.site.register(Product)  # 追加
```

画面を更新すると、商品テーブルのマスタメンテが追加されました。
![スクリーンショット 2023-10-07 181303.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/902793/9d7125f5-20db-479a-e96f-b53a22c9acae.png)

ここから新規登録などができます。
![スクリーンショット 2023-10-07 181410.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/902793/549970f6-7a40-5fbd-c017-24bbf40f1c06.png)

一覧を表示すると、「xx object (1)」のように表示されます。
![スクリーンショット 2023-10-07 181428.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/902793/95aea68a-0496-67a0-8648-bb6001150ce1.png)

名前が表示されるように、モデルを修正します。

```python:products/models.py
from django.db import models

# Create your models here.

class Product(models.Model):

    name = models.CharField(max_length=200)
    price = models.IntegerField()

    # 以下を追加
    def __str__(self):
        return self.name
```

画面を更新すると、一覧に名前が表示されるようになりました！
![スクリーンショット 2023-10-07 181538.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/902793/b8c6d3ad-c4c2-8800-1fe6-ec7902a07a6d.png)

:::note info
管理画面を通じてAPIの動作を確認できます。
:::

### 8.Serializerクラスの作成
APIでデータのやり取りを行うためのSerializer（シリアライザー）を設定するため、以下のファイルを作成します。

```python:products/serializers.py
# 以下を追加
from rest_framework import serializers
from .models import Product


class ProductSerializer(serializers.ModelSerializer):
    class Meta:
        model = Product
        fields = ['name', 'price']
```

```config:ディレクトリ構成
root
├ djangoApiApp
  ├ __pycache__
  ├ __init__.py
  ├ asgi.py
  ├ settings.py
  └ wsgi.py
├ products
  ├ __pycache__
  ├ migtarions
  ├ __init__.py
  ├ admin.py
  ├ apps.py
  ├ models.py
  ├ serializers.py
  ├ tests.py
  └ views.py
└ venv
  ├ Include
  ├ Lib
  ├ Scripts
  └ pyvenv.cfg
.env
db.sqlite3
manage.py
```

Django REST Frameworkの最大の特徴はこのSerializerと言っても過言ではありません。
Serializerについては、「Django REST Framework の特徴（Serializer）」のセクションでの説明が参考になります。

https://qiita.com/pythonista/items/1babe212609aa159af77#django-rest-framewrk-%E3%81%AE%E7%89%B9%E5%BE%B4serializer

### 9.Viewの作成
APIの処理を、ViewSetを使用して実装します。
これにより例えば、一覧や詳細データの取得、新規登録、更新、削除などが処理できるようになります。

ここではModelViewSetを使用して、以下のように実装します。

```python:products/views.py
# 以下を上書き
from rest_framework import viewsets
from .models import Product
from .serializers import ProductSerializer


class ProductViewSet(viewsets.ModelViewSet):

    queryset = Product.objects.all()
    serializer_class = ProductSerializer
```

:::note info
ModelViewSetは、以下が提供されます。
- list()
- retrieve()
- create()
- update()
- partial_update()
- destroy()
:::

### 10.URLの追加
APIを呼び出すためのURLをアプリケーションのurls.pyに追加します。
ViewSetをルーターに登録します。

```python:products/urls.py
# 以下を追加
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import ProductViewSet

router = DefaultRouter()
router.register('products', ProductViewSet)

urlpatterns = [
    path('', include(router.urls)),
]
```

```config:ディレクトリ構成
root
├ djangoApiApp
  ├ __pycache__
  ├ __init__.py
  ├ asgi.py
  ├ settings.py
  └ wsgi.py
├ products
  ├ __pycache__
  ├ migtarions
  ├ __init__.py
  ├ admin.py
  ├ apps.py
  ├ models.py
  ├ serializers.py
  ├ tests.py
  ├ urls.py        # 追加
  └ views.py
└ venv
  ├ Include
  ├ Lib
  ├ Scripts
  └ pyvenv.cfg
.env
db.sqlite3
manage.py
```

djangoプロジェクトのurls.pyに追加します。
パスは任意で指定できます。今回は「products/」としました。
```python:djangoApiApp/urls.py
from django.contrib import admin
from django.urls import path, include  # includeを追加

urlpatterns = [
    path('admin/', admin.site.urls),

    # API
    path('products/', include('products.urls')),  # 追加
]
```

### 11.APIの確認
#### API画面
起動していない場合は、以下のコマンドで開発サーバーを起動しましょう。
```terminal
(venv) python manage.py runserver
```

以下のURLにアクセスします。
```
http://localhost:8000/products/
```

APIを操作するための画面が表示されます。
![スクリーンショット 2023-10-07 183500.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/902793/a1de3fde-fa98-4e6a-191a-312acb2177ae.png)

:::note info
この画面でAPIの確認ができますので大変便利です。
:::

curlコマンドなどでAPIへアクセスすると、実際に使用する際はJSON形式のデータを取得できることが分かります。
```terminal
(venv) curl http://localhost:8000/products/

# {"products":"http://localhost:8000/products/products/"}
```

#### データ編集
一覧を表示するためのURLにアクセスします。
```
http://localhost:8000/products/products/
```

一覧のデータと、新規登録のためのフォームが表示されます。
このフォームからデータが追加できますので、試してみてください。
![スクリーンショット 2023-10-07 183609.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/902793/5ac19f78-f6f3-867b-7118-4a7f11df1f67.png)

詳細を表示するためのURLにアクセスします。（末尾にIDを指定）
```
http://localhost:8000/products/products/1/
```
詳細画面が開き、データの編集や削除などができます。
![スクリーンショット 2023-10-07 183658.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/902793/9c779aea-f094-3983-012b-ee3a9b160cb6.png)

:::note info
これらの画面で操作できることは、APIでも同様にできます。
:::

最低限のコードで、一覧、詳細、新規登録、編集、削除を処理するAPIが実装できました。

### 12.ViewSetについて
#### APIView
Modelに紐づかないようなViewも作成できます。
これはAPIViewクラスを使用して、ダミーデータを返却するようなAPIの実装例です。
```python:products/views.py
from rest_framework.views import APIView      # 追加
from rest_framework.response import Response  # 追加

...
# 以下を追加
class ProductDummyApiView(APIView):

    def get(self, request, format=None):
        return Response({"name": "DUMMY!"})   # ダミーデータを返却
```
:::note info
APIViewを継承したクラスでは、get、または、postメソッドを定義します。
HTTPのGETでアクセスした場合は、getメソッドが呼ばれます。
:::

URLを追加します。
```python:products/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import ProductViewSet, ProductDummyApiView  # ProductDummyApiViewを追加

router = DefaultRouter()
router.register('products', ProductViewSet) 

urlpatterns = [
    path('', include(router.urls)),

    path("dummy/", ProductDummyApiView.as_view()),  # 追加
]
```

以下のURLにアクセスします。
```
http://localhost:8000/products/dummy/
```
ダミーデータを取得できました！
![スクリーンショット 2023-10-07 183851.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/902793/75652731-2cfc-ac08-c69c-33a8a9fdba41.png)

#### api_view
こちらは関数ベースのapi_viewにより、Modelのデータ一覧を表示する処理の実装例です。

```python:products/views.py
from rest_framework.decorators import api_view  # 追加

...
# 以下を追加
@api_view(['GET'])
def product_list(request):

    if request.method == 'GET':
        products = Product.objects.all()
        serializer = ProductSerializer(products, many=True)
        return Response(serializer.data)
```

ModelViewSetのように、ModelとSerializerを使用して、一覧を返却する処理にします。

URLを追加します。
```python:products/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import ProductViewSet, ProductDummyApiView, product_list  # product_listを追加

router = DefaultRouter()
router.register('products', ProductViewSet)

urlpatterns = [
    path('', include(router.urls)),
    path("dummy/", ProductDummyApiView.as_view()),

    path('list/', product_list),  # 追加
]
```

以下のURLにアクセスします。
```
http://localhost:8000/products/list/
```

商品の一覧を取得できました！
![スクリーンショット 2023-10-07 184221.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/902793/5a78da20-d7ea-a949-19b3-36ab62f3f108.png)

:::note info
方法はいろいろあれど、実装は簡単にできますので大変便利です。
他にも機能がありますので、[公式サイト](https://www.django-rest-framework.org/)のドキュメントをご確認ください。
:::


---
今回のソースコードはGitHubにコミット済みです。

https://github.com/whitecat-22/drf_crud

---
### 参考

https://chigusa-web.com/blog/django-rest-framework/

---
### (編集後記)
今回はDjango REST Framework (DRF)を利用して、RESTfulなAPIを作成してみましたが、実装は簡単ですし、画面を通じて確認を行なえるためユーザーフレンドリーです。
バックエンドのAPIを作る際には、選択肢のひとつとしてDRFは大いに利用できるものと分かりました。

ここまでの手順を基に、今後もDjangoによるWebアプリケーション作成をすすめてみます。
皆様の参考になれば幸いです。

今後、フロントエンドにReactまたはFlutterを結合したWebアプリケーションの作成に移りたいのですが、どちらを選ぶかで躊躇している状態でして、両者のメリデメをよく検討した上で決定したいと思います。