Google Apps Script (GAS)と、Google Workspace製品だけで実現するFull StackのWeb開発

2022年12月20日掲載

キービジュアル

2021年、エンジニアとしてソフトバンクに新卒入社しましたが、最初の仕事はクラウド製品の企画です。

企画職と言うと、プレゼン資料作成がメインで、Webサイト作成には遠いイメージがあると思いますが、今の時代ではどんな仕事でも積極的な社内・社外発信を求められるため、意外にもWebサイト作成の需要が多々あります。

そこで、Google Apps Scriptを使って、手軽にできる本格Webサイトの作成方法についてまとめてみました。

目次

TL;DR

Google Apps ScriptGoogle SheetsGoogle Site、この三種の神器だけで、Google製品の範囲内で、しかも無料で、Full StackのWeb開発ができます。

メリット:

  • 無料!
  • 簡単にGmailなどのGoogleサービスと連携できる
  • 開発環境構築は簡単
  • Google Accountによる高いセキュリティ実現

構成コンポーネント:

  • フロントエンド:Google Apps Script(GAS)+ 生のHtml、CSS、Javascript
  • バックエンド:Google Sheets
  • インフラ:Google Apps Script(GAS)+ Google Site
  • ツール:clasp

Full StackのWeb開発の定義

まずは、ここで使う「Full StackのWeb開発」という言葉の定義から説明したいと思います。

基本的には、Webサイトは静的なWebサイトと、動的なWebサイトとに分類できます。今の時代では、ほとんどのWebサイトは後者に当たります。

動的なWebサイトの特徴としては、Webページの内容は固定ではなく、Webサイトのデータが変化する度に、Webページの内容も自動的に変化します。天気予報のサイトは、ずっと同じ日の天気しか書いてないと使い物にならないですね。

基本的には、全てのWebページは、HTML、CSS、JavaScript、3種類のテキストファイルによって表現されています。動的Webサイトは、ユーザにアクセスされる度に、以上のファイルを毎回新しいものを自動生成する仕組みが必要です。※Single Page Application (SPA)の場合を除きます。

この仕組みは、一般的にはさらに以下2つの部分に分解することができます。

  • Webサイトの見た目の大枠を定義する部分、フロントエンド
  • Webサイトの内容を動的に生成する部分、データを保存する部分、バックエンド

ここでは、上記の仕組みで動的Webサイトを作成することを「Full StackのWeb開発」と定義します。

一般的なFull StackのWeb開発の手法

ここでは、現在よく使われている開発技術を幾つか紹介します。

フロントエンド

Webページを表現するため、HTML、CSS、JavaScript、3種類のファイルが必要です。これらのファイルを効率よく開発、生成してくれるフレームワークが存在する。

近年で代表的なフロントエンドフレームワークとして、AngularReactVueなどがあります。

バックエンド

バックエンド側では、Webブラウザと直接やり取りしませんので、HTTPリクエストを処理できれば、どんな言語で書かれても構いません。

近年で代表的なバックエンドフレームワークとして、

などがあります。

インフラ

ソフトウェア面の技術以外に、もう一つ重要な要素として、Webサイトを公開するためのサーバーを用意する必要があります。どんなに優れたソフトウェアであっても、それを実行するハードウェアがないと意味がないので、実際にパーツを買って、自分でサーバーを組み立てる必要があります・・・

と言いたいところですが、時代の進歩が凄まじく、今ではマウスで数回クリックするだけで、クラウド上で仮想サーバーを立てられます。

  • Amazon Web Service (AWS)
  • Microsoft Azure
  • Google Cloud Platform (GCP)
  • Alibaba Cloud

以上の4つはシェアが一番高いクラウドサービスとなります。基本的には、サーバーをレンタルするサービスと考えていただいても差し支えありません。無料トライアルを除けば、時間単位でレンタル料金を支払う有料サービスです。

Google Workspaceだけで実現するWeb開発の手法

前文と同じ図になりますが、簡単に言うと、フロントエンドをGASに、バックエンドをGoogle Sheets (+ GAS)に置き換えればいいです。

メリット

  • 何と言っても無料でサイトを作れるのが一番嬉しいところです。

  • GmailなどのGoogle製品と、簡単に連携できます。

  • 軽めの開発だと、何の環境設定も必要なく、ブラウザだけでできます。

  • 権限周りはGoogle様がしっかり見てくれますので、セキュリティ性が高い。

デメリット

  • データベースをGoogle Sheetで無理やりに代用するため、性能面は期待しない方がいいかもしれません。
  • GAS上で直接利用できるライブラリ数が少ないです。即ちReactなどのフレームワークの利用は面倒です。(WebPackなどの外部ツールの利用が必要)
  • GASの独自仕様(Templated Htmlなど)が存在し、利用するためには一定の勉強が必要です。

Google Apps Script(GAS)によるWebサイトの作成

それでは、実際にGASを使って、簡単なWebページを作ってみましょう。

Step 1. GASのWebサイトを開き(Google Accountは必要)、新しいプロジェクトを作成します。

Step 2. 「Hello World」のページを作ります。

「index.html」ファイルを追加し、以下の内容を入力し、保存します。

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <p>Hello World!</p>
  </body>
</html>

単純に「Hello World!」を表示するだけの簡単なWebページです。

Step 3. GASでHTTPリクエストに応答させます。

普通では、Webサーバーを立てて、色々設定しないとできませんが、GASを利用することで、簡単にできます。

デフォルトで作られた「コード.gs」を開き、以下の内容を入力し、保存します。

(説明:本記事ではよりモダン的なのJS書き方(ES6)でコードを作成します。)

const doGet = (request) => {
  return HtmlService.createTemplateFromFile('index')
    .evaluate();

Step 4. 作成したWebページを公開する

全てのファイルが保存されたことを確認し、右上の「デプロイ」ボタンを押し→「新しいデプロイ」を押すと、ポップアップウィンドウが表示され、必要なパラメータを入力し、右下の「デプロイ」を押す。

少し待つと、デプロイ成功の画面が見えました。これでWebページは無事に公開されたことになります。

実際にURL (https://script.google.com/maros/...) をクリックすると、公開された「Hello World!」のページを見ることができます。

一番上で、注意メッセージが表示されますが、「Hello World!」は無事に表示されました。(注意メッセージを消す方法は後述)

CSS、JavaScriptの利用

直接にhtmlファイルにCSS、JavaScriptを書き込むこともできますが、ここではGoogle推奨のベストプラクティスに従って、ファイルを分離する形で利用します。

Step 5. 「コード.gs」に、ファイル読み込みのヘルパー関数、「include」を追加します。

const doGet = (request) => {
  return HtmlService.createTemplateFromFile('index')
    .evaluate();
}

const include = (filename) => {
  return HtmlService.createHtmlOutputFromFile(filename)
    .getContent();
}

Step 6. 「css.html」、「script.html」を作成し、「index.html」で参照できるように、「index.html」の内容を少し変更します。

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <?!= include('css'); ?>
  </head>
  <body>
    <p>Hello World!</p>
    <?!= include('script'); ?>
  </body>
</html>

新たに追加された2行は、「css.html」と「script.html」の内容を、「index.html」にコピー&ペーストするイメージです。

Step 7. 「css.html」と「script.html」の内容を編集します。

<style>
p {
  color: green;
}
</style>
<script>
  window.addEventListener('load', ()=> {
  alert('Page is loaded');
});
</script>

Step 8. 編集した内容を保存し、もう一回デプロイする

お勧め機能:デプロイテスト機能を利用すると、開発期間は毎回デプロイし直さなくても大丈夫です。同じURLで保存された変更は即時に反映されます。

test deploy

すると、今回の変更は無事に反映されたことが確認できます。

(ポップアップメッセージと、緑色の「Hello World!」)

Templated Htmlの利用

Templated Htmlとは

これもまたGASの独自仕様になりますが、Django、Rubyなどバックエンドフレームワークを利用したことのある方は、似たような仕組みを経験したことがあると思います。

これは、特殊なタグ内にJavaScriptのコードを書くと、GASはそれを解釈して、動的にhtmlの内容を書き変えてくれます。

実は、前の手順の中でも利用されていますが、<? ... ?><?= ... ?><?!= ... ?>3つのバージョンが存在し、使い分けは以下になります。

  • <? ... ?>は、中身のJSのデータがhtmlに反映されません、中身のJSのロジックだけが反映されます。
  • <?= ... ?>は、中身のJSのデータ(最初のデータだけ)も、ロジックもhtmlに反映されます。
  • <?!= ... ?>は、 <?= ... ?>と似ていますが、内容に対するセキュリティチェックを行わないため、ユーザの入力データなどに使われないのが無難です。

説明だけでは理解しにくいので、実際に「index.html」を編集して、Templated Htmlを使ってみます。

Step 9. 「index.html」を編集します。

<!DOCTYPE html>
<html>

<head>
  <base target="_top">
  <?!= include('css'); ?>
</head>

<body>
  <p>Hello World!</p>
  <p>今の時間は
    <?= new Date() ?>です。
  </p>

  <ul>
    <? const data = ['カレー', 'うどん', '魯肉飯'];
      for (let i = 0; i < data.length; i++) { ?>
    <li>
      <?= data[i] ?>
    </li>
    <? } ?>
  </ul>

  <?!= include('script'); ?>
</body>

</html>

<? ... ?>に、for loopのロジック部分を入れて、<?= ... ?>にデータ(現在時刻、料理の名前)を入れると、htmlに反映されます。

しかし、この書き方では、<? ... ?><?= ... ?>の部分が頻繫に入れ替わり、少々読みづらいです。そこで、ある「隠し仕様」を利用することで、コードを少し綺麗に書き直せます。

<!DOCTYPE html>
<html>

<head>
  <base target="_top">
  <?!= include('css'); ?>
</head>

<body>
  <p>Hello World!</p>
  <p>今の時間は
    <?= new Date() ?>です。
  </p>

  <ul>
    <? 
    const data = ['カレー', 'うどん', '魯肉飯'];
    for (let i = 0; i < data.length; i++) {
      output._ = "<li>"
      output._ = data[i]
      output._ = "</li>"
     } 
    ?>
  </ul>

  <?!= include('script'); ?>
</body>

</html>

<? ... ?>の内部に、output._ = xxxという形式で書くと、本来データが反映されないはずの<? ... ?>でも、xxxのデータが反映されます。<?= ... ?>を使わなくても済みます。

Webページを公開すると以下のようになります:

「アメリカ東部標準時」に気になる方は、GASの設定を開き、「appsscript.json」マニフェスト ファイルをエディタで表示するという項目にチェックを入れて、

新しく表示された「appsscript.json」というファイルの「timeZone」という項目の内容を"Asia/Tokyo"に変更すると、日本時間で現在時刻を表示できます。

Google Sheetsとの連動

先まで作ったページでは、「カレー, うどん, 魯肉飯」は、htmlにハードコードされたもので、html自体に変更を加えないと「炒飯、寿司、天ぷら」に変更できませんので、動的Webサイトとは言えません。

そこで、データをGoogle Sheetsに保存し、必要な時にGASによってデータを読み取り/書き込み、Google Sheetsを疑似データベースとして運用する仕組みを作ってみます。

Step 11.  同じGoogle Accountで、データベース用のGoogle Sheetsを作成します。

ここでは、作成されたGoogle SheetsのURL部分に注目してください。もしURLはhttps://docs.google.com/spreadsheets/d/abc1234567/edit#gid=0の場合、中の`abc1234567`の部分は、このGoogle SheetsのIDとなります。このIDは後で使います。

new sheet

Step 12.「コード.gs」を編集し、Google Sheets内のデータを読み取る関数を追加します。

const doGet = (request) => {
  return HtmlService.createTemplateFromFile('index')
    .evaluate();
}

const include = (filename) => {
  return HtmlService.createHtmlOutputFromFile(filename)
    .getContent();
}

const getDinner = () => {
  const ss = SpreadsheetApp.openById("1234567abcdefg"); // 作成されたGoogle SheetsのIDを記入
  const sheet = ss.getSheetByName("Dinner"); // 作成されたシートの名前を記入
  const data = sheet.getRange(1, 1, 1, 3).getValues();
  return data[0];
}

ここでは、SpreadsheetApp.openByIdと先ほど記録したIDでデータベース用のGoogle Sheetsを特定し、さらにgetSheetByNameとシートの名前でデータが入ったシートを特定します。

getRangeの後ろに続く、(1, 1, 1, 3)という暗号みたいな数字は、選択したいデータの範囲を示した数字です。最初の (1, 1, は、データの一番左上のセルの座標で(1行1列目)、その後に続く 1, 3) は、選択したい矩形範囲の大きさです(高さ1行、長さ3列)。範囲を選択した後、getValuesで実際にその範囲内のデータを読み取ります。

最後に、dataではなく、data[0]を返す理由としては、getValuesで読み取られたデータは、2次元配列になります。つまりこの場合、dataの値は["カレー", "うどん", "魯肉飯"]ではなく、[["カレー", "うどん", "魯肉飯"]]となり、余計な[]を取り除くため、data[0]を返しました。

Step 13. 「index.html」を編集し、先ほど追加したgetDinner関数で、データベース用Google Sheetsからデータを読み込みます。

<!DOCTYPE html>
<html>

<head>
  <base target="_top">
  <?!= include('css'); ?>
</head>

<body>
  <p>Hello World!</p>
  <p>今の時間は
    <?= new Date() ?>です。
  </p>

  <ul>
    <? 
    const data = getDinner();
    for (let i = 0; i < data.length; i++) {
      output._ = "<li>"
      output._ = data[i]
      output._ = "</li>"
     } 
    ?>
  </ul>

  <?!= include('script'); ?>
</body>

</html>

<? ... ?>の内部に、GASに記載されたJS関数を任意に呼び出すことが可能なので、const data = getDinner()によって、htmlにデータをハードコードすることを避けました。

Webページを公開すると、同じ内容が表示されますが、データベース用のGoogle Sheetsを編集することで、食べたい晩御飯の内容へ任意で変えることができます(Webページがリフレッシュする度に、Google Sheets上の最新内容が反映されます)。

初回サイトを開く時、以下のような警告が出てくる場合がありますが、GASとGoogle Sheetsにまずい個人情報などが入ってない限り、灰色の文字の部分(詳細を表示、XXX(安全ではないページ)に移動)を順次に押して大丈夫です。

alert 1
alert 2

GASにおいて、フロントエンド側のコードとバックエンド側のコード違い

通常のWeb開発において、フロントエンド側(Client側)のコードとバックエンド側(Server側)のコードは、お互い独立された環境で開発される場合がほとんどですが、GASを利用する場合、フロントエンドもバックエンドもGAS上で開発されることになり、2種類のコードが混在する場合があります。

具体的には:

  • 「.gs」のファイルに書いたコードは、バックエンド側のコードです。
  • 「.html」のファイルに書いて、かつ<? ... ?><?= ... ?><?!= ... ?>タグで囲まれたコードも、バックエンド側のコードです。
  • 「.html」のファイルに書いて、かつ<script>タグで囲まれたコードは、フロントエンド側のコードです。

又、GASの独自仕様として、フロントエンド側からバックエンド側のコードを自由に呼び出す仕組みが用意されています。

この仕様を使うと、「ページ上で何かの操作をした」→「その操作に応じて、バックエンド側に何らかの処理をする」→「処理した結果をページ上でフィードバックする」のような機能を簡単に実装することができます。

ここでは、上記の仕組みを理解するため、自分好みの晩御飯をサイトに任意に書き込むことができるように、サイトをさらに改造します。

Step 14. 「index.html」を編集し、入力欄と登録ボタンを追加します。

<!DOCTYPE html>
<html>

<head>
  <base target="_top">
  <?!= include('css'); ?>
</head>

<body>
  <p>Hello World!</p>
  <p>今の時間は
    <?= new Date() ?>です。
  </p>

  <ul id="dinners">
    <? 
    const data = getDinner();
    for (let i = 0; i < data.length; i++) {
      output._ = "<li>"
      output._ = data[i]
      output._ = "</li>"
     } 
    ?>
  </ul>

  <input type="text" id="dinner" placeholder="好きな晩御飯を入力" size="20" value="">

  <button class="addDinner" type="button" onclick="addDinner()">
    晩御飯を追加
  </button>

  <?!= include('script'); ?>
</body>

</html>

「sript.html」を編集します。

<script>
  const addDinner = () => {
    const textbox = document.getElementById("dinner");
    const inputValue = textbox.value; // 入力内容の取得
    if (inputValue) {
      google.script.run
        .withSuccessHandler(addElement)
        .addDinnerToSS(inputValue); // データを書き込む
    }
  }

  // ウェブページに入力内容を反映する
  const addElement = (inputValue) => {
    const ul = document.getElementById("dinners");
    const li = document.createElement("li");
    li.appendChild(document.createTextNode(inputValue));
    ul.appendChild(li);
  }
</script>

「script.html」の中身はフロントエンド側のコードですが、google.script.runを使って、「コード.gs」(後述)のバックエンド側のコード(addDinnerToSS)を呼び出すことができます。さらに、withSuccessHandlerによって、バックエンド側の戻り値を、フロントエンド側のコードに渡すことができます。

「コード.gs」を編集します。

const doGet = (request) => {
  return HtmlService.createTemplateFromFile('index')
    .evaluate();
}

const include = (filename) => {
  return HtmlService.createHtmlOutputFromFile(filename)
    .getContent();
}

const getSheet = () => {
  const ss = SpreadsheetApp.openById("1234567abcdefg"); // 作成されたGoogle SheetsのIDを記入
  const sheet = ss.getSheetByName("Dinner"); // 作成されたシートの名前を記入
  return sheet
}

const getDinner = () => {
  const sheet = getSheet();
  const lastColumn = sheet.getLastColumn() // 最後の列を取得
  const data = sheet.getRange(1, 1, 1, lastColumn).getValues();
  return data[0];
}

// Google Sheetにデータを書き込む
const addDinnerToSS = (dinner) => {
  const sheet = getSheet();
  const lastColumn = sheet.getLastColumn()
  sheet.getRange(1, lastColumn + 1).setValue(dinner)
  return dinner
}

これによって、食べたい晩御飯を好きなだけ追加することができるようになりました。

GoogleSiteの利用

今までずっと悩まされていた、ページの一番上に表示されている「このアプリケーションは、Googleではなく、別のユーザによって作成されたものです。」という通知メッセージの対処法について紹介したいと思います。

warning

対処法は簡単で、Google SiteのURL埋め込み機能を利用すれば、メッセージが表示されなくなります。

ページ全体のURL埋め込み機能を利用しても大丈夫です。

embeded page

Google Siteは、割と直感的に利用できますので、手順について詳細を省きます。

参考資料:Google Siteの利用方法

便利な開発ツール:clasp

「ブラウザ上で開発がしんどい。。。」、「Visual Studio Codeを使いたい。。。」、「TypeScriptを使いたい。。。」という方々に朗報です。

GASのローカル環境開発ツール、claspが存在します。

コードをローカルで書いて、GASにpushするなどはもちろん、なんとTypeScriptでGASを開発することもできます!これでVisual Studio Codeのコード補完機能をフルに生かした開発を実現できます。

Webpackなどの外部ツールとの組み合わせによって、Reactなどモダンな開発技術の利用も夢ではありません!是非チャレンジしてみてください!

さいごに

ここまでお読みいただき、ありがとうございました。今回はGASを利用して、Full StackなWebサイトの開発を試みました。

内容の多くはGoogleのドキュメント、他の方のブログ、記事を参考にしましたが、自分の心得も一部含まれています。

「Webサイトを開発してみたいが、お金をかけたくない。」

「GASの開発はよくわからない。」

「仕事上でWebサイトの開発が必要が、予算がなかなかとれない。」

と感じた方々は、是非GASでのWebサイト開発にチャレンジしてみてください!

Appendix

おすすめの記事

条件に該当するページがございません