スプレッドシートをWebAPI化するサービスの作り方

![image.png](https://qiita-image-store.s3.amazonaws.com/0/35595/12f4a546-79e6-d6a1-f4e1-971cfd14afd7.png)

スプレッドシートを Web API 化するアプローチとして Google Apps Script を使ったやり方があります。
最近、人気記事になっているのがまさにそのパターンですね。
[3分で API を作って世の中にデプロイするライブコーディング〜今日から君もスピードスターエンジニア〜](https://qiita.com/numanomanu/items/7bb6542961a2c567f05c)

しかし、Google Apps Script を使った手法には次のような弱点があるため、簡易的なものにしか使えません。[^1]

[^1]: GASの各種制限 https://developers.google.com/apps-script/guides/services/quotas#current_limitations

- URLを自由に設定できない
- リクエスト数など各種制限

本記事では、`サーバーやクライアントなど好きな環境からスプレッドシートを読み込む手法の解説`と、`スプレッドシートをWebAPI化するサービスの作り方`について記載します。
スプレッドシートの読み込みは、GAS使わなくても簡単にできますよ！


## Web API を動かしてみよう
### 読込対象のスプレッドシート
デモ用として次のスプレッドシートを用意してあります。
ユーザー一覧を管理しているスプレッドシートで、ヘッダー行とデータがあるよくあるテーブル構造です。
※URL欄で囲ってある部分は `スプレッドシートのID` でスプレッドシートを特定するためのキーとなります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/35595/f1eeacae-da29-4e62-3a50-9c484c256029.png)


### APIを実行
スプレッドシートを Web API 化するサービスを作ってあるので次のURLを叩いてみましょう。
https://asia-northeast1-sheetstowebapi.cloudfunctions.net/api?id=1u7TFoWtbeTS0PKKWKKXQ0RfdIgjbuzuQd53hCqOEuao&range=user!B3:E100

※URLのフォーマットは次のようになります。
`https://asia-northeast1-sheetstowebapi.cloudfunctions.net/api?id={スプレッドシートのID}&range={取得する範囲}`

すると次のJSONが返されます。
ヘッダー行をプロパティ名として、スプレッドシート上の赤枠で囲ってあった部分がJSONとして変換されているのがわかります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/35595/b1e34e6b-04f5-aabc-4639-de9b15b2b252.png)


## 仕組みの解説
先程のデモで使ったスプレッドシートのURLにアクセスしてみましょう。
https://docs.google.com/spreadsheets/d/1u7TFoWtbeTS0PKKWKKXQ0RfdIgjbuzuQd53hCqOEuao/edit

権限がないため次のような画面になると思います。[^2]
![image.png](https://qiita-image-store.s3.amazonaws.com/0/35595/491a54dc-5e51-8b5b-eb51-3a76f06609e5.png)

[^2]: Gmailにログインしていない場合は、Gmailのログイン画面になります

ではこのWebサーバーはどうやってスプレッドシートへのアクセス権を取得しているのでしょうか。

実はサービスアカウントと呼ばれるものにスプレッドシートへのアクセス権を付与してあるのです。
※サービスアカウントの作り方については後で解説します。

このスプレッドシートの共有設定を開くと次のようになっています。
`sheetstowebapi@sheetstowebapi.iam.gserviceaccount.com`というサービスアカウントに読み取り権限を付与しているのが確認できます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/35595/f11ceed7-860e-b33e-9a6a-f358bb401b75.png)

つまり、このWebAPIサーバーは`sheetstowebapi@sheetstowebapi.iam.gserviceaccount.com`というアカウントからアクセスしてシートを読んでいるような形になります。

**ご自分でスプレッドシートを作成＆共有設定を行い、上記APIを叩いてみるとJSONが取得できます。**
※見られて困るデータは使用しないでください


## サービスアカウントを使った手法のメリット・デメリット
サービスアカウントを使った場合のメリット・デメリットを記載しておきます。

### メリット/ コードをほとんど書かなくて済む
サービスアカウントを使った場合、システムに対してユーザーが認可するのではなく、システムが使うアカウントに対してユーザーが権限を与えているため、認可処理が不要になります。
そのため認可処理が不要になった分、API実行コードが **格段** にシンプルになります。

例としてNodeJSのコードを記載しておきます。
これだけのコードでシートの内容が読めています。

```javascript:サービスアカウントを使ったAPI実行
const { google } = require('googleapis');
const sheets = google.sheets('v4');
const path = require('path');

execAPI('1u7TFoWtbeTS0PKKWKKXQ0RfdIgjbuzuQd53hCqOEuao', 'user!B3:E100');

async function execAPI(spreadsheetId, range) {
  const auth = await google.auth.getClient({
    keyFile: path.join(__dirname, 'service_account.json'),
    scopes: ['https://www.googleapis.com/auth/spreadsheets'],
  });

  const apiOptions = {
    auth,
    spreadsheetId,
    range,
  };

  sheets.spreadsheets.values.get(apiOptions, (err, res) => {
    console.log(res.data.values);
  });
}
```

```console:上記のコードを実行
% node src/hoge.js
[ [ 'id', 'slackAccount', 'email', 'name' ],
  [ 'A00001', 'admin', 'admin@example.com', '管理者' ],
  [ 'U00001', 'hi', 'hi@example.com', 'ハイ' ],
  [ 'U00002', 'hey', 'hey@example.com', 'ヘイ' ],
  [ 'U00003', 'hello', 'hello@example.com', 'ハロー' ],
  [ 'U00004', 'howdy', 'howdy@example.com', 'ハウディ' ] ]
```

### メリット/ トークンの管理が不要になる
これも認可が不要になったため発生するメリットです。
認可処理を行うと、時間制限のあるアクセストークンと期限のないリフレッシュトークンを取得し、それぞれ管理する必要があります。
このトークン管理が面倒で、アクセスした人のアカウントとそれに対応するリフレッシュトークンの紐づけを保持したり、アクセストークンが切れたら再取得したり・・・などの処理が必要になります。

### メリット/ 権限範囲を狭くすることができる
例えば、スプレッドシートのAPIの認可を行うと、そのユーザーが保持するすべてのスプレッドシートに対する権限を与えてしまいます。
このスプレッドシートだけアクセスを許可したいんだけど、、、みたいなことができません。
サービスアカウントを用いると、スプレッドシートの共有設定で権限を与えられるため、対象のスプレッドシートだけの許可が可能です。

### デメリット/ 権限設定が面倒
スプレッドシート単位の権限設定のため、複数のスプレッドシートを読み込みたい場合はすべてのファイルに設定する必要があります。

### デメリット/ 認可パターンと比べてQuota制限が厳しくなる
スプレッドシートのAPIは大量にAPIを発行できないように制限がかかっています。[^3]
[^3]: スプレッドシートに限らず、GoogleのAPIには基本的に制限がある

例えば、スプレッドシートの読み込みの場合、次の２つの制限が付きます。

1. プロジェクト(APIを発行するシステムとみていいです）あたり、100秒間に500リクエスト
2. アカウントあたり100秒間に100リクエスト

![image.png](https://qiita-image-store.s3.amazonaws.com/0/35595/d86af8ff-2268-991b-7919-ef8c03bc3d1e.png)

サービスアカウントを使用した場合、1アカウントにQuota制限がかかります。
認可を利用した場合、それぞれのユーザー単位でのQuota制限がかかるため分散されるのです。

ただし、このQuota制限はGoogleへ申請をだして緩和できるため、ものすごいデメリットかというとそうでもありません。


## サーバーからSheetsの中を見るための設定手順
サービスアカウントを使ったAPI実行の仕組みを解説しましたが、ここからは実際にどうやってシートのAPIを実行するサービスアカウントを作っていくか、という解説をします。

### GCPプロジェクトを作る
`GCPプロジェクト 作成` とかでググってください。
※キャプチャ撮り忘れたorz

以降の手順では`SheetsToWebAPI`という名前でGCPプロジェクトを作ったあとの流れになります。

### サービスアカウントを作る
API & Services - Credentials から行います。
Create credentials -> Service account key と選択します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/35595/32060b36-d20d-9c69-acfb-619feb4aa937.png)

名前とIDを入力します。
roleは不要です。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/35595/4bfe6caf-16ed-3d43-7b28-53bb6cb592cc.png)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/35595/53c1fa64-c165-12eb-87a7-9907531a5a00.png)

作り終わるとjsonがダウンロードされます。
このファイルをプログラムから読み込んでサービスアカウントからアクセスする形にします。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/35595/eb37a127-a657-7d51-768f-aba14ac99277.png)


### Sheets API を有効化する
スプレッドシートを読み込むために Sheets API を有効化する必要があります。

API & Services - Dashboard から行います。
ENABLES API AND SERVICES を選択します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/35595/5bd58d5b-bb5c-18df-06eb-ec5198c624e6.png)

Sheets API を検索して有効化します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/35595/19aa4476-eba7-c428-d9d4-013fd8c1f919.png)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/35595/c51a9202-5447-14e4-cc7b-dd042cc53553.png)


## Web API 化するプログラム
私が作成したデモは Cloud Functions を使っているためNode.jsを利用しています。
ソースをGitHubに置いとくので参考までにどうぞ。
[howdy39/sheets-to-webapi: スプレッドシートを読み込んでWebAPI化するCloudFunctionsのソース](https://github.com/howdy39/sheets-to-webapi)

Node.js以外の言語を用いる場合は、[API Client Libraries](https://developers.google.com/api-client-library/) のページからAPIライブラリを探してください。
`service account`で検索すると、サービスアカウントを使用したAPI実行方法が出てくると思います。

※実際にこのようなサービスを作るなら、Sheets API の結果をキャッシュするような仕組みを作って毎回API発行しないようにしたほうがいいかと思います。

# あとがき
ちなみにこのデモをなぜ Cloud Functions で作ったかというと
**Cloud Functions が GA になった！！**
**Node8 が使えるようになった！ async function 使えるぞ！！**
**記念になんか作っとくか！**
という理由です。