Google Apps Scriptで始めるWebアプリ開発

こんにちは。ノーコードCMSのBiNDupのフロントエンドエンジニアのオオクボです。

Webアプリ開発の手段は多々ありますが、必要なツールを導入してサーバーを用意して…等々、いざ手を着けてみようとするとなかなか手間かと思います。 しかしGoogle Apps Scriptを使えば、ブラウザ上で実装から公開までを手軽に済ませることが可能です。今回はその手順について解説させて頂きます。

Google Apps Script(GAS)とは


https://workspace.google.co.jp/intl/ja/products/apps-script/

Googleが提供しているプログラミング言語およびプラットフォームです。JavaScriptがもととなっており、文法もこれに準じています。
GoogleスプレッドシートやGmailをはじめとしたGoogle関連サービスと連携・自動化するアプリの実装がしやすい仕組みを備えており、また開発環境を構築をせずとも、Gmailアカウントとブラウザさえあればすぐにコーディングを開始できる点が主な特徴です。

GASの活用例をいくつか紹介すると、

  • Gmailでメールを自動送信する
  • Googleカレンダー上に予定を一括作成する
  • 目的のWebサイトからHTMLを抽出し、Googleスプレッドシートに収集する
  • Googleドライブ上のファイルを定期的にバックアップする

などが挙げられます。特にGoogleスプレッドシートとの相性が良く、シートに入力された情報をもとに他サービスを実行したり、 反対に他サービスの情報をシートに書き込んだりするケースが多いです。

またGoogle関連サービス以外にも、API(Application Programming Interface:特定の機能を外部のプログラムから呼び出す仕組み)が公開されているアプリとの連携も可能です。

  • Twitterでツイートを定期投稿する
  • Slackのbotを作成する
  • freee会計で請求書や伝票を自動生成する

本稿ではメトロポリタン美術館のAPIを使って公開されている西洋画をキーワード検索し、その結果の中からランダムに6件を表示する絵画検索アプリを作成します。

デモ

デモアプリ公開先リンク

メトロポリタン美術館API(The Metropolitan Museum of Art Collection API)

https://metmuseum.github.io/

メトロポリタン美術館が公開しているパブリック・ドメインの作品情報を取得するAPIです。無料・ユーザー登録不要で利用することができます。

取得したい情報の種別に応じて呼び出し方は変わりますが、今回用いるのは下記の2パターンです。

① 検索(作品IDを取得)

https://collectionapi.metmuseum.org/public/collection/v1/search

② 作品IDをもとに詳細情報を取得

https://collectionapi.metmuseum.org/public/collection/v1/objects/[作品ID]

検索結果を絞る為にパラメータを加えた上で①を実行・作品IDを取得し、これをもとに②で作品詳細を取得。
レスポンスに含まれている画像のリンクを抜き出してHTMLに描画するのが絵画検索アプリの処理の流れとなります。

試しに「cat」でキーワード検索するパラメータ ?&q=cat を①の末尾にを加えた「https://collectionapi.metmuseum.org/public/collection/v1/search?&q=cat」をブラウザで開いてみてください。

{"total":44296,"objectIDs":
[545971,203392,544118,489980,486423,49698,482595,199446,489635,49470, ...

のように無数の数字が表示されるはずです。"objectIDs"より後にカンマ区切りで並んでいる6桁の数字らが、catを検索キーワードに含む作品のIDです。

今度は作品IDのうち一つを拾い、②にくっつけて呼び出してみます。「https://collectionapi.metmuseum.org/public/collection/v1/objects/545971」を開いてみてください。

{
  "objectID": 545971,
  "isHighlight": false,
  "accessionNumber": "45.4.6",
  "accessionYear": "1945",
  "isPublicDomain": true,
  "primaryImage": "https://images.metmuseum.org/CRDImages/eg/original/45.4.6_EGDP014408.jpg",
  "primaryImageSmall": "https://images.metmuseum.org/CRDImages/eg/web-large/45.4.6_EGDP014408.jpg",
  ...

少々見づらいので整形させて頂きましたが、このような文字の羅列が表示されます。これが作品ID「545971」の詳細情報であり、タイトルや作者名などが含まれます。
"primaryImageSmall": に続くURLは作品のミニサイズ画像です。アプリではこちらを抜き出して画像を表示します。

実装手順

プロジェクトの準備

Googleドライブにログインし、画面左上の「+新規」ボタンから「その他 > Google Apps Script」を選択します。
新しいタブが開き、無題のプロジェクトが作成されます。

プロジェクト作成直後は、自動生成されるGASファイル「コード.gs」の編集画面が表示されます。
このファイルに、

  • アプリにアクセスされた際、HTMLを呼び出して表示する

  • APIを実行して作品の情報を受け取り、HTMLに引き渡す

の2つ、サーバー側で実行される位置付けの処理を書いていくこととなります。

続けて「コード.gs」によって呼び出されるHTMLを用意しましょう。
エクスプローラー欄右上の「+」ボタンから「HTML」を選択します。

選択後、名前を「index」に変更しエンターキーを押します。同様の手順を繰り返して「style.html」も作成してください。
以下のようになればアプリを構成するファイルの用意は完了です。

コード.gs index.htmlの呼び出しおよび、メトロポリタン美術館APIを実行して作品の情報を取得するスクリプトファイル。
index.html 絵画検索アプリの画面に当たるHTMLファイル。アプリのURLにアクセスするとコード.gsから呼び出され、ブラウザに表示される。
style.html スタイルシートに当たるHTMLファイル。index.htmlにインポート・適用される。

実装

それでは実装に移ります。
先の手順で作成した各ファイルを、下記の通り編集します。

コード.gs

function doGet() {
  return HtmlService.createTemplateFromFile("index").evaluate();
}

function getImg(keyword) {
  const idList = UrlFetchApp.fetch(`https://collectionapi.metmuseum.org/public/collection/v1/search?departmentId=11&hasImages=true&q=${keyword}`).getContentText();
  const idListJson = JSON.parse(idList);
  const objectIDs = idListJson.objectIDs;
  const srcList = [];
  for(let i=0; i<6; i++) {
    const id = objectIDs[Math.floor(Math.random() * objectIDs.length)]
    const artInfo = UrlFetchApp.fetch(`https://collectionapi.metmuseum.org/public/collection/v1/objects/${id}`).getContentText();
    const artInfoJson = JSON.parse(artInfo);
    if(artInfoJson.primaryImageSmall === '') {
      i--;
      continue;
    }
    srcList.push(artInfoJson.primaryImageSmall);
  }
  return srcList;
}

index.htmlを呼び出す doGet() 関数と、APIを実行して作品画像を取得するgetImg() 関数を用意します。
GASではWebアプリにアクセス(GETリクエスト)があった際 doGet と名付けられた関数が実行される仕組みとなっているので、これを利用してindex.htmlを表示させます。

getImg() 関数はメトロポリタン美術館APIのキーワード検索を呼び出して作品ID一覧を取得し、そのうちランダムに6つの作品IDを取り出して詳細情報を取得、中から画像のURLのみ抽出して返す処理となっています。
getImg() 内1行目のAPIによるキーワード検索ですが、departmentId=11(西洋画) と hasImages=true(画像データ保持)パラメータを付加して検索結果を絞っています。

index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <?!= HtmlService.createHtmlOutputFromFile('style').getContent(); ?>
  </head>
  <body>
    <h1>Search The Collection</h1>
    <div>
      <input id="text"></input>
      <button id="search" onclick="doSearch()">🔍</button>
    </div>
    <div>
      <div id="preview"></div>
    </div>
    <script>
      function addImg(srcList) {
        document.getElementById('preview').innerHTML = '';
        srcList.forEach(element => {
          const img = new Image();
          img.src = element;
          document.getElementById('preview').appendChild(img);
        });
      }
      function doSearch() {
        const keyword = document.getElementById('text').value;
        google.script.run
          .withSuccessHandler(addImg)
          .getImg(keyword);
      }
    </script>
  </body>
</html>

アプリの画面に当たるHTMLです。タイトルと検索バー・ボタンおよび検索結果の画像を表示します。
<script> タグ内には、検索が実行された際に「コード.gs」の getImg() 関数を呼び出し、取得した画像のURLをHTMLに埋め込む処理を用意しています。

ここまでのコードでJavaScriptには無い、GAS特有のクラスとメソッドがいくつか登場しました。

HtmlService.createTemplateFromFile(ファイル名) 指定したHTMLファイルからHtmlTemplateオブジェクト(HTMLを動的に構築する為のオブジェクト)を返す。ここではその上で .evaluate() を呼び出し、ブラウザに表示させる。
HtmlService.createHtmlOutputFromFile(ファイル名) 指定したHTMLファイルからHtmlOutputオブジェクト(サニタイズされたHTML)を返す。ここでは更に .getContent() で文字列として読み込み、強制スクリプトタグでindex.html上に埋め込んでいる。
UrlFetchApp.fetch(URL) 指定したURLにHTTPリクエストを実行し、そのレスポンスを取得する。ここではその上で .getContentText() を呼び出し、レスポンスを文字列化している。
google.script.run.withSuccessHandler(コールバック関数).GASに定義した関数 GASファイルに定義された関数(ここでは getImg() )を呼び出し、実行に成功したらコールバック関数(addImg())を呼び出す。

リソース(HTMLファイル)へのアクセスやフロント側(index.html)とサーバー側(コード.gs)の連携では、このようにGASが用意しているメソッドを利用する必要が出てきます。

style.html

<style>
  body > h1 {
    display: flex;
    justify-content: center;
  }
  body > div:nth-child(2) {
    display: flex;
    justify-content: center;
    margin-bottom: 64px;
  }
  body > div:nth-child(3) {
    display: flex;
    justify-content: center;
  }
  #preview {
    width: 80%;
    text-align: center;
  }
  #preview > img {
    margin: 16px;
    width: 460px;
    height: auto;
  }
</style>

CSSファイルの作成ができない為、この style.html をスタイルシートとして扱います。
もちろん index.html に直書きすることも可能ですが、記述量が増えて読みにくくなってしまう為、切り離すことを推奨します。

デプロイ

作成したアプリをサーバー上に配置し、公開します。
エディタ画面右上の「デプロイ」から「新しいデプロイ」を選択します。

開かれたダイアログ左上の歯車アイコンボタンをクリックし、「ウェブアプリ」を選択します。

「アクセスできるユーザー」を「自分のみ」から「全員」に変更し、「デプロイ」をクリックします。

今回のアプリでは美術品の情報を取得するためにAPIを用いて外部にアクセスしているので、その許可が要されます。
「アクセスを承認」をクリックします。

アプリ作成に用いていたGoogleアカウントを選択し、ログインします。

「許可」をクリックします。

デプロイが完了しました。
URLが表示されるのでこれをクリックすると、別タブで絵画検索アプリが開かれます。
このURLはエディタ画面右上の「デプロイ > デプロイを管理」からいつでも確認することができます。
「完了」をクリックしてダイアログを閉じます。

まとめ

以上がGoogle Apps ScriptでWebアプリを実装~公開するまでの流れとなります。
GASですべてが完結してしまうため環境構築で迷うこともなく、またJavaScriptに触れた経験があれば理解もしやすいので、Webアプリ開発の入門にはうってつけかと思います。
今回は外部サービスのAPIを用いて構築しましたが、Googleサービスとの連携前提で考えれば題材も豊富ですし、是非始めてみることをおすすめします。

付録(再デプロイ / console.log()の使い方 / デバッグ方法)

再デプロイ

コードに変更を加えた場合、アプリに反映するべく再度デプロイが必要となります。
先で説明した手順同様「新しいデプロイ」からでも実行することは可能なのですが、アプリのURLが変わってしまう為、これを回避するデプロイ手順を解説します。

エディタ画面右上「デプロイ > デプロイを管理」を選択します。

ダイアログ右上の鉛筆アイコンボタンをクリックし、バージョンのメニューから「新バージョン」を選択、「デプロイ」をクリックして完了です。

GASファイル上での console.log() の使い方

JavaScriptでのアプリ開発において値を確認する際によく使われる console.log() ですが、もちろんGASでも有効です。
但し、HTMLの <script> タグ内で使われた場合は値をブラウザの開発者ツールから確認できますが、GASファイル内で使われた場合はGAS上のコンソールからでしか確認ができないので注意が必要です。
コード.gsを一部書き換えて、コンソールに値を出力させてみます。

getImg() 関数の引数 keyword(検索バーに入力された文字列を受け取る定義)をコメントアウト
const keyword = 'gogh' を追加し、検索キーワードに「gogh(ゴッホ)」を指定
console.log(idList) を追加し、「gogh」で検索した結果の作品ID一覧のコンソール出力命令を差し込む
getImg() 関数を実行対象に指定
⑤ 「実行」をクリック

エディタ画面下部にコンソールウインドウが表示され、idList の中身(「gogh」の検索結果の作品ID一覧)が確認できます。

デバッグ

GASでは、任意の行で処理を止め変数の内容などを確認できるデバッグも可能です。
「GASファイル上での console.log() の使い方」で書き換えた「コード.gs」を流用し、デバッグを実行してみます。

① ブレークポイントを設定
getImg() 関数を実行対象に指定
③ 「デバッグ」をクリック

エディタ画面右側にデバッグコンソールが表示され、ローカル及びグローバルの変数が確認できます。
尚、赤枠線内の各ボタンでデバッグを制御することも可能です。機能は左から順に下記の通りです。

再開 次のブレークポイントまで処理を進める(ブレークポイントが無ければ最後まで実行する)。
ステップオーバー 1行実行する。自作関数が呼び出された場合はその関数内に潜る。
ステップイン 1行実行する。自作関数が呼び出された場合、実行はされるがその関数内に潜らない。
ステップアウト 現在の関数を最後まで実行する。

オオクボ カテゴリーの記事一覧 -