現役高校生が水泳記録管理サービスを作った話/その1：バックエンド編


# はじめに

この記事は、Qiita Advent Calendar 2 日目の記事です。
以下の記事より、すべての記事をご覧になれます。

https://qiita.com/0_terarin_0/items/0f367210e2465f7772c9

# 筆者について

- 現在高校２年生
- 水泳部マネージャー
- 生徒会長
- とある団体の代表

# 本編

## 構想段階

### 技術選定

この構想段階では、以下のような要件を定めました。

- フロントエンドとの通信ができるだけ単純となるような仕組み
- 将来的には API 対応もしたい
- 複数のリクエストに対応する[^1]仕組み
- Firebase か Google Cloud の対応する言語のフレームワーク
- 自分が触ったことがある言語での実装

### 必要なリクエスト

今回のこのサービスを展開するにあたって、必要なリクエストの一覧を考えると以下のようになりました。

- 記録の登録
- 記録の取得

案外簡単じゃね？と思いましたが、流石に甘すぎる計画。この実装だけで終わるわけがありませんでした。（後述）

### 最終的に

最終的に選定された技術は…Django Restでした！

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

なお、本サービスを実装するにあたり他にも以下のフレームワークを利用しました。

https://pypi.org/project/firebase-admin/

https://pypi.org/project/django-cors-headers/

ちなみに利用フレームワークだけはこれ以上変わらないです。

## 開発スタート！

ここから数ヶ月にわたるデスマーチが、いざスタートです！
全部書いていたらキリがないですので、大きいポイントだけかきます。

### セキュリティ確保の問題

さて、一番最初にぶち当たった問題がこれでした。「どうやってセキュリティ確保するねん！」て。たとえば、直接 Firebaes 上のデータベースにに書き込むようなプログラムだったら UID を含んでいるか含んでいないかで調整できたりするのですが、自前のバックエンドサーバーを使う上ではこの前提は使えません。
次に考えたのが、ID トークンの検証です。結論から言うと今回こちらを採用させていただきました。リクエスト送信時にトークンを埋め込んで、バックエンドサーバーで検証する方法です。Firebase Admin SDK というものを利用します。

### 実装

#### 認証周りの実装

まず、フロントエンドの ID トークンの埋め込みは以下の通りとなりました。
React での実装ですので、非同期処理には`useEffect()`を利用します。また、`useState()`で値を管理します。

```javascript:frontend.js
import React, { useEffect } from 'react';
import 'firebase/compat/auth';
import { useState } from 'react';
import firebase from 'firebase/compat/app';

function yourParts(){
  const [token, setToken] = useState();
  useEffect(() => {
  const getIdToken = async () => {
            try {
                const firebaseConfig = {
                    apiKey: process.env.REACT_APP_FIREBASE_APIKEY,
                    authDomain: process.env.REACT_APP_FIREBASE_AUTHDOMAIN,
                    projectId: process.env.REACT_APP_FIREBASE_PROJECTID,
                    storageBucket: process.env.REACT_APP_FIREBASE_STORAGEBUCKET,
                    messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGEINGSENDERID,
                    appId: process.env.REACT_APP_FIREBASE_APPID,
                };
                const init = firebase.initializeApp(firebaseConfig);
                firebase.auth().onAuthStateChanged((currentUser) => {
                    if (currentUser != null) {
                        setUserIconURL(currentUser.multiFactor.user.photoURL)
                    }
                }
                )
            }catch(error){
                navigate('/login/')
            }
        }
    getIdToken()
  })
  return(
    <button onClock={() => fetch(
      {
            'method': "POST",
            'body': JSON.stringify(dataToRegister),
            'headers': {
                'Content-Type': 'application/json',
                'firebase-id-token': token,
            }
      },yourURL)}></button>
  )
}
```

つづいて、バックエンドの実装は次のとおりです。

```python:backendo.py
import firebase_admin
from firebase_admin import auth

def add(request):
  auth.verify_id_token(request.META["HTTP_FIREBASE_ID_TOKEN"]) 

```

注意するポイントとして、、```request.META["HTTP_FIREBAW_ID_TOKEN]```のところにあるように、Djangoではヘッダー名が全部大文字でかつ最初に'HTTP_'とつきます。（ドキュメントをちゃんと読めばわかる話なのですが、、、。）

また、Firebase Authenticationを利用する上で気をつけなければいけないことなのですが、Firebase Appの初期化は関数外でしなければいけません！！でないと、リクエストされるたびに同じ名前のアプリが複数宣言されているというエラーが出てしまいます。（当たり前っちゃ当たり前の話）

```python:backend.py
cred = credentials.Certificate(os.getenv('FIREBASE_CRED_PATH'))
app = firebase_admin.initialize_app(cred)
```

#### RESTフレームワーク

さて、RESTフレームワークの利用について、本来ならDjangoに付属のデータベースを利用しますので、シリアライザーなるものが必要です。しかし、今回のこのサービスでは以下のような理由があって付属のデータベースを利用しませんでした。

- 認証周りが上手いことできる自信がない
- そもそもシリアライザーをうまく利用できる自信がない
- できるだけ安く済まそうとしており、Google CloudのCompute Engine最安サーバーを利用していて、それで耐え切れる自信がなかった。

…というような感じで、能力不足が故に不安要素が多すぎて外部サービスに逃げた形となります。（勉強しろよ〜！）

さて、ということでDjangoでの開発を始めますが、、、。
URLの書き方などに関しては以下の記事を参考にしてください。

https://qiita.com/yukiya285/items/fb9c3457925e0fbf84d5

さて、僕はDjango RESTのデコレータの利用方法について記したいと思います。まずは以下のコードを見てみてください。

```python:backend.py
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
from django.http import JsonResponse
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
from django.http import JsonResponse

@api_view(['POST'])
def add(request):
  #backend 処理

```

このコードの```@api_view```の部分がデコレータと言われるやつです。要するに、処理をする関数にDjango REST framework独自の処理を加えるためのパーツってことです。詳しくは以下の記事をご参考にしてください。

https://qiita.com/mtb_beta/items/d257519b018b8cd0cc2e

簡単な話で、この```@api_view(['POST])```のPOSTの部分に必要なリクエストの種類を記します。（Get・Putなど）

ここまでできたら、あとはCORSの設定のみです。CORSの詳しい説明は先人の方々の記事をご参考にしてください。

https://qiita.com/mrshouuge/items/c4d5d06d1cf0b73548e1

このCORSの設定について、```settings.py```には以下のような内容を記述してください。

```python:settings.py
INSTALLED_APPS = [
    'corsheaders',
    #etc,,,
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    #etc,,,
]

CORS_ORIGIN_WHITELIST = [
    #許可するオリジンを記述
    'http://localhost:30000/'
]

CORS_ALLOW_HEADERS = [
    #許可するヘッダーを記述
    'firebase-id-token',
    'content-type',
]

#お試し環境の時だけ以下も記述
ORS_ALLOW_ALL_ORIGINS = True
```

### 一部APIの分離

ここまで開発したのち、おおむね満足していたのですが何かが足りませんでした。以下のWebサイトで適当な記録をお手持ちの携帯端末からご確認ください。

https://result.swim.or.jp/

上記のサイトは公式大会を全部確認している日本水泳連盟公式の記録確認サイトです。ここでは、FINAポイントが記載されています。僕の計画ではこれが漏れていましたので、いざ実装です。
この部分に関しては、分離して一つの別のAPIにしました。というのも、他の方々にもFINAポイントの計算を安易にして欲しいという理由からです。（もっとも、こんなAPIを利用する人なんて限られてるでしょうが…。）また、隠すほどの技術でもないですのでGitHubでオープンソースとして公開させていただいておりますので、詳しい内容は以下ページをご確認ください。

https://github.com/0-terarin-0/api-getFinaPoint/tree/master

以上が、バックエンドの実装を進めるにあたって大きかったイベントです。

# 最後に

なんやかんやスタートした開発ですが、自分の未熟さが滲み出ているプロジェクトとなっています。不安定なこのプロジェクトは果たしてこの後どのような道を辿ることになるのでしょうか。今後のアドカレ 23 の記事をお楽しみにしてください。

https://qiita.com/0_terarin_0/items/0f367210e2465f7772c9

[^1]: なぜ複数のリクエストに耐えうるようにするかというと、ここでは記述していませんが他にも拡張機能を行う予定だったからです。ここには後ほど書いておきます。