Google Forms をAPI化しサービスに組み込んで柔軟なフォームを作ろうと思ったんだ！(作らない)

# フォームをサービスに組み込みたい

と思いました。

- 質問内容を柔軟に変更でき
- ユーザーの状態やカテゴリに応じてフォームを変えたりしたい
- 集計をしたい

モチベーションは様々だと思いますが、
自分で実装するのは割と時間がかかっちゃうので、仮説検証として実験的に
Google Formsを使いたいと思いましたが、自分のサービスに組み込む場合にはフォームの入力画面に遷移してしまうため、
流れを止めてしまう感が否めません。

そこで下記のようなGASを作成し、WebAPIとして使えるようにしました。

また、下記の記事もコンセプトは同じなので少し参考にさせていただきました。
https://qiita.com/michiomochi@github/items/dc3b52d8c71ca305a996

---

# なにはともあれ動くコード

コードはTypeScriptで簡単なインターフェースを定義しています。
[clasp](https://github.com/google/clasp)を利用するとGoogle App Scriptの仕様を理解していなくても書きやすいです。
また、Forms Serviceのドキュメントは[こちら](https://developers.google.com/apps-script/reference/forms)です。



```ts:
interface QuestionItem {
  id: number;
  title: string;
  type: string;
  choices: string[];
}
interface AnswerItem {
  id: number;
  value: string;
  // valuesはチェックボックスの回答の際に利用する
  values: string[];
}
interface Form {
  id: string;
  items: Array<AnswerItem | QuestionItem>;
}

const getItem = item => {
  const itemType = item.getType();
  // フォームの項目は型に応じて適切にキャストする必要がある。(まだ自分が使う型にしか対応してない)
  // https://developers.google.com/apps-script/reference/forms/item-type.html
  // https://developers.google.com/apps-script/reference/forms/item
  switch (itemType) {
    case FormApp.ItemType.TEXT:
      return item.asTextItem();
    case FormApp.ItemType.PARAGRAPH_TEXT:
      return item.asParagraphTextItem();
    case FormApp.ItemType.MULTIPLE_CHOICE:
      return item.asMultipleChoiceItem();
    case FormApp.ItemType.LIST:
      return item.asListItem();
    case FormApp.ItemType.CHECKBOX:
      return item.asCheckboxItem();
    default:
      return {};
  }
};

const getChoices = item =>
  getItem(item).getChoices ? getItem(item).getChoices() : [];

function doGet(e) {
  const form = FormApp.openById(e.parameter.id);
  const items = form.getItems();
  const result: Form = { id: form.getId(), items: [] };
  for (let i = 0; i < items.length; i++) {
    const item = items[i];
    const myItem: QuestionItem = {
      id: item.getId(),
      title: item.getTitle(),
      type: String(item.getType()),
      choices: getChoices(item).map(c => c.getValue())
    };
    result.items.push(myItem);
  }
  return ContentService.createTextOutput(JSON.stringify(result)).setMimeType(
    ContentService.MimeType.JSON
  );
}

function doPost(e) {
  const { id, items }: Form = JSON.parse(e.postData.getDataAsString());
  const form = FormApp.openById(id);
  const formResponse = items
    .map(i =>
      getItem(form.getItemById(i.id)).createResponse(
        i.value ? i.value : i.values
      )
    )
    // withItemResponseはメソッドチェインする
    // https://developers.google.com/apps-script/reference/forms/form-response#withitemresponseresponse
    .reduce((acc, cur) => acc.withItemResponse(cur), form.createResponse())
    .submit();
  return ContentService.createTextOutput(
    JSON.stringify({ id: formResponse.getId() })
  ).setMimeType(ContentService.MimeType.JSON);
}
```
プログラムの細かい部分の解説は割愛しますが、GASを"ウェブ アプリケーションとして導入"してWebAPI化し、getとpostでフォームの取得と送信を行います。

---

### doGet

`doGet`のURLクエリパラメータにフォームのID(フォームの編集画面のURLから確認できます)を指定し、
質問項目を取得します。
リクエストとレスポンスとしてはJSONで下記のようになります。

**リクエスト**

下記のようなフォームがあった場合にはURLは`https://docs.google.com/forms/d/1TGGjHvLyYwTh2QryVYjfyx2ZC1_FRvuAXck7Ha69GCg/edit` ですが、フォームのIDは`1TGGjHvLyYwTh2QryVYjfyx2ZC1_FRvuAXck7Ha69GCg` となります。
![スクリーンショット 2019-10-10 11.04.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204698/a86b5519-0739-4694-c7b7-6f0d50b20d88.png)

あとは"ウェブ アプリケーションとして導入"したGASに対して

```sh
curl -sL https://script.google.com/macros/s/{{scriptId}}/exec?id=1TGGjHvLyYwTh2QryVYjfyx2ZC1_FRvuAXck7Ha69GCg
```
実行すると

---

**レスポンス**

```json
{
	"id": "1TGGjHvLyYwTh2QryVYjfyx2ZC1_FRvuAXck7Ha69GCg",
	"items": [
		{
			"id": 1362911615,
			"title": "名前",
			"type": "TEXT",
			"choices": []
		},
		{
			"id": 676568096,
			"title": "T シャツのサイズ",
			"type": "MULTIPLE_CHOICE",
			"choices": [
				"XS",
				"S",
				"M",
				"L",
				"XL"
			]
		},
		{
			"id": 911411168,
			"title": "その他のご意見またはコメント",
			"type": "PARAGRAPH_TEXT",
			"choices": []
		}
	]
}
```

いいね！
ReactでもVueでもなんでもいいですが、このJSONをスタイリングしてかっこいいフォームを作ってください！

---

###doPost

先ほど取得した質問項目に対してvalueを入力し、回答を送ってみます！

**リクエスト**

あとは下記のフォーマットにしたがってリクエストボディを作成し回答を送信してみましょう。

```json

{
	"id": "1TGGjHvLyYwTh2QryVYjfyx2ZC1_FRvuAXck7Ha69GCg",
	"items": [
		{
			"id": 1362911615,
			"value": "田中太郎"
		},
		{
			"id": 676568096,
			"value": "L"
		},
		{
			"id": 911411168,
			"value": "よろしくお願いします！"
		}
	]
}
```

そして上記のデータを下記のように実行すると...

```sh
curl -X POST -H "Content-Type: application/json" -d "{\"id\": \"1TGGjHvLyYwTh2QryVYjfyx2ZC1_FRvuAXck7Ha69GCg\",\"items\": [{\"id\": 1362911615,\"value\": \"田中太郎\"},{\"id\": 676568096,\"value\": \"L\"},{\"id\": 911411168,\"value\": \"よろしくお願いします！\"}]}" -sL https://script.google.com/macros/s/{{scriptId}}/exec
```

---

**レスポンス**

回答ができました！

![スクリーンショット 2019-10-10 11.40.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204698/acc65592-7702-81cb-e5c7-2a60de898a59.png)

ここが若干不明なんだが、なんかエラーになって返ってくる

```html
<!DOCTYPE html>
<html lang=en>
  <meta charset=utf-8>
  <meta name=viewport content="initial-scale=1, minimum-scale=1, width=device-width">
  <title>Error 411 (Length Required)!!1</title>
  <style>
    *{margin:0;padding:0}html,code{font:15px/22px arial,sans-serif}html{background:#fff;color:#222;padding:15px}body{margin:7% auto 0;max-width:390px;min-height:180px;padding:30px 0 15px}* > body{background:url(//www.google.com/images/errors/robot.png) 100% 5px no-repeat;padding-right:205px}p{margin:11px 0 22px;overflow:hidden}ins{color:#777;text-decoration:none}a img{border:0}@media screen and (max-width:772px){body{background:none;margin-top:0;max-width:none;padding-right:0}}#logo{background:url(//www.google.com/images/branding/googlelogo/1x/googlelogo_color_150x54dp.png) no-repeat;margin-left:-5px}@media only screen and (min-resolution:192dpi){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat 0% 0%/100% 100%;-moz-border-image:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) 0}}@media only screen and (-webkit-min-device-pixel-ratio:2){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat;-webkit-background-size:100% 100%}}#logo{display:inline-block;height:54px;width:150px}
  </style>
  <a href=//www.google.com/><span id=logo aria-label=Google></span></a>
  <p><b>411.</b> <ins>That’s an error.</ins>
  <p>POST requests require a <code>Content-length</code> header.  <ins>That’s all we know.</ins>
```

調べてみると詳細は[こちらのブログ](http://kbn1053.hatenablog.com/entry/2017/12/25/192204)に書いてありますが、
JSONの取得ができないらしい。
doPostで別にJSON返さなくてもいいので。普通にstatus200返せばよいかと。

# まとめ

まだサービスに組み込んでないのですが、かなりシンプルに動的なフォームの作成ができ、満足しました！

Google Formsはフォームの作成はもちろん、集計や回答確認もとってもリッチなので運用のイメージができます。ユーザーの属性に応じてformのidを変えることでフォームの向き先を変更できます。アツいですよね〜

といってもGASなのでプロダクションで動かし続けるのは...とも思います。

サクサクっと作って項目が固まったらアプリケーションに組み込む前提とすれば、ありかと思います！

是非参考にしてみてください。