freeeアプリを５分で作れる！スターターパックを公開してみました

# はじめに
この記事は[freee APIで業務を楽しく便利にハックしよう！](https://qiita.com/advent-calendar/2021/freee-api)アドベントカレンダー2021の3日目の記事として投稿しています。

本記事では、freeeアプリを作成するハードルを劇的に下げる**「freeeアプリスターターパック」という自作のひな形アプリ・ツールセット**を紹介します。このパックを使うと、**５分で０からfreeeアプリをデプロイしてAPIを叩けるところまで実装できます。**

デフォルトで作成できるアプリはこんな感じです。freeeとの認証・認可を通して、ウェブアプリでfreeeAPIを叩くとこまで出来ているのがわかると思います。
![freee-demo-3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1228813/6ee9204e-746d-f397-e586-1e4c2c451403.gif)

## freeeアプリでHello Worldするのが大変な２つの理由

freeeではAPIを使ったデータ連携するアプリを作成し、アプリストアで公開することができます。そのfreeeアプリ開発でまず試しにと、Hello World的な最低限のアプリを作ろうとすると、大きな２つのハードルがあります。

1. **freeeとのOpenID Connect連携の実装が必須**。アクセストークン・リフレッシュトークン等の使い方を理解し、コードにする必要があります。
2. **どんな小さなアプリでも、インターネット上に公開した形で動かす必要あり**。ローカルサーバーでは基本不可。アプリはfreeeのインフラ上で動くわけではありません。


この２つのハードルが高く、スクラッチで開発する方は取り掛かりづらいかもしれません。[freeeアプリストア](https://app.secure.freee.co.jp/)を見ると、既存サービスとの連携アプリか、Google SpreadSheetをハックしたローコード開発アプリがほとんどで、スクラッチでアプリを作っているものはごく少数のようです。

## freeeのデータってパワフル
そんな難しいアプリ開発ですが、その一方で**freeeのデータってすごくパワフルで面白いんです！**例えば[会計APIのドキュメント](https://developer.freee.co.jp/docs/accounting/reference#%E3%82%B9%E3%82%BF%E3%83%BC%E3%83%88%E3%82%AC%E3%82%A4%E3%83%89)を見ると、仕訳帳や勘定科目のレベルでデータ操作ができ、ニッチサービスを作るにはパワフルすぎるくらいのAPIになってます。

**こんなにいいデータを既存サービス連携だけに使うのはもったいないと思います。**

## じゃあひな形作ってみよう
そんなモチベーションから、freeeアプリを動かすまでの最低限の実装・デプロイツールをひな形化した「freeeアプリスターターパック」を作って公開しました。GitHubリンクは[こちら](https://github.com/senbishi21/freee-app-starter)となります。

このパックを使うと、**約５分でアプリをデプロイし、freeeのプラットフォームと連携して、APIアクセスができる状態で公開することができます。**スターターパックという名前の通り、実装したい機能を簡単に追加できるようにしているので、このパックを元に本番アプリの作成・公開も可能です。

# スターターパックの概要

スターターパックでは、freeeアプリをGoogle Cloud上にデプロイします。クラウドを使用するものの、無料枠で十分動くので料金は気にしなくて大丈夫です。以下のウェブアプリとデータベースの２つのサービスがGoogle Cloud上で動くことになります。

- ウェブアプリ：**Google Cloud Function**で動き、インターネット上でアクセス可能です。Cloud FunctionはFaaS(Function as a Service)の一つです。ウェブアプリはPythonで書かれてます。
- データベース：**Firestore**というGoogle CloudのNoSQLデータベースを使用しています。

## スターターパックで作れるもの
スターターパックをデプロイしたアプリのデフォルトの動作は、最初のGIF動画の通りとなります。

流れとして、ユーザーはアプリにアクセスすると、まずfreeeにリダイレクトされ、ログイン・認可を要求されます。認可すると、アプリのメインページに戻り、アクセストークンの値とAPIへのコール結果を画面に表示します。アプリで使用するアクセストークンとリフレッシュトークン、セッションクッキーはデータベースに保持してあります。
![simple_diagram.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1228813/21dde5cf-daf1-176a-ec98-3883758f4af3.jpeg)


## スターターパックの中身
中身は４つのファイルのみです。

- main.py: メインのウェブアプリです。認可後はアクセストークンを使用してfreeeAPIからデータを取得できます。
- requirements.txt: 必要なPythonライブラリのリストです。ライブラリのインストールはCloud Functionが自動で行ってくれます。
- deploy.sh: Google Cloudへのデプロイ作業がすべて一つのコードになってます。このスクリプトを実行するだけでデプロイが完了します。
- env.yaml: freeeと連携するために必要な設定を保持します。

# 実際に５分間でfreeeアプリをデプロイしてみる

## 事前準備
- Google Cloudのアカウントとfreeeの開発者アカウントを用意（既にお持ちの方はスキップ）
- Google Cloud CLIをインストール（[こちらのドキュメント](https://cloud.google.com/sdk/docs/install)を参照にして下さい）
- [GitHub](https://github.com/senbishi21/freee-app-starter)からスターターパックのコードをダウンロード

## ５分でデプロイの概要

1. Google Cloudで新しいプロジェクトを作成し、Firestoreを新規作成【約30秒】
2. 1で作成したプロジェクトIDをdeploy.shにコピー【約10秒】
3. freeeの開発画面でアプリを新規作成し、IDやURLを`env.yaml`にコピー【約30秒】
4. `./deploy.sh`でCloud Functionにコードをデプロイ【約1分半】
5. Cloud FunctionのトリガーURLを`env.yaml`にコピーし、さらにURL末尾に`?page=redirect`を加えた文字列を、freeeのアプリ画面の「コールバックURL」にコピー【約30秒】
6. Cloud FunctionのトリガーURLを`env.yaml`にコピーし、再度`./deploy.sh`でコードをデプロイ【約1分半】

という流れで、約5分でfreeeアプリを作成・デプロイすることができます。デプロイ後にCloud Functionにアクセスすると、freeeのログイン・認可画面が出るので、認可するとアプリのメインページにリダイレクトされます。そのメインページで、アクセストークンとAPI呼び出しの結果が表示されているのを確認できると思います。

## ５分でデプロイの詳細
各ステップの詳細をスクリーンショットで紹介します。

1.Google Cloudで新しいプロジェクトを作成し、Firestoreを新規作成
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1228813/9c672ce2-23e0-963c-347d-3663964d94a0.png" width=467 /><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1228813/636440ff-eeb9-335c-aff8-28e6a65f5651.png" width=400 />
2. 1で作成したプロジェクトIDをdeploy.shにコピー
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1228813/10991248-1ae4-de78-7ed8-9f6ce63be55c.png" width=600 />
3. freeeの開発画面でアプリを新規作成し、IDやURLを設定ファイル`env.yaml`にコピー
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1228813/8980b4e6-ccbe-d1da-a79d-24a3e1e0d4b8.png" width=400 />
4. `./deploy.sh`でCloud Functionにコードをデプロイ
![Screen Shot 2021-12-01 at 23.08.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1228813/bc22da66-3ad3-8a5e-b13d-83fb1d0d4090.png)
5. Cloud FunctionのトリガーURLを`env.yaml`にコピーし、さらにURL末尾に`?page=redirect`を加えた文字列を、freeeのアプリ画面の「コールバックURL」にコピー
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1228813/45f6233f-aaf0-8123-4022-7cef395ba3a7.png" width=400 />
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1228813/4e7f9040-0e39-cd25-ced8-811226088719.png" width=600 />

このように、たった５分間で０からfreeeのアプリを公開し、連携してAPIからデータを取得するところまで実行できました。[スターターパックで作れるもの](#スターターパックで作れるもの)のセクションでデモしたように、URLにアクセスしてみてください。

# スターターパックの詳細な実装について

## アプリとfreeeの連携データフロー

アプリに連携するには、freeeのログイン・認可フローが必要となります。[freeeのドキュメント](https://developer.freee.co.jp/tutorials/getting-access-token)にも解説がありますが、本パックでのアプリでの処理内容もいれると以下の図のような流れになります。

Firestoreではセッションクッキーやアクセストークン・リフレッシュトークンの管理を行います。リフレッシュトークンを保持することで、アクセストークンの期限が切れたら再度取得することができます。（もちろんユーザーがアプリへの連携を切ると取得できません。）
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1228813/16fda9e7-b9d5-96aa-d5df-63be58e04953.jpeg" width=800 />

## コード紹介

Google Cloudへデプロイするコードはこんな形です。コメントの通りの処理を行います。

```shell
#!/bin/bash
# デプロイ先のプロジェクトをセットします
gcloud config set project <your-project-id>

# Cloud Functionをデプロイ
gcloud functions deploy freee-main\
		--source main\
		--env-vars-file env.yaml\
		--entry-point mainpage\
		--memory 128mb \
		--runtime python38 --region asia-northeast1 --trigger-http --allow-unauthenticated 
```

アプリのコードの中でキーとなる処理を以下に抜粋します。

- Cloud Functionは一つのエンドポイントURLしかもちません。なので、freeeからのOIDCリダイレクトとアプリのメイン画面、というように２つ以上のアクセスURLが欲しい今回のケースでは、GETパラメーターで処理をスイッチさせています。
- freeeから認可のリダイレクトが来た際には、トークンをDBに保存したあと、紐付けるためのクッキーをユーザーにセットさせてメインページに戻します。
- メインページでは、セッションクッキーを用いてアクセストークンをDBから取り出し、APIコールを行ってます。

```python
# Cloud Functionから最初に呼ばれる関数
def mainpage(request):
    # GETパラメーターのpageによって、処理を変える
    nav_page = request.args.get('page', default='main')

    # page=mainもしくはパラメーターなしの場合はこちら
    if nav_page == "main":
        ...
        access_token = validate_cookie_and_get_access_token(request.cookies[cookie_name])
        ...
        simplest_api_response = test_api_call(access_token)

    # page=redirectの場合はこちら。freeeからのOIDCリダイレクト用
    elif nav_page == "redirect":
        ...
        response = make_response(redirect(mainpage_url))
        response.set_cookie(cookie_name, value=cookie, expires=expire_date) 
        return response
```

クッキーやセッションは、以下のような関数でFirestoreに簡単に保存できます。

```python
# クッキーやトークンをFireStoreに保存する関数
def store_cookie_in_Firestore(cookie_value, access_token, access_token_expires_at_unixtime, refresh_token, scope):
    try:
        db = get_firestore_instance()
        data = {
            u'access_token': access_token,
            u'access_token_expires_at_unixtime': access_token_expires_at_unixtime,
            u'refresh_token': refresh_token,
            u'scope' : scope
        }
        db.collection(Firestore_session_collection_name).document(cookie_value).set(data)
```

## 本番運用のために必要なこと
スターターパックでは、簡便にするため何点か本番運用に適さない部分があります。どれも簡単に変えられるポイントなので、以下を直して下さい。

- すべての設定値をenv.yamlに保持していますが、よりセキュアにするため、freeeのクライアントシークレットは環境変数でなく、Google Secret Managerを用いて管理してください。
- 開発する内容によっては、Firestoreにユーザーデータを保持するかと思います。その場合、必ずFirestoreのアクセス権限ルールを設定してください。最低限、ユーザーごとのCollection階層を作り、そのユーザーしかアクセスできない、というユーザー分離のルールを設定しましょう。Firestoreルールについては、[こちらの記事](https://qiita.com/KosukeSaigusa/items/18217958c581eac9b245)がわかりやすいと思います。

# おわりに
freeeアプリのひな形を作ってみた、という記事でした。freeeのAPI連携はなかなか素直で、一旦連携してしまえばかなり管理は楽ですね。今回のスターターパックはPython+Google Cloudでしたが、AWSやAzure連携、他言語でのアプリ実装など大歓迎ですので、ぜひプルリクお待ちしています！

## 参考文献
- [freeeのOAuth関連処理について](https://developer.freee.co.jp/tutorials/getting-access-token)
- [Google App Scriptで連携する方法](https://tonari-it.com/gas-freee-api-auth/)
- [freee APIドキュメント](https://developer.freee.co.jp/docs)