DjangoRestFrameworkを用いたWebAPI構築基礎(heroku DBサーバ使用とデプロイ)

## 概要
この記事はDjangoRestFramework(以下DRFと記す)を用いたWebAPIを作成する方法を説明する．DRFはPythonのWebフレームワークであるDjangoの拡張機能で，RESTful Web APIの開発を容易にするために設計されている．

>**API（Application Programming Interface）**
>異なるソフトウェアコンポーネント間でデータを交換し,機能を相互利用するための規約やルール,ツールの集合

>**Restful Web API**
>Webアプリケーションやサービス間で情報をやり取りするためのインターフェースであり，REST原則に基づいて設計されている.
>>**REST（Representational State Transfer）**
>>インターネット上での分散システム設計(クライアントサーバシステム)のためのアーキテクチャスタイルを定義し，シンプルさと拡張性を提供します。RESTful APIはHTTPプロトコルを使用してデータやサービスにアクセスし，操作する．

**主な操作メソッド**
RESTful APIでは，主に以下のHTTPメソッドを使用してリソースに対する操作を行う．

**GET** リソースを取得する．
**POST** 新しいリソースを作成する．
**PUT** リソースを更新する．
**DELETE** リソースを削除する．
**PATCH** リソースの一部を更新する．
これらをAPIのエンドポイントに対して使用し，データ通信の命令をする．
>難しいかもしれないが前提知識として知っておいてほしい．

今回はPaaSでherokuにデプロイし，DBサーバにheroku postgresを用いる．

## 前提条件
ローカルサーバーの構築までは以下のページを参照
>[Django初心者]Djangoでのwebアプリの作成[基礎編〜ローカルサーバー構築まで〜]
https://qiita.com/tarakokko3233/items/46c567fb32a26a69269a

この記事では`$ Python manage.py runserver`によって以下の画面が表示することができることを前提とする．
![ad62a72f-9820-710b-48d8-3732916e5d02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/409c671d-1a55-a9e9-bc72-c4d3cca1f686.png)

**初期状態でのDjangoプロジェクトの階層構造**
```
yourprojectname/
    manage.py

    yourprojectname/
        __init__.py
        settings.py
        urls.py
        asgi.py
        wsgi.py

    yourappname/
        __init__.py
        admin.py
        apps.py
        migrations/
            __init__.py
        models.py
        tests.py
        views.py

```
Djangoディレクトリの階層説明を以下の記事で詳しく説明している．
https://qiita.com/tarakokko3233/items/8787692aaf9b1d943205

# 環境構築
### 仮想環境の構築
仮想環境が有効になっていることを確認する．
もし有効でないならば以下のコマンドを実行する．

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

>仮想環境が作成できていないのならば以下のコマンドを実行する．
>```
>$ python -m venv venv
>```
### DRFのインストール
仮想環境にDRFをインストールする．
```
$ pip install djangorestframework
```

### Psycopg2のインストール
psycopg2はPostgreSQLデータベースに接続するためのPythonのアダプターである．このライブラリを使うことで，PythonプログラムからPostgreSQLデータベースに対してSQLクエリを実行したり，データを操作したりできる．
```
$ pip install psycopg2
```

>Apple シリコンMacなどpsycopg2でうまくいかなかった場合以下のコードでインストールする．
>```
>$ pip install psycopg2-binary
>```

## Dj-database-urlのインストール
データベース接続情報をHerokuの環境変数から読み込むために役立つライブラリ．

```
$ pip install dj-database-url
```

### gunicornのインストール
HerokuでDjangoアプリケーションを実行するためのWSGI HTTPサーバー．

```
$ pip install gunicorn
```

### Whitenoiseのインストール
静的ファイルの提供を容易にするためのライブラリ．Heroku上でDjangoアプリをデプロイする際に必要．
```
$ pip install whitenoise
```

## heroku dashboardの設定(DBサーバとデプロイ設定)
**herokuプロジェクトの作成**

以下にアクセスしてheroku dashboardを開く．
https://dashboard.heroku.com/apps

>もしアカウントを作成していないならばherokuアカウントを作成すること．

開いたら，heroku dashboardの`new`を押して，`Create new app`を選択する．

![スクリーンショット 2024-04-11 11.19.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/9cabbb3e-55d6-d44e-660d-8f5b8d5c2529.png)

App nameに任意のプロジェクト名を記述し，`Create app`を選択する．
>あとでdjangoプロジェクトに記述するので覚えておくこと．

![スクリーンショット 2024-04-11 11.21.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/063eefa8-dcc9-734e-c9bc-3ed7891739b4.png)

**ここからはDBサーバの設定を行う**

`Resources`を押して，Add-onsの検索バーに`heroku postgres`と検索し，表示された`Heroku Postgres`を選択する
![スクリーンショット 2024-04-11 11.25.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/818bc6e2-cf0c-5e70-2847-b69b9667b131.png)

プロジェクトにあったDBのプランを選択する．
>適切な Heroku Postgres プランの選択
>https://devcenter.heroku.com/ja/articles/heroku-postgres-plans

![スクリーンショット 2024-04-11 11.28.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/6bbf761a-ca31-5f4a-cb55-e5f800b238a0.png)


プランを選択したら，`Submit Order Form`を選択する．
>今回はEssential2(Beta)-Freeを選択する．

![スクリーンショット 2024-04-11 11.28.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/cfc4f9c0-2fc5-6f24-2f11-fee076155fae.png)

数分後になるとDBサーバの接続情報が見られるので確認する．
確認方法は`Heroku Postgres`を押してHeroku Dataにアクセスする．

![スクリーンショット 2024-04-11 11.36.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/a0e56740-3400-fedc-ad5d-4227c7973032.png)


`Settings`を選択し，`View Credients...`を選択するとDBサーバの接続情報が見られる．これを後で`yourprojectname/setting.py`に入力する．

![スクリーンショット 2024-04-11 11.38.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/47f0bf00-091a-9dd3-32ee-ffe054c58185.png)
>Host,Database,User,Port,Password,URIが表示されるはずだ．
>>URIはDB管理ソフトでこのDBにアクセスするために必要である．


## djangoディレクトリのセッティング
DRFの構築とDBサーバの設定，herokuにデプロイするための設定をする．

### setting.pyの設定
**ALLOWEDHOST**にherokuにデプロイするためにherokuのホスト名である，`herokuapp.com`を追記する．デプロイするのに必要である．
```yourprojectname/setting.py
ALLOWED_HOSTS = [...,
                '.herokuapp.com', #この行を追加
                ]
```
**DATABASE**に先ほどheroku dashboardで作成したHeroku PostgresのDBサーバの接続情報を入力する．DBサーバを利用するのに必要である．

```yourprojectname/setting.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',#どのDBでもPostgreSQLなら同じ
        'NAME': '-',  # Heroku Postgresで確認したDatabaseの値を入力
        'USER': '-',  # Heroku Postgresで確認したUserの値を入力
        'PASSWORD': '-',  # Heroku Postgresで確認したPasswordの値を入力
        'HOST': '-',  # Heroku Postgresで確認したHostの値を入力
        'PORT': '5432',  # どのDBでもPostgreSQLなら同じ
    }
}

```
**INSTALLED_APPS**に以下の情報を追記する．DRFの使用に必要である．
```yourprojectname/setting.py
INSTALLED_APPS = [
    ...,
    'yourappname',    # アプリ名も記述
    'rest_framework', # この行を追加
]
```

**MIDDLEWARE**に以下の情報を追記し，**STATIC_ROOT**設定を追加する．デプロイ時に静的ファイルを扱うのに必要である．
```yourprojectname/setting.py
MIDDLEWARE = [
    ...,
    'whitenoise.middleware.WhiteNoiseMiddleware',
]

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

新しく**REST_FRAMEWORK** 設定を追加する．**REST_FRAMEWORK** 設定は、APIのデフォルトのパーミッションクラスを指定する．DRFの使用に必要である．
```yourprojectname/setting.py
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly',
    ]
}
```

## モデル，ビュー，シリアライザーの作成
デプロイするのみの人は不要だがAPIをデプロイするなら必要である．

**モデルの作成**
API通信をするデータの構造を定義する`models.py`を作成する．
```yourappname/models.py
from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()

    def __str__(self):
        return self.title

```
>ここでは簡単にタイトルとその内容を格納するPostモデルを作成する．

モデルを作成したら以下のコマンドを実行してデータベースに適用させる必要がある．
```
$ python manage.py makemigrations
$ python manage.py migrate
```
**シリアライザーの作成**
シリアライザーは，モデルインスタンスとデータ型（JSONなど）との間の変換を行う．`serializers.py`は初期では存在しないので追加でファイル作成をしてほしい．Djangoはモデルをマイグレートすると，自動的に主キーとしてid属性が振られるのでシリアライザーにはid属性を入れる必要がある．
```yourappname/serializers.py

from rest_framework import serializers
from .models import Post

class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = ['id', 'title', 'content']

```
**ビューの作成**
ビューはリクエストを処理し，レスポンスを返す．DRFでは，ビューセットを使用してCRUD(作成，読み取り，更新．削除）操作を簡単に実装できる．
```yourappname/view.py
from rest_framework import viewsets
from .models import Post
from .serializers import PostSerializer

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

```
`Post.objects.all()`で，モデルの全レコードを取得し，`serializer_class`に作成したPostモデルのシリアライザーを取得する．

## ルーティング(エンドポイント)の設定
リクエストを適切なビューにルーティングする必要があるので，urls.pyにURLパスを定義する．このエンドポイントにアクセスしてデータのやり取りを行う．
```yourprojectname/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import PostViewSet

router = DefaultRouter()
router.register(r'posts', PostViewSet)

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

```
これでPostモデルに対するCRUD操作を行うエンドポイントが設定される．例えば，/posts/へのGETリクエストはデータのリストを返し，POSTリクエストは新しいデータを作成する．

## デプロイ設定
herokuにデプロイするときはプロジェクトを認識させるためにファイルを作成する必要がある．
manage.pyがあるディレクトリに以下の3つのファイルを追加する．

**Procfile**
HerokuでDjangoアプリケーションをデプロイする際に，Herokuがアプリケーションをどのように実行すべきかを指示するファイル．
```Procfile
web: gunicorn yourprojectname.wsgi --log-file -
```
**runtime.txt**
デプロイ時のwebサーバで使うpythonバージョンを指定するファイル．
```runtime.txt
python-3.12.2 #　プロジェクトで使用しているPythonのバージョンにすること
```
**requirements.txt**
pipからインストールしたものを記載する．デプロイ時のwebサーバでインストールする際に必要である．
```requirements.txt
Django
djangorestframework
psycopg2
dj-database-url
gunicorn
whitenoise
//他にpipからインストールしたものがあれば記載する
```
## gitへプッシュ
ここまで作成した物をgitにプッシュする．そして，git上にあるリポジトリをデプロイすることになるため，以下のディレクトリ構造にしてプッシュすること．

```
manage.py
Procfile
runtime.txt
requirements.txt
.gitignore

yourprojectname/
    __init__.py
    settings.py
    urls.py
    asgi.py
    wsgi.py

yourappname/
    __init__.py
    admin.py
    apps.py
    migrations/
        __init__.py
    models.py
    serializers.py
    tests.py
    views.py

```
**manage.pyがある階層がルートになるようにgitにプッシュすること．**

また仮想環境やキャッシュファイルがgitにプッシュされないように.gitignoreを記述することをお勧めする．プッシュ，プル，マージの際にコンフリクトが起こる可能性があるからである．
```.gitignore
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
media/

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
.python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
#Pipfile.lock

# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.venv
venv/
ENV/
env.bak/
venv.bak/

/venv/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# VS Code settings
.vscode/

# JupyterLab
.jupyterlab/

# DS_Store
.DS_Store

```

## herokuにデプロイ
[heroku dashboard](https://dashboard.heroku.com/apps)にアクセスしてyourprojectnameを選択し，deployを選択する．
Deplayment methodにGitHubを選択し，Connect to GitHubの検索バーに先ほどのリポジトリを入力する．
![スクリーンショット 2024-04-11 13.50.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/f71520bc-3281-8bf8-beb3-0029a43bceba.png)
Connectを選択する．
![スクリーンショット 2024-04-11 13.53.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/1ae9e4e5-12eb-404a-49c9-923fa26802bd.png)
Manual deployでデプロイするブランチを選択してDeploy Branchを押せばデプロイ完了．
![スクリーンショット 2024-04-11 13.54.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/6e9c3fb7-87bb-c81e-e4ea-7913246fcc88.png)

>Automatic deploysでデプロイするブランチを選択して，Enable Automatic Deploysを選択すると，gitにプッシュするときに自動でデプロイしてくれるようになる．
>![スクリーンショット 2024-04-11 14.04.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/53a330eb-f7e0-c32d-0775-710edead6bd3.png)

ページ上部のOpen Appを押せば以下の画面が表示される．
![スクリーンショット 2024-04-11 14.11.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/ca1a9173-f946-afed-8ee2-e1b72e3d9e6f.png)

これでAPIが作成できた．

**APIクライアント受け取り設定**

Androidクライアント側でのAPI受け取り設定
```androidappname/app/src/main/java/com/example/androidappname/Post.java

package com.example.androidappname;

public class Post {

    private int id;

    private String name;

    private String content

}
```

```androidappname/app/src/main/java/com/example/androidappname/ApiClient.java
package com.example.androidappname;

import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class ApiClient {
    private static final String BASE_URL = "デプロイしたherokuプロジェクトのURL";
    private static Retrofit retrofit = null;

    public static ApiService getApiService() {
        if (retrofit == null) {
            // ロギングインターセプタを作成
            HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
            logging.setLevel(HttpLoggingInterceptor.Level.BODY);

            // カスタムOkHttpClientを作成
            OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
            httpClient.addInterceptor(logging); // ロギングインターセプタを追加

            retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .client(httpClient.build()) // カスタムOkHttpClientを使用
                    .build();
        }
        return retrofit.create(ApiService.class);
    }
}

```
UnityでのAPI受け取り設定
```Post.cs
[System.Serializable]
public class Post
{
    public int id;
    public string name;
    public string content;
}

```


```ApiClient.cs
using UnityEngine;
using UnityEngine.Networking;
using System.Collections;

public class APIHandler : MonoBehaviour
{
    private readonly string apiUrl = "デプロイしたherokuプロジェクトのURL"; // APIのエンドポイントを指定

    // APIからデータを取得するメソッド
    public IEnumerator GetUserData()
    {
        UnityWebRequest request = UnityWebRequest.Get(apiUrl);
        yield return request.SendWebRequest();

        if (request.result == UnityWebRequest.Result.Success)
        {
            // レスポンスのJSONをUserModelオブジェクトにデシリアライズ
            Post postdata = JsonUtility.FromJson<Post>(request.downloadHandler.text);
            Debug.Log("User Name: " + postdata.name); // デバッグログにユーザー名を表示
        }
        else
        {
            Debug.LogError("Error: " + request.error);
        }
    }

    // スタート時にAPIからデータを取得
    void Start()
    {
        StartCoroutine(GetUserData());
    }
}

```
Swiftでの受け取り方
```Post.swift
import Foundation
struct Post: Codable, Identifiable {
    let id: Int
    let name: String
    let content: String

}
```

```APIClient.swift
import Foundation

class APIClient {
    static let shared = APIClient()

    private let baseURL = "デプロイしたherokuプロジェクトのURL"

    func getRequest<T: Decodable>(path: String, completion: @escaping (Result<T, Error>) -> Void) {
        guard let url = URL(string: baseURL + path) else {
            completion(.failure(NSError(domain: "Invalid URL", code: 0, userInfo: nil)))
            return
        }

        let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
            if let error = error {
                completion(.failure(error))
                return
            }

            guard let data = data else {
                completion(.failure(NSError(domain: "No data", code: 0, userInfo: nil)))
                return
            }

            do {
                let decoder = JSONDecoder()
                let result = try decoder.decode(T.self, from: data)
                completion(.success(result))
            } catch {
                completion(.failure(error))
            }
        }

        task.resume()
    }
}

```