シナプス技術者ブログ

シナプスの技術者公式ブログ。インターネットで、鹿児島の毎日を笑顔にします。

Google Apps Scriptでウェブアプリケーションを作ってみた。

シナプスの技術部システム開発課の小園です。
Google Apps Scriptでウェブアプリケーションを作ってみました。

Google Apps Script とは

  • Google Apps Script(以下GAS)は、Googleが提供するクラウドベースのスクリプト言語で、Googleの各種サービス(Googleドキュメント、Googleスプレッドシート、Googleフォームなど)に統合された独自の開発環境を持つ、サーバーレスなプログラミングプラットフォームです。
    www.google.com
    developers.google.com

  • GASでは以下のように様々なことができます。

    • Googleの各種サービスとの連携
      • Gmail、Googleドキュメント、Googleカレンダー、Googleスプレッドシートなど、Googleの様々なサービスと簡単に連携することができます。例えば、Gmailの自動返信や、Googleカレンダーのイベント作成、Googleスプレッドシートのデータ処理などが可能です。
    • 外部APIとの連携
      • 外部APIとの連携も可能です。例えば、Twitter APIやSlack APIなどのAPIを使って、外部サービスとGASを連携させることができます。
      • スプレッドシートの外部関数から外部APIを利用する事で、強力な関数を用意することができます。
    • トリガー機能による定時実行
      • 指定した時間や条件でスクリプトを実行可能です。
    • ウェブアプリケーションの作成
      • 簡単なウェブアプリケーションを作成することができます。例えば、Webフォームを作成して、スプレッドシートにデータを保存するアプリケーションを作成することができます。
  • GASはV8 ランタイムで動いており、ES2015(ES6)の構文で書けます。
    V8 ランタイムの概要  |  Apps Script  |  Google Developers
  • GASは無償のGoogleアカウント、有償のGoogle Workspaceアカウント どちらでも利用できます。

GASでウェブアプリケーションとは?

GASでは、ブラウザからのGET/POSTを処理する機能やデータソースにアクセスできる機能があります。
その機能を使う事で、GASで簡単なウェブアプリケーションを作成する事ができます。

  • HTML、CSS、JavaScriptを使用して、ウェブアプリケーションのフロントエンドを作成することができる HTMLサービスというサービスがあります。
    ウェブアプリ  |  Apps Script  |  Google Developers
  • Googleが提供するサービスとのシームレスな統合を可能にするAPIを提供しており、Googleサービスの機能を呼び出して、データの取得、処理、更新、保存を行うことができます。

作ったもの

  • Googleフォームで入力されたコンテンツをGoogleスプレッドシートに保存し、GASからスプレッドシートにデータを取得し、そのデータを表示するウェブアプリケーションを作成しました。
    連携図は以下です。
graph LR;
    subgraph Googleクラウド
        Googleフォーム -- データ保存 --> Googleスプレッドシート;
        GAS -- データ取得 --> Googleスプレッドシート
    end
    subgraph ブラウザ
        vue.js_Webサイト -- HTTPリクエスト --> GAS;
        vue.js_Webサイト -- JSONデータ取得 --> GAS;
    end
  • この中でGASはウェブアプリケーションのコントローラー機能と、フロントエンドからのデータ取得機能を兼用した機能を持っています。

ウェブアプリケーションで利用するコンテンツ

  • Googleスプレッドシートにデータを用意しました。
  • データはA列から、index、タイムスタンプ、タイトル、日付、リード文、本文のデータを持っています。
    この中で、indexとタイムスタンプは自動的に付与したデータになっています。
  • Googleフォームに入力されたコンテンツをGoogleスプレッドシートに保存する箇所は今回の記事内では省略しています。

ウェブアプリケーション ブラウザ側の技術要素

  • ウェブアプリケーションは、以下の技術を用いて構築しました。
    • Vue.js v2.7
      jp.vuejs.org
      • フロントエンド(ブラウザ)側のJavaScriptフレームワーク
      • Vue.js はビルドなしで構築しました。ビルドをする場合には、clasp(google/clasp [Command Line Apps Script Projects])を利用します。
        github.com
    • Bootstrap5
      getbootstrap.jp
      • フロントエンド(ブラウザ)側のUIフレームワーク

Webサイト内容

最終的なファイル構成

  • GAS上での最終的なファイル構成は以下のようになります。
    • GASでは、.jsや.cssをそのまま保存できません。必ず.htmlという拡張子になります。
  • 最初は、コード.gsのみが存在していますので、必要なファイルを追加、編集していきます。
  • ファイル追加は右側の+から行います。
    • 拡張子 .html, .js , .css のファイルを追加する場合には、+押下後にHTMLを選択します。
      • ブラウザ側で動かすjavascriptファイルはHTMLを選択します。
      • index.htmlを作成したい場合には、indexと入れます。
        拡張子は入力しません。
        自動的に拡張子.htmlが付加されます。
      • .js又は.cssファイルを作成したい場合には、拡張子まで含めて入れます。
        index.jsを作成したい場合には、index.jsと入れます。
        実際に作成されるファイル名は拡張子.htmlが付加され、index.js.htmlとなります。
    • クラウド側で動かすjavascriptファイルを追加する場合には、+押下後にスクリプトを選択します。
      入力するファイル名は拡張子を除いたものにします。
      自動的に拡張子.gsが付加されます。
  • appsscript.jsonは画面左側の歯車マーク プロジェクトの設定 の"「appsscript.json」マニフェスト ファイルをエディタで表示する"にチェックを入れた場合に表示されるファイルです。
    今回の開発内容ではチェックを入れても、入れなくても影響はありません。

GASプロジェクト作成

  • 最初にGoogleドライブから GASプロジェクト を作成します。
    • その他の中にある Google Apps Script を選択します。
    • 紐づくアプリケーションが存在しないGASプロジェクトを作成します。
      • スタンドアロンスクリプトと呼ばれます。
        スプレッドシート、フォーム等のアプリケーションに紐づいたGASプロジェクトは、コンテナバインドプロジェクトと呼ばれます。

GASスクリプト

コード.gs
  • プロジェクト作成時に作られる(存在している)ファイルです。
    ファイル中に最初から存在する関数myFunctionは不要です。削除しても問題ありません。
  • doGet関数は、ブラウザからのGETメソッドでアクセスした場合に呼ばれます。
    • POSTメソッドに対応したdoPost関数もあります。
  • GETやPOSTのパラメータは、parameter又はparametersオブジェクトに格納されています。
    コード中のe.parameter.indexの箇所です。
  • e.parameter.indexはGETの場合、URLクエリパラメータ(クエスチョン以降)のindexの値が設定されています。
    http://exapmle.com?index=[この値]
  • GASプロジェクト内に作成したjsやcssは外部ファイルとしてhtml(ブラウザ)から呼び出せないので、include関数でhtml内にファイルの中身を展開します。
// コンテンツデータを保存したスプレッドシートのid
const sheet_id = "**********************************************************";
// スプレッドシートのシート名
const sheet_name = "*******";
const page_title = "GAS ウェブアプリケーション サンプル";

// GETメソッドでアクセスすると実行される関数
function doGet(e)
{
  const index = Number(e.parameter.index);
  if(!index)
  {
    return HtmlService.createTemplateFromFile('error').evaluate();
  }
  const template = HtmlService.createTemplateFromFile('index');
  template.title = page_title;
  template.index = index;
  return template.evaluate();
}
// htmlから他ファイル(.js、.cssなど)を読み込んで展開するためのHelper関数
function include(filename)
{
  return HtmlService.createHtmlOutputFromFile(filename)
      .getContent();
}
// スプレッドシートからデータ一覧を取得
function getSpreadsheetValues()
{
  const spreadsheet = SpreadsheetApp.openById(sheet_id);
  const sheet = spreadsheet.getSheetByName(sheet_name);
  const table = sheet.getDataRange().getDisplayValues().slice(1);
  const filterd_table = table.filter(a => a[1] != "");
  return filterd_table;
}
// スプレッドシートデータ一覧から指定行を取得
function getSpreadsheetValue(index)
{
  const rows = getSpreadsheetValues();
  if(rows.length < index){
    return [];
  }
  return rows[index-1];
}

htmlファイル

index.html
  • コンテンツを表示するHTMLです。
  • <?!= 関数(); ?> はGASの関数を実行して、結果を展開します。
  • <?= 変数 ?>はGASスクリプトで定義した値を展開します。
  • include関数はサーバサイトでファイルの中身が展開されてブラウザに渡します。
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title><?= title ?></title>
  <?!= include('libraries.css'); ?>
  <script>
    const index = new Number(<?= index ?>);
  </script>
</head>
<body>
  <header>
    <nav class="navbar navbar-dark bg-primary mb-2">
      <div class="navbar-header"><a class="navbar-brand" href="#"><?= title ?></a></div>
    </nav>
  </header>
  <div class="container" id="app">
    <main role="main" class="container">
      <div class="row">
        <div class="col-md-8 blog-main">
          <div class="blog-post">
            <h2 class="blog-post-title">{{ article[2] }}</h2>
            <p class="blog-post-meta">{{ article[3] }}</p>
            <div>
              <p>
                {{ article[4] }}
              </p>
              <p>
                {{ article[5] }}
              </p>
            </div>
          </div>
        </div>
      </div>
    </main>
  </div>
  <?!= include('libraries.js'); ?>
  <?!= include('index.js'); ?>
</body>
</html>
error.html
  • エラー時に表示されるHTMLです。
  • しっかり作る時には、エラー内容などが伝えられるように作りましょう。
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>error</title>
</head>
<body>
  <div>
    <span>error</span>
  </div>
</body>
</html>

cssファイル

libraries.css.html
  • bootstrapのcssを読み込んでいます。
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">

jsファイル

libraries.js.html
  • Vue.jsとbootstrapを読み込んでいます。
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
index.js.html
<script>
var app = new Vue({
  el: "#app",
  data: {
    article: [],
    index: index,
  },
  mounted() {
    const self = this;
    self.loadSheetIndexValues();
  },
  methods: {
    loadSheetIndexValues: async function()
    {
      const self = this;
      self.article = await self.getSheetData();
    },
    getSheetData: function()
    {
      const self = this;
      return new Promise((resolve, reject) => 
      {
        google.script.run
          .withSuccessHandler((result) => resolve(result))
          .withFailureHandler((error) => resolve(error))
          .getSpreadsheetValue(self.index.valueOf());
      });
    },
  },
})
</script>

デプロイ

  • デプロイをする事で、ウェブアプリケーションとしてブラウザからアクセスできるようになります。
  • プロジェクト右側のデプロイボタンから新しいデプロイを選びます。
  • 新しいデプロイの画面が表示されるので、各項目に値を設定してデプロイを選択します。
    • 次のユーザーとして実行は、自分又はウェブアプリケーションにアクセスしているユーザのどちらかを選択します。
      ウェブアプリケーション開発時にアプリ開発/保守/実行をするユーザを作成する方法を取る事が多いと思います。
      その場合は自分を選択します。
      • ウェブアプリケーションの実行時にリソース(スプレッドシートなど)に対して権限を付与する必要があります。ウェブアプリケーションにアクセスしているユーザを選択すると、アクセスする全ユーザに権限付与を設定する必要があります。
    • アクセスできるユーザーは、自分のみ又はGoogleアカウントを持つ全員又は全員のいずれかを選択します。
      有償のGoogle Workspaceアカウントを利用の場合には、そのグループに属する人を対象にする選択子が増えます。
  • デプロイをすると以下の画面が表示されます。
    ウェブアプリケーションのURLが確認できます。

ブラウザ表示内容

  • ブラウザに表示される内容です。
  • デプロイ時に表示されるURLをブラウザに入力します。
    • 今回はindexというURLクエリパラメータを必要としています。デプロイ時に表示されたURLの最後に?index=1のような文字列を付加します。
  • 画面上部にGoogleからの注記が表示されます。
    これは、無償のGoogleアカウントを利用したため、URLのドメインがGoogleの所有物であるscript.google.comであるためです。
    有償のGoogle Workspaceを利用した場合(独自ドメインを利用している場合)はこの注記は表示されません。

良かった事/困った事

良かった事

  • 認証が楽。
    • 特にGoogleWorkspaceでは、組織単位の公開が簡単にできる。
  • 無料で動かせる。
    • データソース(スプレッドシート)、Webサーバなども無料で利用可。
  • デプロイの概念があり、公開バージョンの管理や、デプロイのテストをする機能がある。

困った事

  • URLがGoogle側にて生成するものになる。
    • URLが長い。
    • URLに処理内容の意味を持たせる事ができない。
    • URLからコードに辿る事ができない。
  • GASのリソース枠を消費すると使うことができない。
    • リソース枠を消費したときに、有償でリソース枠を増やすことはできない。
    • 無償のGoogleアカウント、有償のGoogle Workspaceアカウントでリソース枠は異なる。
  • Vue.js devtools が使えない。
  • スプレッドシートをデータベースとして扱うのは辛い。
    • トランザクションが無い。
    • インデックスが無い。
    • SQLのように条件(WHERE)、並び順(ORDER)を書けない。
      • WHEREはQUERY関数を使う方法がある。
    • Cloud SQLを使う方法もある(JDBCを使える)が、費用が掛かる。
    • Googleフォームズから簡単にデータ投入できるメリットはあるが…

作ってみての所感

小規模で簡易なウェブアプリケーションを作る事は可能。
Googleスプレッドシートをデータソースとするウェブアプリケーションは簡単に作成できるが、ある程度規模が大きくなるとボトルネックになる。
リソース枠を超えての使うことは出来ないので、そこもボトルネックになる。