🍏

Google Apps Script + gas-db + HTML を使った TODO アプリ

2025/01/25に公開4

以前自分が作った ライブラリをこちらで紹介したのですが、使い所がわかりづらいかもと思いまして、TODOアプリのサンプルを作りました。

このサンプルは以下の技術を組み合わせ、簡易的な TODO リストアプリを構築する例です:

  • gas-db(Google スプレッドシートをデータベースのように扱うためのライブラリ)
  • Google Apps Script のウェブアプリ機能
  • Apps Script から返される HTML(クライアントサイド JS)

GitHub Pages などの外部ホスティングは不要で、すべてを GAS 上で提供します。

また、こちらで紹介したコードはリポジトリ内でも載せていますので
完全なコードが気になる方は こちら をご覧ください。

↓↓↓ サンプルコード
https://github.com/shunta-furukawa/gas-db/tree/master/sample/todos

↓↓↓ 動作するアプリ
(デプロイしたらURL変わるから動かなくなるかも)
https://script.google.com/macros/s/AKfycbzkIlQjRkUMcIqULqbOdTqwgy0j7MxM_-P9nITMb7CH/dev

↓↓ スプレッドシート(誰でも編集できます)
https://docs.google.com/spreadsheets/d/1-QxJN6sEoIEwrhhgLZS_eEAzJSCft3lCqjdgL_cuBRw/edit?gid=0#gid=0


概要

  1. Google スプレッドシート – “データベース” の役割
  2. code.js – サーバーサイドコード(GAS)
    • gas-db を用いてスプレッドシートをCRUD操作
    • エントリーポイント(doGet(e))で index.html を返す
    • TODOリストの取得・作成・更新・削除関数を定義
  3. index.html – クライアントサイド(UI部分)
    • HtmlService から返される
    • google.script.run を使ってサーバー側の関数を呼び出す

手順

1. スプレッドシートの準備

  1. 新規の Google スプレッドシートを作成します。
  2. シート名を Todos とする(コードと合わせるため)。
  3. 見出し行に id, title, completed などを追加。
  4. スプレッドシートの ID を控えておきます(URL の /d//edit の間にある文字列)。

2. GAS プロジェクトを作成

  1. script.google.com にアクセスし、新しいプロジェクト を作成する。
    • あるいはスプレッドシート画面の「拡張機能」→「Apps Script」でも可。
  2. プロジェクト名やその他の設定は任意。

3. コードを追加

  1. gas-db をライブラリとして取り込んでください。 詳細はこちら
  2. code.js の内容をコピーし、GAS プロジェクトに貼り付けてください。(例:ファイル名は Code.gs など)
/**
 * シンプルなTODO管理用のAPIサーバ例
 * gas-db を用いてスプレッドシートをCRUD操作します
 */

function debug() {
    console.log(getDb())
}

/**
 * 1. スプレッドシートおよびシートの取得
 *   gas-db の書き方に合わせて取得
 */
function getDb() {
    // あなたのスプレッドシートID・シート名に適宜書き換えてください
    return new gasdb.Spreadsheet()
        .from("YOUR_SPREADSHEET_ID")
        .at("Todos");
}

/**
 * メインエントリーポイント
 * HTMLファイル (index.html) を返してWebアプリ化する
 */
function doGet(e) {
    return HtmlService.createTemplateFromFile('index') // テンプレート読み込み
        .evaluate()
        // (オプション)最大幅を広げるなどの設定
        .setTitle("TODO App")
}

/**
 * すべてのTODOを取得して配列として返す
 */
function getTodos() {
    const db = getDb();
    // [{id, title, completed}, ...] を全取得
    const rows = db.findAll();
    // booleanなどの型を適切に扱うために変換しておく例
    return rows.map(r => ({
        id: r.id,
        title: r.title,
        completed: (r.completed === true || r.completed === "true")
    }));
}

/**
 * 新規TODOを作成
 */
function createTodo(title) {
    const db = getDb();
    // idは簡易的にタイムスタンプ
    const newTodo = {
        id: new Date().getTime(),
        title: title,
        completed: false
    };
    db.insert(newTodo);
}

/**
 * TODOの完了フラグをトグル
 */
function toggleTodo(id) {
    const db = getDb();
    // 既存の1件を検索
    const existing = db.pick({ id });
    if (!existing) return;
    // 反転した値を更新
    db.update({ completed: !existing.completed }, { id: existing.id });
}

/**
 * TODOを削除
 */
function deleteTodo(id) {
    const db = getDb();
    db.delete({ id: Number(id) });
}

  1. 同じプロジェクト内に index.html ファイルを追加し、リポジトリ内の同名ファイルの内容をコピーします。
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8" />
  <title>My TODO App</title>
  <!-- Onsen UIのCSSとFontAwesomeをCDNから読み込み -->
  <link rel="stylesheet" href="https://unpkg.com/onsenui/css/onsenui.css">
  <link rel="stylesheet" href="https://unpkg.com/onsenui/css/onsen-css-components.min.css">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
  <style>
    .completed {
      text-decoration: line-through;
      color: #999;
    }
  </style>
</head>
<body>
  <ons-page>
    <ons-toolbar>
      <div class="center">TODO List (GAS Web App)</div>
    </ons-toolbar>
    <div style="padding: 10px;">
      <ons-card>
        <div class="title">New Task</div>
        <div class="content">
          <form id="todoForm">
            <ons-input id="todoInput" placeholder="New task..." float required></ons-input>
            <div style="text-align: right; margin-top: 10px;">
              <ons-button type="submit" onclick="createTodo()">Add</ons-button>
            </div>
          </form>
        </div>
      </ons-card>
      <ons-list id="todoList"></ons-list>
    </div>
  </ons-page>

  <!-- Onsen UIのJavaScriptとVueをCDNから読み込み -->
  <script src="https://unpkg.com/vue@2.6.14/dist/vue.min.js"></script>
  <script src="https://unpkg.com/onsenui/js/onsenui.min.js"></script>
  <script>
    document.addEventListener('init', function() {
      fetchTodos();
    });

    function fetchTodos() {
      google.script.run.withSuccessHandler(renderTodos).getTodos();
    }

    function renderTodos(todos) {
      const listEl = document.getElementById('todoList');
      listEl.innerHTML = '';

      todos.forEach(todo => {
        const listItem = document.createElement('ons-list-item');
        listItem.setAttribute('modifier', 'longdivider');
        listItem.innerHTML = `
          <div class="center ${todo.completed ? 'completed' : ''}">${todo.title}</div>
          <div class="right">
            <ons-button modifier="quiet" onclick="toggleTodo(${todo.id})">
              <ons-icon icon="fa-${todo.completed ? 'undo' : 'check'}"></ons-icon>
            </ons-button>
            <ons-button modifier="quiet" onclick="removeTodo(${todo.id})">
              <ons-icon icon="fa-trash"></ons-icon>
            </ons-button>
          </div>
        `;
        listEl.appendChild(listItem);
      });
    }

    function createTodo() {
      const input = document.getElementById('todoInput');
      const title = input.value.trim();
      if (!title) return;

      google.script.run.withSuccessHandler(() => {
        fetchTodos();
      }).createTodo(title);
      input.value = '';
    };

    function toggleTodo(id) {
      google.script.run.withSuccessHandler(() => {
        fetchTodos();
      }).toggleTodo(id);
    }

    function removeTodo(id) {
      google.script.run.withSuccessHandler(() => {
        fetchTodos();
      }).deleteTodo(id);
    }
  </script>
</body>
</html>
  1. code.js の中にある "YOUR_SPREADSHEET_ID" を先ほど控えたスプレッドシートIDに書き換えます。

4. ウェブアプリとしてデプロイ

  1. GAS エディタの上部メニュー「デプロイ」→「新しいデプロイ」をクリック。
  2. 「種類を選択」で 「ウェブアプリ」 を選択。
  3. 「次のユーザーとして実行」 は「自分」を、「アクセスできるユーザー」 は「全員」または用途に応じて選択。
    • 認証無しで誰でもアクセス可能にしたい場合は「全員(匿名ユーザーを含む)」を選択。
  4. 「デプロイ」を押すと表示される ウェブアプリのURL をコピー。

5. 動作確認

  1. ウェブアプリのURLをブラウザで開く。
  2. GAS が index.html を返してTODOアプリ画面が表示される。
  3. 新しいタスクを追加すると、自動でスプレッドシートに行が追加される。
  4. 作成したタスクの完了チェックや削除ボタンが動作するかを確認。

仕組み

  • doGet(e)
    HtmlService を使い index.html を返す。ここがウェブアプリのエントリーポイント。
  • index.html
    ページロード時に google.script.run でサーバーサイドの getTodos() を呼び出し、スプレッドシートから取得したTODOデータを表示。
    ボタン操作などで createTodo(), toggleTodo(), deleteTodo() をサーバー側関数として呼び出し、処理完了後に再取得を実行。
  • サーバーサイド (code.js)
    • getDb()gas-dbSheet インスタンスを返し、Todos シートを操作。
    • 各種CRUD操作(findAll / insert / update / delete)を行って TODO データを管理。
      スプレッドシートへのアクセス権は「このスクリプトを実行するユーザーの権限」で行うか、匿名で許可する場合は「自分として実行」+「全員(匿名ユーザーを含む)」を設定するなど柔軟に調整できます。

よくある質問 (FAQ)

Q. なぜ GitHub Pages でホスティングしないのですか?

  • フロントとバックエンドが別ドメインになる場合、ブラウザの同一生成元ポリシーにより CORS 設定が必要となり、Apps Script の標準機能では簡単に Access-Control-Allow-Origin を設定できません。
  • GAS ウェブアプリとして HTML を提供すれば、同一ドメイン で完結するため、CORS を気にせず実装可能です。

Q. gas-db の他の機能を使いたい場合は?

  • このサンプルでは findAll, find, insert, update, delete のみを使っています
  • スプレッドシートの列を追加し、titlecompleted 以外のデータを扱うことも可能です。

Q. デバッグはどうすればいい?

  • サーバーサイド (GAS) は Logger.log() でログを出せます。エディタの「表示」→「ログ」から確認したり、「実行」→「過去の実行」タブでも参照可能です。
  • クライアント(index.html)側の JavaScript はブラウザのデベロッパーツールで console.log を使って確認してください。

関連リンク


少ない記述量で スプレッドシートをバックエンドとした アプリを作ることができます!
是非試してみてください!!

Discussion

むろひむろひ
Shunta FurukawaShunta Furukawa

ありがとうございます!
何かしらの Google アカウントにログインして頂けたら確認できると思いますが、いかがでしょうか?

むろひむろひ

いくつかのアカウントで試したんですが、無理でした、、

Shunta FurukawaShunta Furukawa

すみません、スプレッドシート自体が閲覧のみになっていたので、編集可能にしてみたのですがどうでしょうか?