5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

サーバーレスでデータ連動型社内ツールを作った話

Last updated at Posted at 2025-12-04

はじめに

この記事は LIFULL Advent Calendar 2025 の5日目の記事です。
今回も、愛して止まないGoogle Apps Script(以降、「GAS」と書きます)について書きます。
( 数年投稿をサボっており、3年ぶりの投稿...(^^; )

この記事では、(頑張れば)社内ツールを非エンジニアでも作れる方法を紹介します。

前提

弊社では社内でGoogleAppsを利用しており、
今回紹介する方法は、社内限定による公開ができる仕組みによって実現しているため、
社内でGoogleAppsを使っていない方は多分真似できないです。
予めご了承ください。

背景

私は常々、社内業務の生産性向上のために何かできないかを考えており、
その武器の一つとして、社内ツールで何か作る方法を模索していました。

社内には既に、S3上に静的なHTMLファイル等をアップロードすることで、
社内限定アクセスができるサイトを構築できる仕組みがあるのですが、
この場合、以下の問題があります。

  1. 社内のデータが使えない
  2. データが保存できない
  3. データを共有できない

1の問題に関しては、社内データをJSON等の形でアップロードすれば
使用ができるものの、最新を保つには都度アップロードし直す必要があるため、不便です。

試行錯誤 → うまくいかず、一旦寝かせるの巻

以前に紹介しました、ローコード(GAS)&サーバーレスでWebサービスを創るでは、
Googleフォームで回答した内容が記載されたスプレッドシートの情報を読み込み、
それをGoogleサイト上で表示できるようにするものでした。
今回はこれの応用になります。

Google Apps Scriptでは、作成したコードをウェブアプリとして出力する以外に、
APIとして作る方法があります。

この機能を活かして、以下の2種類のAPIを作成し、
上記のS3サイトからJavaScriptによって操作することを思いつきました。

  • データを取得するためのAPI
  • データを保存(新規保存、更新、削除)するためのAPI

ただし、この場合、ある問題にぶち当たります。
JavaScriptで「異なるドメインのURLに対して自由にアクセスできない」制約、
「同一生成元ポリシー(Same-Origin Policy, SOP)」
です。

クロスドメイン通信が失敗し、CORS(Cross-Origin Resource Sharing)エラーが出ます。

上記への対策方法として、ネットで調べると以下の方法が出てきたのですが、
どちらもうまくいきませんでした。

なお、②の方はデプロイ時にエラーが出ます。たしか。
※執筆時には既に少し古い記憶になっています..

①呼ぶ側のPOSTリクエストのContent-Typeをapplication/jsonからtext/plainに設定する

const response = await fetch('https://script.google.com/macros/s/(デプロイID)/exec', {
  method: 'POST',
  headers: {
    'Content-Type': 'text/plain', // 'application/json'から変更
  },
  body: JSON.stringify(data),
});

②APIのヘッダーに「Access-Control-Allow-Origin」を入れる。

function doOptions(e) {
  return ContentService.createTextOutput() // 本文は空でOK
    .setMimeType(ContentService.MimeType.TEXT)
    .setHeader('Access-Control-Allow-Origin', '*')
    .setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS')
    .setHeader('Access-Control-Allow-Headers', 'Content-Type');
}

上記の構成は諦めることにしました。

光明

行き詰ったので、一度諦めたまま放置していたのですが、
ちょうどこのタイミングで、AIコーディングエージェントが次々と登場し、
自分も触り始めることになりました。

AIなら自分が気づいていない方法で実現してくれるのでは?と思い、
ダメ元で聞いてみたら、悩んでいた悩みを解決する方法でサっとコードを書いてくれました。
AIコーディングエージェント、最高!
そりゃエンジニアがこぞって使いますわな。

構成

構成はわりかしシンプルです。

まず、サイトはGASでウェブアプリとして書き出し、
生成したHTMLに埋め込んだJavaScriptから直接GASを使い、
スプレッドシートに行を足したり、既存の値を変更するなどの操作を行うようにします。
GASで生成したウェブアプリは直接GASが使えるんか!知らなかった...

具体構成

メインとなるGASのファイルには、以下の4つの関数を用意します。

  1. HTMLを出力するためのdoGet関数
  2. データを読み込むための関数
  3. データを保存するための関数
function doGet() {
  return HtmlService.createTemplateFromFile('index').evaluate();
}

function getData() {
  const sheet = SpreadsheetApp.openById('xxx').getActiveSheet();
  const data = sheet.getDataRange().getValues();
  
  // ヘッダー行をスキップして、データを取得
  return data.slice(1).map(row => ({
    hoge: row[0] || '',
    fuga: row[1] || ''
  })).filter(item => (item.hoge || item.fuga));
}

function addData(data) {
  const sheet = SpreadsheetApp.openById('xxx').getActiveSheet();
  
  // 新しい行を追加
  sheet.appendRow([
    data.hoge || '',
    data.fuga || ''
  ]);
  
  return { success: true };
}

次に、HTML用のファイルを用意します。
※GASの画面上で、ファイル作成時に「HTML」を指定し、
 「index.html」として保存してください。

<!DOCTYPE html>
<html lang="ja">
<head>
  <!-- 省略 -->
</head>
<body>
  <!-- データを保存するためのフォーム -->
  <form id="addForm">
    <input type="text" id="hoge">
    <input type="text" id="fuga">
    <button type="submit" class="save-btn" data-key="saveBtn">保存</button>
  </form>
  <!-- 取得したデータを表示させるための空要素 -->
  <div id="list"></div>

  <script>
    var sampleClass = {
      init: function() {
       this.loadDataFromGAS();
        // 他に何か初期設定したいものがあれば書き足す
      },

      render: function(dataToRender) {
        var list = document.getElementById('list');
        if (!list) return;

        var html = '';
        for (var i = 0; i < dataToRender.length; i++) {
          var data = dataToRender[i];
          html += '<div class="item">' + data.hoge + '</div>';
        }
        list.innerHTML = html;


      // GAS経由でデータを取得
      loadFromGAS: function() {
        try {
          if (typeof google !== 'undefined' && google.script && google.script.run) {
            google.script.run
              .withSuccessHandler(function(data) {
                // データ取得後にHTMLに表示
                sampleClass.render();
              })
              .getData();
            }
          } catch (error) {
            // エラー時の処理を書く
        }
      },

      // データ保存時(フォームで保存ボタンを押したとき)の挙動
      handleAddSubmit: function() {
        var hoge = document.getElementById('hoge').value;
        var fuga = document.getElementById('fuga').value;
                
        if (!hoge || !fuga) {
          alert('必須項目を入力してください');
          return;
        }
        var formData = {
          hoge: hoge,
          fuga: fuga
        };
        this.addURL(formData);
      },

      // GASファイルにある「addData」メソッドを呼び出す
      // データ保存後に最新データを再取得&サイト表示させる。
      addURL: function(data) {
        if (typeof google !== 'undefined' && google.script && google.script.run) {
          google.script.run
            .withSuccessHandler(function() {
              sampleClass.loadFromGAS();
              alert('登録が完了しました');
            })
            .withFailureHandler(function(error) {
              alert('登録に失敗しました');
            })
            .addData(data);
          }
        }
      }
    };
    sampleClass.init();
  </script>
</body>
</html>

最後に、GASファイルをウェブアプリとしてデプロイすれば完成です。

なお、今回紹介したデータ操作は新規追加のみとしています。
更新や削除も実現可能だとは思いますが、
複数の人が同時操作することも考えると、整合性が取れなくなるリスクがあるため、
更新や削除はスプレッドシート内で直接行ってもらうようにしました。

応用版

今回紹介した方法は、単純にゼロベースでデータを作るパターンでしたが、
社内のデータを活用することもできます。

データソースがBigQueryであれば、
スプレッドシート上から「データコネクタ」で接続先をBigQueryに指定し、
接続先からデータを取得するだけです。

データをS3にデータをアップロードする方法とは異なり、
この方法であれば、定期的に自動実行するようタイマー設定しておくことで、
アップロードし直すことなくデータの最新状況を保つことができますし、
データソースがスプレッドシートであれば、IMPORTRANGE関数で参照する手もあります。

最後に

アイディア次第では色々な用途に使えますし、
上記で紹介したコードも、AIコーディングエージェントや生成AIを使えば、
時間をかけずとも便利なものが作れるようになるはずです。

何かしら作りたいものが思い浮かびましたら、
是非参考にして作ってみてください。

5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?